using Cinemachine; using UnityEngine; namespace SepCore.CameraModule { /// /// 基于 Cinemachine 的相机控制器。挂载到场景中的主相机上(主相机需有 CinemachineBrain)。 /// 会在主相机同级自动创建 CinemachineVirtualCamera,避免虚拟相机成为被 Brain 驱动相机的子物体。 /// [RequireComponent(typeof(CinemachineBrain))] public class CinemachineCameraController : MonoBehaviour, ICameraController { [SerializeField] private CameraEffectSettings _settings = CameraEffectSettings.Default; [SerializeField] private Vector3 _baseFollowOffset = new Vector3(0f, 10f, -10f); [SerializeField] private bool _lockRotation = true; [SerializeField] private Vector3 _fixedRotationEuler = new Vector3(90f, 0f, 0f); private CinemachineVirtualCamera _virtualCamera; private CinemachineTransposer _transposer; private bool _followDampingEnabled = true; private bool _movementDirectionOffsetEnabled = true; private Vector3 _currentAppliedOffset; private Vector3 _previousTargetPosition; private bool _hasTarget; public Transform Target { get; private set; } public CameraEffectSettings CurrentSettings => _settings; private void Awake() { CreateVirtualCamera(); } private void OnValidate() { ApplyRotationMode(); } private void LateUpdate() { if (!_hasTarget || Target == null) return; if (_movementDirectionOffsetEnabled) UpdateMovementDirectionOffset(); else ResetOffsetSmoothly(); _previousTargetPosition = Target.position; if (_lockRotation) ApplyFixedRotation(); } public void SetTarget(Transform target) { Target = target; _hasTarget = target != null; if (_virtualCamera == null) return; _virtualCamera.Follow = target; ApplyRotationMode(); if (_hasTarget) { _previousTargetPosition = target.position; _currentAppliedOffset = _baseFollowOffset; _transposer.m_FollowOffset = _baseFollowOffset; } } public void EnableEffect(CameraEffectType effect) { switch (effect) { case CameraEffectType.FollowDamping: _followDampingEnabled = true; ApplyDampingValues(); break; case CameraEffectType.MovementDirectionOffset: _movementDirectionOffsetEnabled = true; break; } } public void DisableEffect(CameraEffectType effect) { switch (effect) { case CameraEffectType.FollowDamping: _followDampingEnabled = false; ApplyDampingValues(); break; case CameraEffectType.MovementDirectionOffset: _movementDirectionOffsetEnabled = false; break; } } public bool IsEffectEnabled(CameraEffectType effect) { return effect switch { CameraEffectType.FollowDamping => _followDampingEnabled, CameraEffectType.MovementDirectionOffset => _movementDirectionOffsetEnabled, _ => false, }; } public void ApplySettings(CameraEffectSettings settings) { _settings = settings; ApplyDampingValues(); } private void CreateVirtualCamera() { var go = new GameObject("CM_CameraController_VCam"); go.transform.SetParent(transform.parent, false); go.transform.rotation = Quaternion.Euler(_fixedRotationEuler); _virtualCamera = go.AddComponent(); _virtualCamera.m_Lens.FieldOfView = 60f; _transposer = _virtualCamera.AddCinemachineComponent(); _transposer.m_BindingMode = CinemachineTransposer.BindingMode.WorldSpace; _transposer.m_FollowOffset = _baseFollowOffset; ApplyRotationMode(); ApplyDampingValues(); } private void ApplyRotationMode() { if (_virtualCamera == null) return; if (_lockRotation) { _virtualCamera.LookAt = null; _virtualCamera.DestroyCinemachineComponent(); ApplyFixedRotation(); return; } _virtualCamera.LookAt = Target; if (_virtualCamera.GetCinemachineComponent() == null) _virtualCamera.AddCinemachineComponent(); } private void ApplyFixedRotation() { if (_virtualCamera == null) return; _virtualCamera.transform.rotation = Quaternion.Euler(_fixedRotationEuler); } private void ApplyDampingValues() { if (_transposer == null) return; if (_followDampingEnabled) { _transposer.m_XDamping = _settings.dampingX; _transposer.m_YDamping = _settings.dampingY; _transposer.m_ZDamping = _settings.dampingZ; } else { _transposer.m_XDamping = 0f; _transposer.m_YDamping = 0f; _transposer.m_ZDamping = 0f; } } private void UpdateMovementDirectionOffset() { float dt = Time.deltaTime; Vector3 velocity = (Target.position - _previousTargetPosition) / Mathf.Max(dt, 0.0001f); float speed = new Vector3(velocity.x, 0f, velocity.z).magnitude; Vector3 targetOffset = _baseFollowOffset; if (speed > _settings.offsetDeadZone) { Vector3 dir = new Vector3(velocity.x, 0f, velocity.z).normalized; float offsetAmount = Mathf.Min(speed * _settings.offsetStrength, _settings.maxOffsetDistance); targetOffset += dir * offsetAmount; } _currentAppliedOffset = Vector3.Lerp( _currentAppliedOffset, targetOffset, _settings.offsetSmoothing * dt ); _transposer.m_FollowOffset = _currentAppliedOffset; } private void ResetOffsetSmoothly() { _currentAppliedOffset = Vector3.Lerp( _currentAppliedOffset, _baseFollowOffset, _settings.offsetSmoothing * Time.deltaTime ); _transposer.m_FollowOffset = _currentAppliedOffset; } } }