86 lines
3.9 KiB
C#
86 lines
3.9 KiB
C#
using UnityEngine;
|
||
using Unity.Cinemachine;
|
||
|
||
namespace BaseGames.Camera
|
||
{
|
||
/// <summary>
|
||
/// Y 轴非对称阻尼扩展。实现下落快、起跳缓的非对称相机追随手感:
|
||
/// - <b>下落时快速跟随</b>(低阻尼):玩家落下时相机迅速移动,提前呈现地面地形;
|
||
/// - <b>起跳时缓慢上移</b>(高阻尼):相机不会在跳跃峰值前立刻拉高,保留地面视野。
|
||
///
|
||
/// <para>使用须知:</para>
|
||
/// <list type="bullet">
|
||
/// <item>挂载在 VCamA / VCamB 上(<see cref="CameraStateController"/> 初始化时自动识别)。</item>
|
||
/// <item>此扩展存在时,<see cref="CameraStateController.ConfigureSlot"/> 会自动将
|
||
/// <see cref="CinemachinePositionComposer.Damping"/> 的 Y 分量清零,避免双重阻尼。</item>
|
||
/// <item>阻尼值可由 <see cref="CameraArea"/> 通过 <c>DampingDown</c> / <c>DampingUp</c>
|
||
/// 属性 per-area 覆写,无需手动修改此组件。</item>
|
||
/// </list>
|
||
/// </summary>
|
||
[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;
|
||
|
||
/// <summary>供 <see cref="CameraStateController"/> 运行时按区域写入阻尼值。</summary>
|
||
public float DampingDown { get => _dampingDown; set => _dampingDown = value; }
|
||
/// <summary>供 <see cref="CameraStateController"/> 运行时按区域写入阻尼值。</summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重置内部平滑状态。在相机硬切(instantCut)时由 CameraStateController 调用,
|
||
/// 确保新房间的 Y 坐标从目标位置开始,不受旧房间阻尼状态影响。
|
||
/// </summary>
|
||
public void ResetState() => _initialized = false;
|
||
}
|
||
}
|