Unity中SmoothDamp实现UI动画重启协程时速度递增问题
问题修复:SmoothDamp重启协程后动画加速的问题
问题分析
你推测的方向完全正确——SmoothDamp的vel变量没有在协程重启时重置,导致每次重新触发动画时,之前积累的速度会被保留,叠加后让UI移动越来越快。另外你的StopCoroutine调用方式也存在问题:直接传方法会创建新的协程实例,无法正确停止之前运行的协程,可能导致多个协程同时驱动动画,进一步加剧速度异常。
具体修复步骤
1. 修复协程的启停逻辑(PlayerActionHandler脚本)
需要保存协程引用,确保启停的是同一个实例:
public class PlayerActionHandler : MonoBehaviour { [SerializeField] private Vector3 interactionSize; [SerializeField] PlayerActionController controller; // 保存协程引用,确保启停目标一致 private Coroutine _actionCoroutine; private void Start() { setUpTrigger(); } private void setUpTrigger() { if (controller.interactionObject == null) controller.interactionObject = gameObject; GameObject interactionObject = controller.interactionObject; BoxCollider trigger = interactionObject.AddComponent<BoxCollider>(); trigger.isTrigger = true; trigger.size = interactionSize; } private void OnTriggerEnter(Collider c) { PlayerMovementController p = c.GetComponent<PlayerMovementController>(); if (p == null) return; // 先停止已有协程(如果存在) if (_actionCoroutine != null) StopCoroutine(_actionCoroutine); // 保存新的协程实例 _actionCoroutine = StartCoroutine(controller.actionFieldEnter()); } private void OnTriggerExit(Collider c) { PlayerMovementController p = c.GetComponent<PlayerMovementController>(); if (p == null) return; // 使用保存的引用停止协程 if (_actionCoroutine != null) { StopCoroutine(_actionCoroutine); _actionCoroutine = null; } // 销毁UI后重置速度变量 controller.ResetVelocity(); Destroy(controller.parent); Destroy(controller.iImage.gameObject); // 清空UI引用,避免后续空引用错误 controller.parent = null; controller.iImage = null; } }
2. 重置SmoothDamp的速度变量(PlayerActionController脚本)
在协程启动时强制重置vel为Vector3.zero,并添加外部调用的重置方法:
[Serializable] public class PlayerActionController { [SerializeField] private Key key; [SerializeField] public GameObject interactionObject; [SerializeField] private Vector3 sizeOfInteractionHelper = Vector3.one; [SerializeField] private EventHandler keyAction; [NonSerialized] public GameObject parent; [NonSerialized] public Image iImage; private float animationOffset = 0.1f; private Vector3 vel = Vector3.zero; private Vector3 iOffset; // 公开方法供外部重置速度 public void ResetVelocity() { vel = Vector3.zero; } public bool createUi() { parent = new GameObject("InterActionMenu"); Canvas pCanvas = parent.AddComponent<Canvas>(); RectTransform scaler = parent.GetComponent<RectTransform>(); scaler.sizeDelta = sizeOfInteractionHelper; pCanvas.worldCamera = Camera.main; parent.AddComponent<CanvasScaler>(); parent.AddComponent<GraphicRaycaster>(); GameObject interactionImage = new GameObject("InteractionImage"); interactionImage.transform.parent = parent.transform; iImage = interactionImage.AddComponent<Image>(); iImage.sprite = key.keySprite; iImage.rectTransform.sizeDelta = sizeOfInteractionHelper; iImage.color = Color.white; return true; } public IEnumerator actionFieldEnter() { // 协程启动时立即重置速度,确保初始状态干净 vel = Vector3.zero; yield return new WaitUntil(() => createUi()); iOffset = new Vector3(0, interactionObject.transform.localScale.y + 0.2f + animationOffset, 0); parent.transform.position = interactionObject.transform.position + iOffset; while (!Input.GetKeyDown(key.actionKey)) { controlAnimation(); yield return null; } } private void controlAnimation() { if (parent == null || iImage == null) return; // 移除无意义的新实例创建,完全冗余且无效 float targetY = interactionObject.transform.position.y + iOffset.y; if (MathF.Round(parent.transform.position.y, 1) == MathF.Round(targetY, 1)) { toogleAnimation(-animationOffset); } else { toogleAnimation(animationOffset); } } private void toogleAnimation(float _offset) { animationOffset = _offset; iOffset = new Vector3(0, interactionObject.transform.localScale.y + 0.2f + animationOffset, 0); parent.transform.position = Vector3.SmoothDamp(parent.transform.position, interactionObject.transform.position + iOffset, ref vel, 1.2f); } }
额外优化说明
- 移除了
controlAnimation中new PlayerActionHandler()的无效调用:创建新实例调用Test方法不会输出挂载对象的正确日志,属于冗余代码。 - 在
OnTriggerExit中清空UI引用,避免销毁后残留的无效状态影响下一次动画启动。
修复逻辑说明
- 协程引用保存:确保每次启停的是同一个协程实例,避免多个协程同时驱动动画导致速度叠加。
- 速度变量重置:SmoothDamp的
vel是引用传递的累加变量,每次重启动画时必须重置为0,否则之前的速度会被继承,导致动画越来越快。 - 状态清理:销毁UI后同步清理相关引用和变量,保证下一次动画启动时处于干净的初始状态。
内容的提问来源于stack exchange,提问作者Benjamin Cisek
相关产品推荐
相关产品推荐

