Files
gold_dolphin/unity/Assets/enemy/BillboardSprite.cs
2026-06-28 12:25:22 +08:00

104 lines
4.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using UnityEngine;
namespace IndianOceanAssets.Engine2_5D
{
/// <summary>
/// 场景物件面向摄像机脚本饥荒风格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 才重新吸附 → 摄像机静止时物件彻底不动。
///
/// 用法:挂到需要面向摄像机的精灵/物件上即可。
/// </summary>
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;
}
}
}
}