非MonoBehaviour脚本中调用Coroutine失效问题排查与解决
问题分析
- 空引用错误:你声明的
mono字段没有被赋值就调用了StartCoroutine,运行时会直接抛出NullReferenceException,导致协程根本无法启动。 - 重复启动协程:
UpdateLogic方法每帧都会执行,你在这里直接调用StartCoroutine会导致每一帧都启动一个新的DelayFunction协程,2秒后会批量打印大量日志,完全不符合预期逻辑。
解决方案
下面提供两种靠谱的实现方式,根据你的项目结构选择:
方式一:利用状态机本身的MonoBehaviour(推荐)
如果你的MovementSM是继承自MonoBehaviour的状态机类(这在Unity状态机实现中很常见),直接通过状态机对象启动协程即可,不需要额外声明mono字段:
using System.Collections; using UnityEngine; public class Attacking : BaseState { private MovementSM _sm; public float movementSpeed; private bool _isCoroutineRunning; // 标记协程是否正在运行 public Attacking(MovementSM stateMachine) : base("Attacking", stateMachine) { _sm = stateMachine; } public override void Enter() { base.Enter(); _sm.animator.SetTrigger("attack"); _isCoroutineRunning = false; // 进入状态时重置标记 } public override void UpdateLogic() { base.UpdateLogic(); // 只有协程未运行时才启动 if (!_isCoroutineRunning) { _isCoroutineRunning = true; _sm.StartCoroutine(DelayFunction()); } } IEnumerator DelayFunction() { yield return new WaitForSeconds(2); Debug.Log("Test!"); _isCoroutineRunning = false; // 协程结束后重置标记,允许后续再次触发 } }
方式二:全局协程管理器(适合状态机非MonoBehaviour的场景)
如果你的状态机本身不是MonoBehaviour,可以创建一个全局单例的MonoBehaviour专门管理协程:
1. 创建协程管理器脚本
using UnityEngine; using System.Collections; public class CoroutineManager : MonoBehaviour { public static CoroutineManager Instance { get; private set; } private void Awake() { // 单例初始化,保证全局唯一 if (Instance == null) { Instance = this; DontDestroyOnLoad(gameObject); // 场景切换不销毁 } else { Destroy(gameObject); } } // 对外暴露的协程启动方法 public Coroutine StartManagedCoroutine(IEnumerator coroutine) { return StartCoroutine(coroutine); } }
把这个脚本挂载到任意一个全局存在的GameObject上(比如空的Manager对象)。
2. 修改Attacking脚本调用协程
using System.Collections; using UnityEngine; public class Attacking : BaseState { private MovementSM _sm; public float movementSpeed; private bool _isCoroutineRunning; public Attacking(MovementSM stateMachine) : base("Attacking", stateMachine) { _sm = stateMachine; } public override void Enter() { base.Enter(); _sm.animator.SetTrigger("attack"); _isCoroutineRunning = false; } public override void UpdateLogic() { base.UpdateLogic(); if (!_isCoroutineRunning) { _isCoroutineRunning = true; CoroutineManager.Instance.StartManagedCoroutine(DelayFunction()); } } IEnumerator DelayFunction() { yield return new WaitForSeconds(2); Debug.Log("Test!"); _isCoroutineRunning = false; } }
内容的提问来源于stack exchange,提问作者Abhinav pv
相关产品推荐
相关产品推荐

