如何判断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
相关产品推荐
相关产品推荐

