using UnityEngine; namespace IndianOceanAssets.Engine2_5D { /// /// 场景物件面向摄像机脚本(饥荒风格)v2。 /// 让物体始终朝摄像机方向旋转,默认只绕 Y 轴(保持竖直不倒)。 /// /// 抖动修复(v2): /// 旧版平滑旋转用 Quaternion.Slerp(rot, target, speed * deltaTime), /// 它永远无法真正到达目标(每帧只走剩余距离的几分之一),叠加 deltaTime /// 逐帧波动,会持续产生微小旋转 → 看起来在抖动。若摄像机本身也用 Lerp /// 跟随(CameraFollow 永不收敛),两者叠加更明显。 /// /// 修复要点: /// 1. 指数平滑 t = 1 - exp(-speed * dt),帧率无关,比 speed * dt 更稳定。 /// 2. 剩余角度 ≤ snapAngle 时直接吸附到目标,杜绝永不收敛的微旋转。 /// 3. 已吸附后,目标方向在 deadZone 内的微小漂移(摄像机/物件微抖)一律忽略, /// 只有方向变化超过 deadZone 才重新吸附 → 摄像机静止时物件彻底不动。 /// /// 用法:挂到需要面向摄像机的精灵/物件上即可。 /// public class BillboardSprite : MonoBehaviour { [Tooltip("只绕Y轴旋转(饥荒风格,保持竖直不倒)。关闭则完全面向摄像机。")] [SerializeField] private bool lockYAxis = true; [Tooltip("平滑旋转(过渡),关闭则瞬间转向")] [SerializeField] private bool smoothRotation = false; [Tooltip("平滑旋转速度(越大转得越快)")] [SerializeField] private float rotationSpeed = 8f; [Tooltip("剩余角度小于此值时直接吸附到目标,避免永不收敛的微旋转(主要抖动来源)。")] [SerializeField] private float snapAngle = 0.5f; [Tooltip("已吸附后,目标方向在此角度内的微小漂移视为噪声忽略(过滤摄像机微抖)。")] [SerializeField] private float deadZone = 0.2f; private Camera _mainCamera; private Transform _camTransform; private Quaternion _convergedRot; private bool _hasConverged; private void Start() { _mainCamera = Camera.main; if (_mainCamera != null) _camTransform = _mainCamera.transform; } private void LateUpdate() { if (_camTransform == null) { // 摄像机丢失时重新获取(场景切换等情况) _mainCamera = Camera.main; if (_mainCamera != null) _camTransform = _mainCamera.transform; if (_camTransform == null) return; } Vector3 dir = _camTransform.position - transform.position; if (lockYAxis) dir.y = 0f; // 保持竖直 if (dir.sqrMagnitude < 0.0001f) return; Quaternion targetRot = Quaternion.LookRotation(dir); if (!smoothRotation) { transform.rotation = targetRot; _convergedRot = targetRot; _hasConverged = true; return; } // ---- 平滑旋转 ---- float angleToTarget = Quaternion.Angle(transform.rotation, targetRot); if (angleToTarget <= snapAngle) { // 已接近目标。仅当目标相对上次吸附转了超过 deadZone 才重新吸附, // 否则视为噪声保持不动(摄像机静止时物件不抖) if (!_hasConverged || Quaternion.Angle(targetRot, _convergedRot) > deadZone) { transform.rotation = targetRot; _convergedRot = targetRot; _hasConverged = true; } } else { // 还差较远,指数平滑趋近(帧率无关,比 speed*deltaTime 更稳) float t = 1f - Mathf.Exp(-rotationSpeed * Time.deltaTime); transform.rotation = Quaternion.Slerp(transform.rotation, targetRot, t); _hasConverged = false; } } } }