using UnityEngine;
namespace BaseGames.Camera
{
///
/// 双轴窥视系统(Look Up / Down / Left / Right)。
///
/// - 垂直窥视:玩家按住垂直方向超过 _holdDelayV 秒后,
/// 相机在 Y 轴漂移最多 _lookDistanceV 个世界单位,松开后平滑回弹。
/// - 水平窥视:玩家静止时按住左/右超过 _holdDelayH 秒后,
/// 相机在 X 轴漂移最多 _lookDistanceH 个单位。
/// - 速度门控:玩家速度超过 _speedGateThreshold 时窥视不再激活。
///
/// 挂载位置:Persistent 场景 [Camera] 节点下。
/// CameraStateController 持有此组件的引用(_lookSystem 字段),
/// 在 SetFollowTarget 时自动注册玩家目标,VCam.Follow 指向 VirtualTarget。
///
/// 玩家输入接入:由 PlayerController 调用 SetLookInput(float, float)。
///
public class CameraLookSystem : MonoBehaviour
{
[Header("窥视参数 —— 垂直")]
[Tooltip("持续按住垂直方向键多少秒后触发垂直窥视。0 = 立即触发。推荐 0.8。")]
[SerializeField] private float _holdDelayV = 0.8f;
[Tooltip("最大垂直偏移量(世界单位)。推荐 3~4 单位。")]
[SerializeField] private float _lookDistanceV = 3.5f;
[Tooltip("垂直偏移过渡速度(越大收敛越快)。")]
[SerializeField] private float _lookSpeedV = 2.5f;
[Tooltip("垂直回弹速度(建议稍快于 _lookSpeedV,避免回弹拖沓)。")]
[SerializeField] private float _resetSpeedV = 5f;
[Header("窥视参数 —— 水平")]
[Tooltip("静止后持续按住水平方向键多少秒后触发水平窥视。推荐 0.5。")]
[SerializeField] private float _holdDelayH = 0.5f;
[Tooltip("最大水平偏移量(世界单位)。水平面比垂直面小,避免与 Lookahead 叠加过度。推荐 2.5。")]
[SerializeField] private float _lookDistanceH = 2.5f;
[Tooltip("水平偏移过渡速度。")]
[SerializeField] private float _lookSpeedH = 2.0f;
[Tooltip("水平回弹速度。")]
[SerializeField] private float _resetSpeedH = 5f;
[Header("速度门控")]
[Tooltip("玩家移动速度超过此值时窥视系统不再新增偏移,已有偏移平滑回弹。推荐 2.5。")]
[SerializeField] private float _speedGateThreshold = 2.5f;
// ── 内部状态 ──────────────────────────────────────────────────────────
private Transform _baseTarget;
private Transform _virtualTargetTransform;
private Vector3 _lastBasePosition;
private float _estimatedSpeed;
// 垂直窥视
private float _holdTimerV;
private float _inputY;
private float _currentOffsetY;
private float _targetOffsetY;
// 水平窥视
private float _holdTimerH;
private float _inputX;
private float _currentOffsetX;
private float _targetOffsetX;
///
/// VCam 应跟随此 Transform(玩家位置 + 窥视偏移)。
/// 由 赋值给 VCam.Follow。
///
public Transform VirtualTarget => _virtualTargetTransform;
// ── Lifecycle ─────────────────────────────────────────────────────────
private void Awake()
{
var go = new GameObject("[CameraLookTarget]")
{
hideFlags = HideFlags.HideInHierarchy
};
DontDestroyOnLoad(go);
_virtualTargetTransform = go.transform;
}
private void OnDestroy()
{
if (_virtualTargetTransform != null)
Destroy(_virtualTargetTransform.gameObject);
}
// ── 公开 API ──────────────────────────────────────────────────────────
///
/// 注册玩家的 CameraFollowTarget。通常由 调用。
///
public void SetBaseTarget(Transform target)
{
_baseTarget = target;
_lastBasePosition = target != null ? target.position : Vector3.zero;
}
///
/// 传入归一化输入(x = 水平,y = 垂直)。
/// 由 PlayerController / InputReader 在 Update 中调用。
///
public void SetLookInput(float horizontal, float vertical)
{
_inputX = Mathf.Clamp(horizontal, -1f, 1f);
_inputY = Mathf.Clamp(vertical, -1f, 1f);
}
/// 仅设置垂直窥视输入(向后兼容旧调用方式)。
public void SetLookInput(float vertical) => SetLookInput(0f, vertical);
///
/// 重置窥视状态。房间切换(即时硬切)时调用,
/// 避免旧房间的窥视偏移残留影响新房间的相机初始位置。
///
///
/// true = 立即将当前偏移归零(硬切时推荐);
/// false = 仅清除目标偏移,让当前偏移通过正常 Update 平滑回弹。
///
public void ResetOffsets(bool snap = false)
{
_holdTimerV = 0f;
_holdTimerH = 0f;
_targetOffsetY = 0f;
_targetOffsetX = 0f;
if (snap)
{
_currentOffsetY = 0f;
_currentOffsetX = 0f;
if (_baseTarget != null && _virtualTargetTransform != null)
_virtualTargetTransform.position = _baseTarget.position;
}
}
// ── Update ────────────────────────────────────────────────────────────
private void LateUpdate()
{
if (_baseTarget == null || _virtualTargetTransform == null) return;
// ── 速度估算(速度门控基准)────────────────────────────────────────
float dt = Time.deltaTime;
if (dt > 0f)
{
float rawSpeed = (_baseTarget.position - _lastBasePosition).magnitude / dt;
_estimatedSpeed = Mathf.Lerp(_estimatedSpeed, rawSpeed, 1f - Mathf.Exp(-dt * 8f));
}
_lastBasePosition = _baseTarget.position;
bool withinGate = _estimatedSpeed < _speedGateThreshold;
// ── 垂直窥视 ──────────────────────────────────────────────────────
if (withinGate && Mathf.Abs(_inputY) > 0.5f)
{
_holdTimerV += dt;
if (_holdTimerV >= _holdDelayV)
_targetOffsetY = _inputY * _lookDistanceV;
}
else
{
_holdTimerV = 0f;
_targetOffsetY = 0f;
}
float speedV = Mathf.Abs(_targetOffsetY) < 0.01f ? _resetSpeedV : _lookSpeedV;
_currentOffsetY = Mathf.Lerp(_currentOffsetY, _targetOffsetY, 1f - Mathf.Exp(-dt * speedV));
// ── 水平窥视 ──────────────────────────────────────────────────────
if (withinGate && Mathf.Abs(_inputX) > 0.5f)
{
_holdTimerH += dt;
if (_holdTimerH >= _holdDelayH)
_targetOffsetX = _inputX * _lookDistanceH;
}
else
{
_holdTimerH = 0f;
_targetOffsetX = 0f;
}
float speedH = Mathf.Abs(_targetOffsetX) < 0.01f ? _resetSpeedH : _lookSpeedH;
_currentOffsetX = Mathf.Lerp(_currentOffsetX, _targetOffsetX, 1f - Mathf.Exp(-dt * speedH));
// ── 虚拟目标 = 玩家位置 + 双轴偏移 ────────────────────────────────
_virtualTargetTransform.position = _baseTarget.position
+ new Vector3(_currentOffsetX, _currentOffsetY, 0f);
}
}
}