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

如何判断Unity协程Fade的结束状态并正确触发存档逻辑?

Unity 淡入淡出协程与存档逻辑的同步问题

我在Unity项目里写了Fader和SavingGame两个脚本,需求是:按下K键触发屏幕淡入,点击UI按钮调用Save方法触发淡出,必须等淡出协程完全结束后,再执行StartSaving的存档逻辑。但现在用静态标志位isInState的方式经常逻辑混乱,会提前触发存档,想问下怎么正确判断Fade协程的结束状态(不管是淡入还是淡出完成),并在合适时机触发存档?


原始Fader脚本

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

public class Fader : MonoBehaviour
{
    public bool fadeAutomatically = false;
    public float fadeTime;
    public CanvasGroup canvasGroup;
    public static bool isInState = false;
    private Coroutine _currentRoutine;
    private bool _currentDirection;
    private bool _nextDirection;

    private void Start()
    {
        if (canvasGroup == null)
        {
            canvasGroup = GameObject.FindGameObjectWithTag("Loading Saved Game Description").GetComponent<CanvasGroup>();
        }
    }

    public void StartFade(bool isIn, bool autoSwitch)
    {
        if (_currentRoutine != null)
        {
            if (_currentDirection != isIn)
            {
                StopCoroutine(_currentRoutine);

                _currentRoutine = StartCoroutine(Fade(canvasGroup, isIn, autoSwitch));
            }
        }
        else
        {
            _currentRoutine = StartCoroutine(Fade(canvasGroup, isIn, autoSwitch));
        }
    }

    private void Update()
    {
        if (!fadeAutomatically)
        {
            if (Input.GetKeyDown(KeyCode.O))
            {
                StartFade(false,false);
            }

            if (Input.GetKeyDown(KeyCode.I))
            {
                StartFade(true,false);
            }
        }
        else
        {
            StartFade(_nextDirection,true);
        }
    }

    IEnumerator Fade(CanvasGroup canvasGroup, bool isIn, bool autoSwitch)
    {
        _currentDirection = isIn;

        var startValue = isIn ? 0 : 1;
        var targetValue = isIn ? 1 : 0;

        var elapsedTime = fadeTime * (isIn ? canvasGroup.alpha : (1 - canvasGroup.alpha));
        while (elapsedTime <= fadeTime)
        {
            canvasGroup.alpha = Mathf.Lerp(startValue, targetValue, elapsedTime / fadeTime);

            yield return null;

            elapsedTime += Time.deltaTime;
        }

        canvasGroup.alpha = targetValue;

        if (autoSwitch) _nextDirection = !isIn;

        _currentRoutine = null;

        if(isIn == false)
        {
            isInState = true;
        }
        else
        {

        }
    }
}

原始SavingGame脚本

using UnityEngine;
using System.Collections;
using System.IO;
using TMPro;

public class SavingGame : MonoBehaviour
{
    public int resWidth = 1920;
    public int resHeight = 1080;
    public SaveLoad saveLoad;
    public Fader fader;
    public TMP_InputField _inputField;

    private static int countName;

    private void Start()
    {
        countName = 0;
        
        string[] dirs = Directory.GetDirectories(Application.persistentDataPath + "\\" + "Saved Screenshots",
            "*.*", SearchOption.TopDirectoryOnly);

        if(dirs.Length > 0)
        {
            countName = dirs.Length;
        }
    }

    public static string ScreenShotName(int width, int height)
    {
        return string.Format("{0}/Saved Screenshots/SaveSlot{1} SavedGameSlot_{2}x{3}_{4}/SavedGameSlot_{1}x{2}_{3}.png",
            Application.persistentDataPath,
            countName,
            width, height, System.DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"));
    }

    void Update()
    {
        if (Input.GetKeyDown("k"))
        {
            fader.StartFade(true, false);
        }

        if(Fader.isInState)
        {
            StartSaving();
            Fader.isInState = false;
        }
    }

    public void Save()
    {
        fader.StartFade(false, false);
    }

    public void StartSaving()
    {
        string filename = ScreenShotName(resWidth, resHeight);
        string directory = Path.GetDirectoryName(filename);
        Directory.CreateDirectory(directory);
        ScreenCapture.CaptureScreenshot(filename);
        string descriptionContent = _inputField.text;
        if (_inputField.text.Length != 0)
        {
            string descriptionFileFolder = directory + "\\" + "Description.txt";
            File.WriteAllText(descriptionFileFolder, descriptionContent);
        }
        StartCoroutine(saveLoad.SaveWithTime(directory, Path.GetFileNameWithoutExtension(filename) + ".savegame.txt"));

        countName++;
    }
}

解决方案:用回调/事件替代静态标志位

静态变量的问题在于全局共享,很容易被其他逻辑意外修改,导致状态判断出错。改用回调委托来通知协程结束,能精准控制时机,逻辑更可靠。

步骤1:修改Fader脚本

  • 移除静态变量isInState
  • 添加Action<bool>委托,用来通知外部淡入淡出完成(参数bool表示当前是淡入/淡出)
  • 在Fade协程末尾触发这个委托

修改后的Fader脚本:

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

public class Fader : MonoBehaviour
{
    public bool fadeAutomatically = false;
    public float fadeTime;
    public CanvasGroup canvasGroup;
    // 新增:fade完成时触发的回调,参数isIn表示完成的是淡入(true)还是淡出(false)
    public Action<bool> OnFadeComplete;
    
    private Coroutine _currentRoutine;
    private bool _currentDirection;
    private bool _nextDirection;

    private void Start()
    {
        if (canvasGroup == null)
        {
            canvasGroup = GameObject.FindGameObjectWithTag("Loading Saved Game Description").GetComponent<CanvasGroup>();
        }
    }

    public void StartFade(bool isIn, bool autoSwitch)
    {
        if (_currentRoutine != null)
        {
            if (_currentDirection != isIn)
            {
                StopCoroutine(_currentRoutine);
                _currentRoutine = StartCoroutine(Fade(canvasGroup, isIn, autoSwitch));
            }
        }
        else
        {
            _currentRoutine = StartCoroutine(Fade(canvasGroup, isIn, autoSwitch));
        }
    }

    private void Update()
    {
        if (!fadeAutomatically)
        {
            if (Input.GetKeyDown(KeyCode.O))
            {
                StartFade(false,false);
            }

            if (Input.GetKeyDown(KeyCode.I))
            {
                StartFade(true,false);
            }
        }
        else
        {
            StartFade(_nextDirection,true);
        }
    }

    IEnumerator Fade(CanvasGroup canvasGroup, bool isIn, bool autoSwitch)
    {
        _currentDirection = isIn;

        var startValue = isIn ? 0 : 1;
        var targetValue = isIn ? 1 : 0;

        var elapsedTime = fadeTime * (isIn ? canvasGroup.alpha : (1 - canvasGroup.alpha));
        while (elapsedTime <= fadeTime)
        {
            canvasGroup.alpha = Mathf.Lerp(startValue, targetValue, elapsedTime / fadeTime);
            yield return null;
            elapsedTime += Time.deltaTime;
        }

        canvasGroup.alpha = targetValue;

        if (autoSwitch) _nextDirection = !isIn;
        _currentRoutine = null;

        // 触发回调,通知外部fade完成
        OnFadeComplete?.Invoke(isIn);
    }
}

步骤2:修改SavingGame脚本

  • 在Start中订阅Fader的OnFadeComplete事件
  • 实现回调方法,仅在淡出完成时执行存档
  • 移除Update中对静态变量的判断
  • 在OnDestroy中取消订阅,避免内存泄漏

修改后的SavingGame脚本:

using UnityEngine;
using System.Collections;
using System.IO;
using TMPro;

public class SavingGame : MonoBehaviour
{
    public int resWidth = 1920;
    public int resHeight = 1080;
    public SaveLoad saveLoad;
    public Fader fader;
    public TMP_InputField _inputField;

    private static int countName;

    private void Start()
    {
        countName = 0;
        
        string[] dirs = Directory.GetDirectories(Application.persistentDataPath + "\\" + "Saved Screenshots",
            "*.*", SearchOption.TopDirectoryOnly);

        if(dirs.Length > 0)
        {
            countName = dirs.Length;
        }

        // 订阅Fader的fade完成事件
        fader.OnFadeComplete += HandleFadeComplete;
    }

    private void OnDestroy()
    {
        // 取消订阅,防止内存泄漏
        fader.OnFadeComplete -= HandleFadeComplete;
    }

    // Fade完成后的回调处理
    private void HandleFadeComplete(bool isIn)
    {
        // 只有淡出(isIn=false)完成时,才执行存档逻辑
        if (!isIn)
        {
            StartSaving();
        }
    }

    public static string ScreenShotName(int width, int height)
    {
        return string.Format("{0}/Saved Screenshots/SaveSlot{1} SavedGameSlot_{2}x{3}_{4}/SavedGameSlot_{1}x{2}_{3}.png",
            Application.persistentDataPath,
            countName,
            width, height, System.DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"));
    }

    void Update()
    {
        if (Input.GetKeyDown("k"))
        {
            fader.StartFade(true, false);
        }
    }

    public void Save()
    {
        fader.StartFade(false, false);
    }

    public void StartSaving()
    {
        string filename = ScreenShotName(resWidth, resHeight);
        string directory = Path.GetDirectoryName(filename);
        Directory.CreateDirectory(directory);
        ScreenCapture.CaptureScreenshot(filename);
        string descriptionContent = _inputField.text;
        if (_inputField.text.Length != 0)
        {
            string descriptionFileFolder = directory + "\\" + "Description.txt";
            File.WriteAllText(descriptionFileFolder, descriptionContent);
        }
        StartCoroutine(saveLoad.SaveWithTime(directory, Path.GetFileNameWithoutExtension(filename) + ".savegame.txt"));

        countName++;
    }
}

逻辑说明

  • 按下K键触发淡入,完成后回调收到isIn=true,不会触发存档
  • 点击UI按钮调用Save触发淡出,协程结束后回调收到isIn=false,此时执行StartSaving,完美实现等待淡出完成再存档的需求
  • 这种方式避免了静态变量的全局状态污染,逻辑清晰且不会出现提前触发的问题

内容的提问来源于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.21 05:15:34