Unity C#里氏替换原则实践疑问:第三人称相机锚点控制类设计
第三人称相机SOLID实现疑问解析与优化方案
在练习用SOLID原则重构第三人称相机控制代码时,针对锚点控制部分的两个核心疑问,解析如下:
一、AnchorControlForThirdPersonCamera类的方法是否需要拆分?
当前类同时承担了锚点生命周期管理(创建/销毁)、锚点旋转计算(含输入读取、角度限制)、锚点位置计算三类职责,既违反了单一职责原则(SRP),也限制了*里氏替换原则(LSP)*的灵活性——如果后续需要替换旋转逻辑(比如手柄输入、固定角度锁死),当前类的高耦合会导致修改成本很高。
建议拆分职责,拆分为三个独立的类与对应接口:
- 锚点生命周期管理类:专注锚点的创建、销毁、实例持有,只保留与锚点生命周期相关的方法。
- 锚点旋转控制类:独立实现旋转逻辑,包含输入读取、旋转速度、角度限制等参数,后续可轻松替换为不同输入方式的实现。
- 锚点位置计算类:专注根据玩家位置与偏移量计算锚点位置,职责单一。
拆分后,每个类只负责一件事,且符合里氏替换原则——任何实现对应接口的子类都可以无缝替换原有实现,不会影响整体相机逻辑。
二、anchor变量应该放在哪个类?
建议保留在锚点生命周期管理类中(原AnchorControlForThirdPersonCamera或拆分后的AnchorLifecycleManager),原因如下:
- 职责归属清晰:锚点的创建、持有、销毁是锚点管理类的核心职责,由它持有
anchor变量完全符合单一职责原则。 - 降低耦合:如果主相机类持有
anchor,则需要承担锚点的生命周期管理逻辑,会增加主相机的职责,后续替换锚点实现时还需修改主相机代码,违反开闭原则(OCP)。 - 符合迪米特法则:主相机只需要通过接口获取锚点的
Transform实例,无需关心锚点的创建、销毁细节,减少不必要的依赖。
另外,当前代码中Update()重复调用CreateAnchor()是冗余操作,CreateAnchor()已包含空判断,仅需在Awake()中调用一次即可。
优化后的代码示例
拆分后的核心接口
// 锚点生命周期管理接口 public interface IAnchorLifecycleManager { Transform GetAnchor(); void CreateAnchor(); void OnDestroyAnchor(); } // 锚点旋转控制接口 public interface IAnchorRotationControl { void CalculateRotation(Transform anchor); } // 锚点位置计算接口 public interface IAnchorPositionCalculator { void CalculatePosition(Transform anchor, Transform player); }
锚点生命周期管理类
public class AnchorLifecycleManager : MonoBehaviour, IAnchorLifecycleManager { [SerializeField] private string anchorName = "CameraAnchor"; private Transform anchor; public Transform GetAnchor() => anchor; public void CreateAnchor() { if (!anchor) { anchor = new GameObject(anchorName).transform; anchor.hideFlags = HideFlags.HideInHierarchy; } } public void OnDestroyAnchor() { if (anchor) Destroy(anchor); } }
鼠标驱动的锚点旋转控制类
public class MouseAnchorRotationControl : MonoBehaviour, IAnchorRotationControl { [Header("Rotation Settings")] [SerializeField] private string inpMouseX = "Mouse X"; [SerializeField] private string inpMouseY = "Mouse Y"; [SerializeField] private float rotationSpeed = 100; [SerializeField, Range(1, 179)] private float minAxisY = 89; [SerializeField, Range(181, 359)] private float maxAxisY = 320; public void CalculateRotation(Transform anchor) { float axisMouseX = Input.GetAxis(inpMouseX); float axisMouseY = Input.GetAxis(inpMouseY); Vector3 anchorEuler = anchor.eulerAngles; anchorEuler.y += axisMouseX * rotationSpeed * Time.fixedDeltaTime; anchorEuler.x -= axisMouseY * rotationSpeed * Time.fixedDeltaTime; // 角度限制逻辑 if (anchorEuler.x > minAxisY && anchorEuler.x < 180) anchorEuler.x = minAxisY; if (anchorEuler.x < maxAxisY && anchorEuler.x > 180) anchorEuler.x = maxAxisY; anchor.eulerAngles = anchorEuler; } }
基于偏移量的锚点位置计算类
public class OffsetAnchorPositionCalculator : MonoBehaviour, IAnchorPositionCalculator { [SerializeField] private Vector3 offset = new Vector3(0, 1, 0); public void CalculatePosition(Transform anchor, Transform player) { anchor.position = player.TransformPoint(offset); } }
修改后的主相机类
public class ThirdPersonCamera : MonoBehaviour { private IAnchorLifecycleManager _anchorLifecycle; private IAnchorRotationControl _anchorRotation; private IAnchorPositionCalculator _anchorPosition; private ICameraPositionRayForThirdPersonCamera _cameraPositionRay; private ICameraPositionForThirdPersonCamera _cameraPosition; private ICameraOrientationForThirdPersonCamera _cameraOrientation; [SerializeField] private Transform player; private RaycastHit hitForCameraPos = new RaycastHit(); private Ray rayForCameraPos; private void Awake() { // 初始化各依赖,自动添加缺失的组件 _anchorLifecycle = GetComponent<IAnchorLifecycleManager>() ?? gameObject.AddComponent<AnchorLifecycleManager>(); _anchorRotation = GetComponent<IAnchorRotationControl>() ?? gameObject.AddComponent<MouseAnchorRotationControl>(); _anchorPosition = GetComponent<IAnchorPositionCalculator>() ?? gameObject.AddComponent<OffsetAnchorPositionCalculator>(); _cameraPositionRay = GetComponent<ICameraPositionRayForThirdPersonCamera>() ?? gameObject.AddComponent<CameraPositionRayForThirdPersonCamera>(); _cameraPosition = GetComponent<ICameraPositionForThirdPersonCamera>() ?? gameObject.AddComponent<CameraPositionForThirdPersonCamera>(); _cameraOrientation = GetComponent<ICameraOrientationForThirdPersonCamera>() ?? gameObject.AddComponent<CameraOrientationForThirdPersonCamera>(); _anchorLifecycle.CreateAnchor(); } void Start() { Cursor.lockState = CursorLockMode.Locked; Cursor.visible = false; Transform anchor = _anchorLifecycle.GetAnchor(); _anchorRotation.CalculateRotation(anchor); rayForCameraPos = _cameraPositionRay.CreateRayForCameraPosition(anchor); hitForCameraPos = _cameraPosition.SpherecastForCalculateCameraPosition(rayForCameraPos); _anchorPosition.CalculatePosition(anchor, player); rayForCameraPos = _cameraPositionRay.CreateRayForCameraPosition(anchor); transform.position = _cameraPosition.GetCameraPosition(rayForCameraPos, hitForCameraPos); transform.rotation = _cameraOrientation.GetCameraLookAtTargetPoint(anchor, transform); } void Update() { Transform anchor = _anchorLifecycle.GetAnchor(); _anchorRotation.CalculateRotation(anchor); rayForCameraPos = _cameraPositionRay.CreateRayForCameraPosition(anchor); hitForCameraPos = _cameraPosition.SpherecastForCalculateCameraPosition(rayForCameraPos); } void LateUpdate() { Transform anchor = _anchorLifecycle.GetAnchor(); _anchorPosition.CalculatePosition(anchor, player); rayForCameraPos = _cameraPositionRay.CreateRayForCameraPosition(anchor); transform.position = _cameraPosition.GetCameraPosition(rayForCameraPos, hitForCameraPos); transform.rotation = _cameraOrientation.GetCameraLookAtTargetPoint(anchor, transform); } private void OnDestroy() { _anchorLifecycle.OnDestroyAnchor(); } }
内容的提问来源于stack exchange,提问作者Ary Guilherme Pires Caramez
相关产品推荐
相关产品推荐

