218 lines
7.0 KiB
C#
218 lines
7.0 KiB
C#
using Cinemachine;
|
||
using UnityEngine;
|
||
|
||
namespace SepCore.CameraModule
|
||
{
|
||
/// <summary>
|
||
/// 基于 Cinemachine 的相机控制器。挂载到场景中的主相机上(主相机需有 CinemachineBrain)。
|
||
/// 会在主相机同级自动创建 CinemachineVirtualCamera,避免虚拟相机成为被 Brain 驱动相机的子物体。
|
||
/// </summary>
|
||
[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<CinemachineVirtualCamera>();
|
||
|
||
_virtualCamera.m_Lens.FieldOfView = 60f;
|
||
|
||
_transposer = _virtualCamera.AddCinemachineComponent<CinemachineTransposer>();
|
||
_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<CinemachineHardLookAt>();
|
||
ApplyFixedRotation();
|
||
return;
|
||
}
|
||
|
||
_virtualCamera.LookAt = Target;
|
||
|
||
if (_virtualCamera.GetCinemachineComponent<CinemachineHardLookAt>() == null)
|
||
_virtualCamera.AddCinemachineComponent<CinemachineHardLookAt>();
|
||
}
|
||
|
||
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;
|
||
}
|
||
}
|
||
}
|