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

Unity中有限状态机状态切换时如何中断当前状态执行?

Unity有限状态机:状态切换后中断当前Update执行的替代方案

这是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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 20:47:34