Unity中有限状态机状态切换时如何中断当前状态执行?
这是Unity中有限状态机(FSM)的纯C#问题,基础状态结构如下:
public abstract class BaseState { public StateMachine stateMachine; public abstract void EnterState(); public abstract void UpdateState(); public abstract void ExitState(); }
正常状态切换流程应该是:
stateA.UpdateState() ----- stateA.UpdateState() ----- stateA.UpdateState <- 此帧触发切换至stateB stateMachine.ChangeState(stateB) stateA.ExitState() stateB.EnterState() ----- stateB.UpdateState() ----- stateB.UpdateState()
但实际执行时,当前状态的UpdateState不会在切换后立即停止,顺序变成:
stateA.UpdateState()中状态切换前的所有逻辑
退出stateA
进入stateB
stateA.UpdateState()中状态切换后的剩余逻辑
需要彻底移除最后这一步。直接在切换后return是最简单的,但我的实际代码结构是这样的:
public void UpdateState(float deltaTime) { FunctionA(); // 这些函数都可能触发状态切换,还会修改状态内部变量(想避免) FunctionB(); FunctionC(); FunctionD(); }
没法在这些函数内部用return中断,而给每个状态加enabled标志、在每步调用后加if (!enabled) return;又太繁琐,尤其是这些函数还会调用其他可能触发切换的函数时。
我试过用自定义StateEndedException抛出异常来中断执行,虽然能生效,但属于反模式(类似花哨的goto),状态机代码如下:
public class StateMachine { State currentState; void UpdateState() { try { currentState.UpdateState(); } catch (Exception e) { if (e is not StateEndedException) throw e; } } void ChangeState(State newState) { currentState.ExitState(); newState.EnterState(); currentState = newState; throw new StateEndedException(); } }
下面是几个更合理的替代方案:
方案1:让状态切换相关函数返回布尔值,逐层检查
给每个可能触发状态切换的函数(包括子函数)返回bool,表示是否已经触发了状态切换。在UpdateState里每调用一个函数就检查返回值,一旦为true就直接退出。
示例代码:
public void UpdateState(float deltaTime) { if (FunctionA()) return; if (FunctionB()) return; if (FunctionC()) return; if (FunctionD()) return; } private bool FunctionA() { if (切换条件满足) { stateMachine.ChangeState(stateB); return true; } // 其他逻辑 return false; }
如果函数内部还调用了可能触发切换的子函数,也要传递这个布尔值:
private bool FunctionA() { if (SubFunction()) return true; // 其他逻辑 return false; } private bool SubFunction() { if (切换条件满足) { stateMachine.ChangeState(stateB); return true; } return false; }
方案2:状态机统一维护"状态已切换"标志
给StateMachine加一个isStateChanged字段,在ChangeState中标记为true,然后在UpdateState的每一步调用后(甚至子函数内部的关键节点)检查这个标志,一旦为true就退出。
示例代码:
public class StateMachine { State currentState; public bool isStateChanged { get; private set; } void UpdateState() { isStateChanged = false; currentState.UpdateState(); } void ChangeState(State newState) { if (isStateChanged) return; // 防止同一帧重复切换 currentState.ExitState(); newState.EnterState(); currentState = newState; isStateChanged = true; } } // 状态类的UpdateState实现 public void UpdateState(float deltaTime) { FunctionA(); if (stateMachine.isStateChanged) return; FunctionB(); if (stateMachine.isStateChanged) return; FunctionC(); if (stateMachine.isStateChanged) return; FunctionD(); } // 子函数内部也可以插入检查 private void FunctionA() { if (切换条件满足) { stateMachine.ChangeState(stateB); return; } // 执行部分逻辑后 if (stateMachine.isStateChanged) return; // 剩余逻辑 }
这个方案比每个状态单独维护enabled更集中,不需要修改状态的内部字段,所有状态都复用状态机的标志。
方案3:用迭代器/Unity协程实现可中断的流程
利用C#迭代器或者Unity协程,把UpdateState拆分成可中断的执行步骤,状态切换时直接终止当前迭代器/协程。
迭代器版本示例:
public abstract class BaseState { public StateMachine stateMachine; public abstract IEnumerator UpdateStateCoroutine(float deltaTime); public abstract void EnterState(); public abstract void ExitState(); } public class StateMachine { State currentState; IEnumerator currentUpdateIterator; void Update() { if (currentUpdateIterator == null) { currentUpdateIterator = currentState.UpdateStateCoroutine(Time.deltaTime); } // 如果迭代器还能继续执行就走下一步,否则重置 if (!currentUpdateIterator.MoveNext()) { currentUpdateIterator = null; } } void ChangeState(State newState) { currentState.ExitState(); newState.EnterState(); currentState = newState; currentUpdateIterator = null; // 终止当前迭代器,下一帧会启动新状态的迭代器 } } // 具体状态的实现 public class StateA : BaseState { public override IEnumerator UpdateStateCoroutine(float deltaTime) { yield return ExecuteFunctionA(); yield return ExecuteFunctionB(); yield return ExecuteFunctionC(); yield return ExecuteFunctionD(); } private IEnumerator ExecuteFunctionA() { // 执行逻辑 if (切换条件满足) { stateMachine.ChangeState(stateB); yield break; // 终止当前迭代器 } // 剩余逻辑 yield return null; } }
Unity协程版本:
直接用Unity的StartCoroutine和StopCoroutine,状态切换时停止当前状态的协程,启动新状态的协程,原理和迭代器一致,更贴合Unity的使用习惯。
方案4:重构逻辑,分离状态切换判断与纯业务逻辑
如果代码结构允许,把所有可能触发状态切换的判断逻辑集中到一个单独的阶段,先执行所有切换检查,确认没有切换后再执行不会触发切换的纯业务逻辑。
示例代码:
public void UpdateState(float deltaTime) { // 第一阶段:检查所有可能的状态切换条件 if (CheckStateTransition()) return; // 第二阶段:执行不会触发切换的纯逻辑 RunStateLogic(deltaTime); } // 集中处理所有状态切换判断 private bool CheckStateTransition() { if (条件1满足) { stateMachine.ChangeState(stateB); return true; } if (条件2满足) { stateMachine.ChangeState(stateC); return true; } // 把原FunctionA/FunctionB里的切换条件都移到这里 return false; } // 只执行纯业务逻辑,不会触发状态切换 private void RunStateLogic(float deltaTime) { FunctionA_LogicOnly(); FunctionB_LogicOnly(); FunctionC_LogicOnly(); FunctionD_LogicOnly(); }
这个方案需要重构现有代码,但能彻底避免切换后执行剩余逻辑的问题,后续维护也更清晰,状态切换的逻辑都集中在一处。
内容的提问来源于stack exchange,提问作者Nerudian

