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

如何让Unity UI淡入淡出从当前进度接续而非重新开始?

问题根源

原代码存在两个核心问题:

  1. 每次触发淡入淡出都会启动新协程,旧协程仍在后台运行,导致透明度被多个协程同时修改,出现混乱
  2. 每次启动动画时强制将透明度重置为淡入/淡出的起始值(0或1),完全忽略当前的透明度状态

修复后的淡入淡出脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

public class Description : MonoBehaviour
{
    public Canvas canvas;
    public AnimationCurve animationCurve;
    public float fadingSpeed = 5f;
    
    public TMP_InputField _inputField;

    public enum Direction { FadeIn, FadeOut };

    private CanvasGroup canvasGroup;
    private Coroutine currentFadeCoroutine; // 跟踪当前运行的淡入淡出协程

    void Start()
    {
        if (canvas == null) canvas = GetComponent<Canvas>();
        canvasGroup = canvas.GetComponent<CanvasGroup>();
        if (canvasGroup == null) Debug.LogError("请给画布添加CanvasGroup组件!");

        if (animationCurve.length == 0)
        {
            Debug.Log("未指定动画曲线,将创建默认缓入缓出曲线");
            animationCurve = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
        }
    }

    public float StartFading(bool InOut)
    {
        if (canvasGroup != null)
        {
            // 停止当前协程,避免多个协程冲突
            if (currentFadeCoroutine != null)
            {
                StopCoroutine(currentFadeCoroutine);
            }
            
            Direction direction = InOut ? Direction.FadeIn : Direction.FadeOut;
            currentFadeCoroutine = StartCoroutine(FadeCanvas(canvasGroup, direction, fadingSpeed));
        }

        return fadingSpeed;
    }

    public IEnumerator FadeCanvas(CanvasGroup canvasGroup, Direction direction, float totalDuration)
    {
        // 从当前透明度反向计算对应的动画曲线进度
        float currentAlpha = canvasGroup.alpha;
        float currentProgress = GetCurrentProgressFromAlpha(currentAlpha);

        // 计算剩余需要完成的进度和对应时长
        float targetProgress = direction == Direction.FadeIn ? 1f : 0f;
        float progressDelta = Mathf.Abs(targetProgress - currentProgress);
        float remainingDuration = totalDuration * progressDelta;

        if (remainingDuration <= 0f)
        {
            canvasGroup.alpha = animationCurve.Evaluate(targetProgress);
            _inputField?.SetReadOnly(false);
            yield break;
        }

        var startTime = Time.time;
        var endTime = startTime + remainingDuration;

        while (Time.time <= endTime)
        {
            float elapsedTime = Time.time - startTime;
            float percentage = elapsedTime / remainingDuration;
            float newProgress = Mathf.Lerp(currentProgress, targetProgress, percentage);
            canvasGroup.alpha = animationCurve.Evaluate(newProgress);

            yield return new WaitForEndOfFrame();
        }

        // 确保最终透明度准确
        canvasGroup.alpha = animationCurve.Evaluate(targetProgress);
        _inputField?.SetReadOnly(false);
        
        currentFadeCoroutine = null;
    }

    // 根据当前透明度反向查找对应的动画曲线进度
    private float GetCurrentProgressFromAlpha(float alpha)
    {
        float closestProgress = 0f;
        float closestAlphaDiff = Mathf.Infinity;

        // 先找最接近的关键帧进度
        foreach (Keyframe keyframe in animationCurve.keys)
        {
            float diff = Mathf.Abs(keyframe.value - alpha);
            if (diff < closestAlphaDiff)
            {
                closestAlphaDiff = diff;
                closestProgress = keyframe.time;
            }
        }

        // 处理关键帧之间的插值情况
        for (int i = 0; i < animationCurve.keys.Length - 1; i++)
        {
            Keyframe start = animationCurve.keys[i];
            Keyframe end = animationCurve.keys[i + 1];

            if ((alpha >= start.value && alpha <= end.value) || (alpha <= start.value && alpha >= end.value))
            {
                float alphaRange = end.value - start.value;
                if (alphaRange == 0) continue;
                float alphaPercent = (alpha - start.value) / alphaRange;
                closestProgress = Mathf.Lerp(start.time, end.time, alphaPercent);
                break;
            }
        }

        return Mathf.Clamp01(closestProgress);
    }
}

关键修改说明

  • 新增currentFadeCoroutine变量,每次触发新动画时先停止旧协程,避免多个协程同时修改透明度
  • 移除强制重置起始透明度的逻辑,改为从当前透明度反向计算动画进度
  • 添加GetCurrentProgressFromAlpha方法,实现从透明度到动画曲线进度的反向映射,保证动画从当前状态续接
  • 动态计算剩余动画时长,确保淡入淡出的速度始终一致

触发脚本无需修改

原ButtonHover脚本的逻辑保持不变即可,所有中断续接逻辑已在Description脚本中处理。

内容的提问来源于stack exchange,提问作者Daniel Lip

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 22:54:32