You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 07:24:53