using UnityEngine; using DG.Tweening; public class BubbleFloat : MonoBehaviour { [Header("浮动范围设置")] [SerializeField] private float minY = -20f; // 相对于初始位置的最低偏移 [SerializeField] private float maxY = 20f; // 相对于初始位置的最高偏移 [Header("动画设置")] [SerializeField] private float duration = 2f; // 单程时间 [SerializeField] private Ease easeType = Ease.InOutSine; [SerializeField] private bool useRandomDelay = true; // 是否开启随机延迟 private Vector3 initialPosition; private Tween floatTween; private void Awake() { // 记录 UI 挂载时的原始本地坐标 initialPosition = transform.localPosition; } private void OnEnable() { RestartAnimation(); } private void RestartAnimation() { KillTween(); // 1. 确保回到初始参考点 transform.localPosition = initialPosition; float startY = initialPosition.y + minY; float endY = initialPosition.y + maxY; // 2. 先设置初始位置(起始点) transform.localPosition = new Vector3(initialPosition.x, startY, initialPosition.z); // 3. 创建动画并记录 // 注意:我们将动画赋值给变量,并强制立即执行 floatTween = transform.DOLocalMoveY(endY, duration) .SetEase(easeType) .SetLoops(-1, LoopType.Yoyo) .SetUpdate(true); if (useRandomDelay) { // 关键修正:先让动画计算出初始状态,再跳转进度 // 否则在创建的第一帧,动画系统可能还没意识到它需要移动 float randomTime = Random.Range(0f, duration * 2f); floatTween.Goto(randomTime, true); // true 表示立即执行回调和位移 } } private void OnDisable() { KillTween(); } private void OnDestroy() { KillTween(); } private void KillTween() { if (floatTween != null) { floatTween.Kill(); floatTween = null; } } }