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;
}
}
}