using UnityEngine;
using Unity.Cinemachine;
namespace BaseGames.Camera
{
///
/// Y 轴非对称阻尼扩展。实现下落快、起跳缓的非对称相机追随手感:
/// - 下落时快速跟随(低阻尼):玩家落下时相机迅速移动,提前呈现地面地形;
/// - 起跳时缓慢上移(高阻尼):相机不会在跳跃峰值前立刻拉高,保留地面视野。
///
/// 使用须知:
///
/// - 挂载在各 CameraArea 的专属 DedicatedCamera 上( 激活区域时自动识别)。
/// - 此扩展存在时, 会自动将
/// 的 Y 分量清零,避免双重阻尼。
/// - 阻尼值可由 通过 DampingDown / DampingUp
/// 属性 per-area 覆写,无需手动修改此组件。
///
///
[AddComponentMenu("Cinemachine/Extensions/Camera Asymmetric Damping")]
[DisallowMultipleComponent]
public class CameraAsymmetricDampingExtension : CinemachineExtension
{
[Tooltip("相机向下(下落)时的 Y 轴阻尼(秒)。\n" +
"越小跟随越快,玩家能提前看到地面。推荐 0.05 ~ 0.15。")]
[SerializeField] private float _dampingDown = 0.08f;
[Tooltip("相机向上(起跳)时的 Y 轴阻尼(秒)。\n" +
"越大跟随越慢,保留地面视野、不会在起跳瞬间拉高。推荐 0.5 ~ 0.8。")]
[SerializeField] private float _dampingUp = 0.65f;
/// 供 运行时按区域写入阻尼值。
public float DampingDown { get => _dampingDown; set => _dampingDown = value; }
/// 供 运行时按区域写入阻尼值。
public float DampingUp { get => _dampingUp; set => _dampingUp = value; }
private float _smoothedY;
private bool _initialized;
protected override void PostPipelineStageCallback(
CinemachineVirtualCameraBase vcam,
CinemachineCore.Stage stage,
ref CameraState state,
float deltaTime)
{
if (stage != CinemachineCore.Stage.Body) return;
// deltaTime <= 0:编辑器预览 / 初始帧,重置平滑器避免脏状态
if (deltaTime <= 0f)
{
_initialized = false;
return;
}
// 此时 state.RawPosition 是 CinemachinePositionComposer(Damping.y = 0)输出的"理想"位置
float idealY = state.RawPosition.y;
if (!_initialized)
{
_smoothedY = idealY;
_initialized = true;
return;
}
// idealY < _smoothedY → 相机目标在当前位置下方(玩家下落) → 低阻尼快速跟随
// idealY > _smoothedY → 相机目标在当前位置上方(玩家起跳) → 高阻尼缓慢跟随
float damping = idealY < _smoothedY ? _dampingDown : _dampingUp;
float t = damping > 0f
? 1f - Mathf.Exp(-deltaTime / damping)
: 1f;
_smoothedY = Mathf.LerpUnclamped(_smoothedY, idealY, t);
var pos = state.RawPosition;
pos.y = _smoothedY;
state.RawPosition = pos;
}
///
/// 重置内部平滑状态。在相机硬切(instantCut)时由 CameraStateController 调用,
/// 确保新房间的 Y 坐标从目标位置开始,不受旧房间阻尼状态影响。
///
public void ResetState() => _initialized = false;
}
}