免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

游戏AI开发:有限状态机(FSM)核心原理与C#实战框架详解

游戏AI开发:有限状态机(FSM)核心原理与C#实战框架详解 1. 项目概述为什么有限状态机是游戏AI的“定海神针”如果你在游戏开发中尤其是涉及到角色行为控制时感觉自己的代码逐渐变成了一团“意大利面条”——各种if-else嵌套状态标志位满天飞逻辑耦合得剪不断理还乱——那么是时候认真了解一下有限状态机Finite-State Machine, FSM了。这玩意儿不是什么高深莫测的黑科技它更像是一种帮你把混乱思维整理得井井有条的“编程哲学”。简单来说FSM就是把一个复杂实体的行为拆解成若干个明确的“状态”并规定好这些状态之间在什么条件下可以互相转换。想象一下你控制的游戏角色站立、奔跑、跳跃、攻击、受伤、死亡……这些不就是一个个清晰的状态吗FSM就是帮你管理这些状态切换的“交通警察”。在游戏AI决策系统里FSM扮演着核心骨架的角色。它逻辑清晰、易于实现和调试是处理那些行为模式相对固定、可预测的AI比如巡逻的卫兵、按照固定套路攻击的Boss的绝佳工具。虽然现在行为树、GOAP目标导向行动规划、HTN分层任务网络等更复杂的AI架构被广泛讨论但FSM凭借其直观性和在简单场景下的高效性依然是绝大多数游戏项目中不可或缺的基础。很多看似复杂的行为树底层其叶子节点执行具体动作的节点内部往往就是一个微型的FSM在运作。所以掌握FSM不仅是学会一个工具更是理解复杂AI系统如何从简单模块搭建起来的关键一步。2. 有限状态机的核心思想与设计模式拆解2.1 从生活到代码理解状态机的本质让我们先抛开代码用最生活的例子理解FSM。你家的空调就是一个典型的状态机。它的状态可能有关机、制冷、制热、送风。触发状态转换的条件或称“事件”是你按下遥控器上的“开/关”、“模式”、“温度”等按钮。规则很明确从关机状态按下“开”且模式设为“制冷”进入制冷状态在制冷状态按下“模式”切换到“制热”则进入制热状态在任何状态按下“关”都回到关机状态。你看整个系统的行为被描述得清清楚楚没有歧义。把这个模型映射到游戏角色上状态State空闲Idle、巡逻Patrol、追击Chase、攻击Attack、逃跑Flee。转换条件Transition Condition发现玩家Player Sighted、进入攻击范围In Attack Range、生命值过低Low Health、丢失目标Target Lost。当前状态Current State同一时刻角色只能处于上述状态中的某一个。FSM的核心规则就是下一个状态只能由当前状态转换而来。这强制你思考清楚所有合法的行为路径避免了逻辑上的混乱。比如角色不能直接从空闲状态跳到攻击状态中间必须经过发现玩家进入追击和进入攻击范围进入攻击这两个条件和状态的过渡。2.2 状态的三段式生命周期Enter, Update, Exit一个设计良好的状态类应该清晰地定义其在生命周期不同阶段的行为。这是FSM实现中最经典、最重要的模式通常包含三个核心方法进入状态Enter当从其他状态切换到此状态时立即执行一次的逻辑。这是进行“初始化”操作的最佳位置。典型操作播放该状态对应的动画如Animator.CrossFade(“Attack”)、播放音效攻击吼叫、重置计时器、设置移动速度、生成攻击判定框等。为什么单独提出来因为有些逻辑只需要在状态开始时跑一次如果混在持续更新的逻辑里就需要额外的布尔标志位来控制增加了复杂度。状态更新Update / LogicalUpdate当处于此状态时每帧或每个固定时间间隔都会执行的逻辑。这是状态“活着”时的主要行为。典型操作持续向目标移动在追击状态、检测转换条件是否满足如“是否到达攻击距离”、更新动画状态、消耗能量等。核心任务在这里判断并触发向其他状态的转换。这是FSM的“决策心脏”。退出状态Exit当从此状态切换到另一个状态前立即执行一次的清理逻辑。典型操作停止播放某些特效、回收临时对象、保存状态数据以备将来返回、取消预定的指令等。容易被忽略但至关重要它保证了状态切换的“干净”避免资源泄漏或逻辑冲突。例如从攻击状态退出时必须关闭攻击判定的碰撞体否则角色在跑动时可能还会伤及无辜。用一个攻击状态的伪代码来感受一下public class AttackState : IState { private float attackTimer; private Collider attackHitBox; public void Enter() { // 1. 播放攻击动画 animator.Play(Attack); // 2. 激活攻击判定碰撞体 attackHitBox.enabled true; // 3. 重置攻击计时器 attackTimer 0f; Debug.Log(进入攻击状态); } public void Update(float deltaTime) { // 1. 更新计时器 attackTimer deltaTime; // 2. 攻击动画结束后自动回到空闲状态 if (attackTimer attackAnimationLength) { stateMachine.ChangeStateIdleState(); } // 3. 同时检测如果目标突然死亡也应转换状态 if (target.IsDead) { stateMachine.ChangeStateIdleState(); } } public void Exit() { // 1. 务必关闭攻击碰撞体 attackHitBox.enabled false; // 2. 可以在这里通知其他系统“攻击动作结束” Debug.Log(退出攻击状态。); } }2.3 状态机的管理集中式与分散式决策状态机本身需要一个管理者负责持有当前状态、状态列表并驱动状态的生命周期。这里有两种常见的设计思路集中式转换Centralized Transition在状态机管理器如FSMManager的Update中集中检查所有可能的转换条件。这种方式将所有转换逻辑放在一起一目了然适合状态数量少、转换关系简单的情况。但当状态增多时管理器的Update会变得非常臃肿难以维护。// 在FSMManager的Update中 void Update() { switch (currentState) { case State.Idle: if (SeePlayer()) ChangeState(State.Chase); if (IsHurt()) ChangeState(State.Hurt); break; case State.Chase: if (InAttackRange()) ChangeState(State.Attack); if (LostPlayer()) ChangeState(State.Patrol); break; // ... 更多状态 } currentState.OnUpdate(); }分散式转换Decentralized Transition这也是目前更主流、更符合面向对象思想的做法。将转换条件的判断下放到每个具体状态类的Update方法中。每个状态只关心“我能转换到哪去”。正如参考文章示例中所做的那样在Player_Idle状态的LogicalUpdate里判断IsTryDown和IsTryJump来决定是切换到Player_Down还是Player_Jumping。这样做的好处是高内聚、低耦合每个状态类自成一体包含了自身的所有行为逻辑和转换逻辑。添加新状态时几乎不需要修改其他已有状态只需在新状态的Update里写好它的转换条件即可。这大大提升了代码的可维护性和可扩展性。实操心得状态转换的“推”与“拉”在分散式转换中状态如何获取转换条件通常有两种方式“拉”模式Pull状态在Update里主动去查询所需的数据。比如Player_Idle状态直接读取agent.IsTryDown一个封装了输入检测的属性。这是最简单直接的方式。“推”模式Push由外部系统如输入管理器、事件系统在事件发生时向状态机发送一个“事件”如OnJumpPressed状态机再将这个事件转发给当前状态由当前状态决定是否响应并转换。这种方式更解耦适合基于事件的复杂系统。对于初学者建议先从“拉”模式开始逻辑更清晰。3. 从零构建一个可复用的C#状态机框架理解了思想我们动手实现一个比参考文章更健壮、更通用的FSM框架。这个框架将严格遵循上述三段式生命周期和分散式转换原则。3.1 定义状态接口与基类首先我们定义最核心的状态接口。它强制所有具体状态实现三个生命周期方法。/// summary /// 状态接口。所有具体状态必须实现。 /// /summary public interface IState { /// summary /// 当进入此状态时调用。 /// /summary void OnEnter(); /// summary /// 当在此状态时每帧调用。 /// /summary /// param namedeltaTime帧时间差/param void OnUpdate(float deltaTime); /// summary /// 当退出此状态时调用。 /// /summary void OnExit(); }但直接实现接口每个状态类都要重复写一些公共代码比如获取状态机引用、访问控制的实体Agent。因此我们创建一个抽象的泛型基类它持有状态机和实体的引用并提供一些便利方法。/// summary /// 状态基类。所有具体状态应继承自此基类。 /// /summary /// typeparam nameT状态机类型/typeparam /// typeparam nameU控制实体Agent类型/typeparam public abstract class StateBaseT, U : IState where T : StateMachineBaseU { /// summary /// 所属的状态机实例。 /// /summary protected T StateMachine { get; private set; } /// summary /// 状态所控制的实体如玩家、敌人。 /// /summary protected U Agent { get; private set; } /// summary /// 初始化状态注入状态机和实体。 /// /summary public void Initialize(T stateMachine, U agent) { StateMachine stateMachine; Agent agent; } // 这三个虚方法提供了默认的空实现子类按需重写。 public virtual void OnEnter() { } public virtual void OnUpdate(float deltaTime) { } public virtual void OnExit() { } /// summary /// 便捷方法请求切换到另一个状态。 /// /summary /// typeparam nameS目标状态类型/typeparam protected void ChangeStateS() where S : IState { StateMachine.ChangeStateS(); } }这个基类做了几件重要的事依赖注入通过Initialize方法将状态机和实体“注入”到状态中状态内部可以直接使用StateMachine和Agent。提供默认实现OnEnter、OnUpdate、OnExit都是虚方法且为空子类不必强制重写所有方法更灵活。封装转换请求提供了ChangeStateS()这个泛型方法让子状态切换状态时语法更简洁、安全类型安全。3.2 构建状态机管理器接下来是状态机管理器它是大脑负责注册状态、切换状态、驱动状态更新。using System; using System.Collections.Generic; /// summary /// 状态机基类。 /// /summary /// typeparam nameU控制实体Agent类型/typeparam public class StateMachineBaseU { // 使用字典以状态类型为Key存储所有已注册的状态实例。 private DictionaryType, IState _states new DictionaryType, IState(); // 当前活跃的状态。 private IState _currentState; // 控制实体。 protected U Agent { get; private set; } /// summary /// 当前状态只读。 /// /summary public IState CurrentState _currentState; public StateMachineBase(U agent) { Agent agent; } /// summary /// 注册一个状态到状态机。状态必须继承自StateBaseT, U。 /// /summary /// typeparam nameS要注册的状态类型/typeparam public void RegisterStateS() where S : StateBaseStateMachineBaseU, U, new() { Type stateType typeof(S); if (_states.ContainsKey(stateType)) { Debug.LogWarning($状态 {stateType.Name} 已经注册过了。); return; } // 创建状态实例并为其注入状态机和实体。 S state new S(); state.Initialize(this, Agent); _states.Add(stateType, state); } /// summary /// 启动状态机并进入初始状态。 /// /summary /// typeparam nameS初始状态类型/typeparam public void StartS() where S : IState { ChangeStateS(); } /// summary /// 切换到指定状态。 /// /summary /// typeparam nameS目标状态类型/typeparam public void ChangeStateS() where S : IState { Type newStateType typeof(S); if (!_states.TryGetValue(newStateType, out IState newState)) { throw new ArgumentException($状态 {newStateType.Name} 未注册); } // 执行旧状态的退出逻辑 _currentState?.OnExit(); // 切换当前状态引用 _currentState newState; // 执行新状态的进入逻辑 _currentState.OnEnter(); } /// summary /// 每帧更新驱动当前状态的OnUpdate。 /// /summary /// param namedeltaTime帧时间差/param public void Update(float deltaTime) { _currentState?.OnUpdate(deltaTime); } }这个状态机管理器有几个关键设计点泛型设计StateMachineBaseU是泛型类U代表被控制的实体类型如PlayerController,EnemyAI。这使得状态机可以轻松应用到不同类型的对象上。状态注册使用RegisterStateS()方法注册状态。状态实例在注册时被创建和初始化并以Type为键存储在字典中。这比在外部创建好再传入更内聚。安全的类型转换ChangeStateS()是泛型方法调用时直接指定状态类型S编译器会保证类型安全。在内部通过typeof(S)从字典中获取实例。清晰的切换流程在ChangeState中严格遵循了旧状态.OnExit() - 切换引用 - 新状态.OnEnter()的顺序这是FSM正确运行的铁律。3.3 实战应用实现一个敌人AI状态机让我们用这个框架实现一个经典的敌人AI巡逻 - 发现玩家 - 追击 - 攻击 - 丢失目标 - 返回巡逻。首先定义我们的敌人实体类它持有状态机并提供一些共享的数据和方法。using UnityEngine; public class EnemyAI : MonoBehaviour { public Transform player; // 玩家引用 public float sightRange 10f; // 视野范围 public float attackRange 2f; // 攻击范围 public float patrolSpeed 2f; public float chaseSpeed 5f; public Transform[] patrolPoints; // 巡逻路径点 private int currentPatrolIndex 0; // 敌人的状态机 public StateMachineBaseEnemyAI StateMachine { get; private set; } void Start() { // 1. 初始化状态机传入自身作为控制实体 StateMachine new StateMachineBaseEnemyAI(this); // 2. 注册所有可能的状态 StateMachine.RegisterStatePatrolState(); StateMachine.RegisterStateChaseState(); StateMachine.RegisterStateAttackState(); // 3. 启动状态机从巡逻状态开始 StateMachine.StartPatrolState(); } void Update() { // 4. 每帧更新状态机 StateMachine.Update(Time.deltaTime); } // --- 供状态查询的公共方法 --- public bool CanSeePlayer() { if (player null) return false; float distance Vector3.Distance(transform.position, player.position); return distance sightRange; } public bool IsPlayerInAttackRange() { if (player null) return false; float distance Vector3.Distance(transform.position, player.position); return distance attackRange; } public Vector3 GetNextPatrolPoint() { if (patrolPoints null || patrolPoints.Length 0) return transform.position; Vector3 target patrolPoints[currentPatrolIndex].position; if (Vector3.Distance(transform.position, target) 0.5f) { currentPatrolIndex (currentPatrolIndex 1) % patrolPoints.Length; } return patrolPoints[currentPatrolIndex].position; } }现在实现三个具体状态。注意它们都继承自StateBaseT, U并指定了正确的泛型参数。// 巡逻状态 public class PatrolState : StateBaseStateMachineBaseEnemyAI, EnemyAI { public override void OnEnter() { Debug.Log(${Agent.name}: 进入巡逻状态); // 可以在这里设置巡逻速度、播放巡逻动画等 } public override void OnUpdate(float deltaTime) { // 1. 执行巡逻逻辑向下一个路径点移动 Vector3 targetPoint Agent.GetNextPatrolPoint(); Agent.transform.position Vector3.MoveTowards(Agent.transform.position, targetPoint, Agent.patrolSpeed * deltaTime); // 2. 检查转换条件是否发现玩家 if (Agent.CanSeePlayer()) { ChangeStateChaseState(); // 使用基类提供的便捷方法 } } public override void OnExit() { Debug.Log(${Agent.name}: 退出巡逻状态); } } // 追击状态 public class ChaseState : StateBaseStateMachineBaseEnemyAI, EnemyAI { public override void OnEnter() { Debug.Log(${Agent.name}: 进入追击状态锁定目标); // 可以在这里播放警觉音效、设置追击速度等 } public override void OnUpdate(float deltaTime) { if (Agent.player null) { ChangeStatePatrolState(); return; } // 1. 执行追击逻辑向玩家移动 Vector3 direction (Agent.player.position - Agent.transform.position).normalized; Agent.transform.position direction * Agent.chaseSpeed * deltaTime; // 2. 检查转换条件 if (Agent.IsPlayerInAttackRange()) { ChangeStateAttackState(); // 进入攻击范围转为攻击 } else if (!Agent.CanSeePlayer()) { ChangeStatePatrolState(); // 丢失视野返回巡逻 } } public override void OnExit() { Debug.Log(${Agent.name}: 退出追击状态); } } // 攻击状态 public class AttackState : StateBaseStateMachineBaseEnemyAI, EnemyAI { private float attackCooldown 2f; private float lastAttackTime; public override void OnEnter() { Debug.Log(${Agent.name}: 进入攻击状态发动攻击); lastAttackTime Time.time; // 播放攻击动画生成攻击特效等 } public override void OnUpdate(float deltaTime) { // 1. 执行攻击逻辑例如冷却时间到了就攻击一次 if (Time.time - lastAttackTime attackCooldown) { PerformAttack(); lastAttackTime Time.time; } // 2. 检查转换条件 if (!Agent.IsPlayerInAttackRange()) { // 玩家跑出攻击范围但还在视野内则继续追击 if (Agent.CanSeePlayer()) { ChangeStateChaseState(); } else { // 玩家完全消失返回巡逻 ChangeStatePatrolState(); } } // 注意这里没有检查玩家是否死亡可以在PerformAttack中处理或由外部事件触发状态转换。 } private void PerformAttack() { Debug.Log(${Agent.name} 对玩家造成了伤害); // 这里实现具体的伤害计算、击退等逻辑 } public override void OnExit() { Debug.Log(${Agent.name}: 退出攻击状态); // 停止攻击动画关闭攻击判定等 } }将这个EnemyAI脚本挂载到一个游戏对象上设置好player引用和patrolPoints一个具备基础智能的敌人就诞生了。它的行为逻辑完全由三个状态类定义清晰且易于修改。如果你想增加一个“受伤后逃跑”的状态只需要新建一个FleeState类并在AttackState和ChaseState的OnUpdate里加入生命值过低的检查条件然后切换到新状态即可原有代码几乎无需改动。4. 高级技巧与实战避坑指南掌握了基础框架我们来看看如何让FSM更强大、更稳健以及在实际项目中容易踩的坑。4.1 状态间数据传递与共享状态之间经常需要传递信息。例如追击状态可能需要知道玩家最后被看见的位置以便在丢失目标后前往该点搜寻。有几种常见做法通过控制实体Agent共享这是最简单的方式。在EnemyAI类中定义公共字段或属性供所有状态读写。public class EnemyAI : MonoBehaviour { // ... 其他字段 public Vector3 LastKnownPlayerPosition { get; set; } // 最后已知的玩家位置 } // 在 ChaseState 的 OnUpdate 中 if (Agent.CanSeePlayer()) { Agent.LastKnownPlayerPosition Agent.player.position; } // 在 PatrolState 中可以前往 LastKnownPlayerPosition 查看优点简单直接。缺点污染了Agent的接口所有状态都能访问可能被误修改。通过状态机上下文Context传递创建一个专门用于状态间通信的数据容器上下文对象作为状态机构造参数的一部分。这比直接放在Agent里更清晰意图更明确。public class EnemyStateContext { public Vector3 LastKnownPlayerPosition; public bool IsAlerted; // ... 其他共享数据 } public class StateMachineBaseU, C // 增加一个泛型参数C代表上下文 { protected C Context { get; private set; } // ... 在ChangeState等方法中可以将Context传递给状态 }使用事件Event或消息Message当状态发生特定事情时发布一个事件。其他状态或系统可以订阅这些事件并做出反应。这种方式耦合度最低但系统复杂度会增加。// 在 AttackState 的 PerformAttack 中 public static event ActionEnemyAI, float OnEnemyAttacked; OnEnemyAttacked?.Invoke(Agent, damageAmount); // 其他状态或UI系统可以订阅这个事件避坑指南状态转换的“瞬时”与“延迟”在ChangeState方法中我们立即调用了旧状态的OnExit()和新状态的OnEnter()。这意味着状态转换是瞬时完成的。这带来了一个潜在问题如果在OnExit或OnEnter中又触发了另一个ChangeState比如在退出攻击状态时条件立刻满足又进入了追击状态可能会导致递归调用甚至栈溢出。解决方案引入“延迟转换”机制。状态机维护一个“下一状态”的请求队列在当前帧所有状态更新完成后再在下一帧初执行实际的切换。或者更简单的做法是在状态机内部设置一个标志位isChangingState在切换过程中将其设为true并在ChangeState开头检查如果正在切换中则忽略新的请求或将其加入队列。4.2 分层与并行状态机参考文章中提到了使用两个状态机分别控制身体大动作和手臂动作这引出了FSM的两个高级概念分层状态机Hierarchical FSM, HFSM将状态组织成树形结构。父状态可以包含子状态机。当处于某个父状态时其子状态机是活跃的。这允许你进行更高层次的抽象。例如一个战斗Combat父状态其下可以有近战Melee、远程Ranged、防御Defend等子状态。当退出战斗状态时其下的所有子状态自动终止。这极大地减少了状态数量的爆炸式增长并提高了逻辑的复用性。并行状态机Parallel FSM多个状态机同时在同一实体上运行各自管理独立的行为维度。就像参考文章的例子一个FSM管理移动站立、下蹲、跳跃另一个FSM管理上半身动作空闲、挥拳。它们并行更新互不干扰。这在处理角色复合动作如边跑边射击、边跳边挥剑时非常有用。实现思路在你的PlayerController或EnemyAI中直接声明并管理多个StateMachineBase实例即可。关键在于确保它们更新的数据源如输入、传感器是协调一致的并且要注意动画层Animation Layer的匹配就像参考文章中对Animator.CrossFade使用不同层级索引那样。4.3 与动画系统的深度集成游戏中的状态机与动画状态机Animator Controller是天作之合。通常有两种集成模式代码驱动动画Code-Driven就像我们的示例和参考文章所做在状态的OnEnter中直接调用Animator.Play()或CrossFade()来播放对应的动画片段。状态转换条件也由代码逻辑决定。这种方式灵活且强大你可以实现非常复杂的转换逻辑比如根据距离播放不同强度的攻击动画但需要手动管理动画过渡和参数。public override void OnEnter() { // 直接播放动画0.2秒混合时间 agent.Animator.CrossFade(Attack_Heavy, 0.2f); }动画驱动代码Animation-Driven在Unity Animator中设置好状态和转换条件Parameters。在代码中你只负责更新这些参数如Animator.SetBool(“IsRunning”, true)。实际的动画状态切换由Animator自己管理。你可以在动画片段中嵌入事件Animation Events在特定帧触发代码逻辑如生成攻击判定的帧。这种方式直观、便于动画师协作但对于复杂的行为逻辑Animator的图形化界面可能变得难以维护。最佳实践对于简单的、视觉反馈明确的行为移动、基础攻击可以使用动画驱动。对于复杂的、逻辑驱动的AI行为整个敌人的决策流程使用代码驱动的FSM并在其中调用动画。很多项目采用混合模式一个主FSM控制AI决策决策结果通过设置Animator参数来驱动动画同时Animator中的动画事件反过来触发FSM中的某些逻辑如攻击生效。4.4 状态机可视化与调试纯代码的FSM在状态多起来后调试会变得困难。你无法直观地看到当前处于哪个状态转换是如何发生的。以下是一些提升开发效率的技巧添加调试信息在每个状态的OnEnter和OnExit中加入Debug.Log输出状态名。在状态机的Update中可以绘制GUI文本显示当前状态。void OnGUI() { GUILayout.Label($当前状态: {StateMachine.CurrentState?.GetType().Name}); }自定义编辑器工具为你的状态机类编写一个自定义的Editor脚本在Unity Inspector窗口中以图表或下拉菜单的形式显示当前状态和所有已注册状态。这需要一定的Editor GUI编程知识但能极大提升调试体验。使用现有插件Unity Asset Store上有许多优秀的行为树和状态机可视化插件如NodeCanvas、Behavior Designer它们提供了图形化编辑和运行时调试功能。如果你的项目预算允许直接使用这些成熟方案可以节省大量开发时间尤其是在设计复杂AI时。5. 有限状态机的局限性与混合架构FSM并非银弹它有明显的局限性状态爆炸State Explosion当行为复杂时状态数量会呈组合式增长。例如一个角色有移动走、跑、跳、战斗轻击、重击、格挡、情绪平静、愤怒三个维度如果全用单一FSM就需要3 * 3 * 2 18个状态且转换条件网会极其复杂。僵化Rigidity转换逻辑硬编码在状态内部。要修改一个转换必须修改状态类的代码。对于需要频繁调整和迭代的游戏玩法来说这不够灵活。难以处理目标导向行为FSM是“反应式”的它根据当前条件和事件决定下一个状态。但对于“我要去那里拿个钥匙然后开门”这类需要多步规划、有明确目标的任务纯FSM会显得力不从心。因此在现代游戏AI中FSM常常作为更高级AI架构的执行层或组成部分FSM 行为树Behavior Tree行为树负责高层的决策和规划选择做什么而叶子节点Action Node内部则是一个小的FSM负责具体执行这个动作怎么做。例如行为树决定“攻击”叶子节点里的FSM则管理“接近 - 攻击动画 - 冷却”这一系列子状态。FSM GOAP/HTN正如参考文章末尾提到的GOAP目标导向行动规划或HTN分层任务网络负责规划出一系列行动来达成目标然后将这个行动序列交给一个FSM去顺序执行。FSM负责处理执行中的中断如被攻击并反馈给规划器进行重新规划。分层FSMHFSM如前所述用层次结构来管理复杂状态本身就是对基础FSM局限性的重要补充。结论有限状态机是游戏AI领域坚实的地基。它概念简单实现直接是处理明确、离散行为模式的利器。即使在你未来使用行为树、效用理论Utility Theory等更炫酷的AI技术时在底层具体动作的实现上一个精心设计的小FSM依然是你最可靠的伙伴。理解并掌握它不仅能帮你立刻解决眼前角色控制逻辑混乱的问题更能为你理解更复杂的AI系统打下坚实的基础。从今天起试着用状态机的思维去分析游戏里的每一个NPC你会发现它们的“大脑”突然变得清晰可见了。
返回列表