Unity游戏开发实战:有限状态机FSM实现NPC 4种行为状态切换
Unity游戏开发实战有限状态机FSM实现NPC 4种行为状态切换在游戏开发中NPC非玩家角色的行为逻辑往往需要处理多种状态之间的切换。想象一下你正在开发一款开放世界RPG游戏游戏中的守卫NPC需要在巡逻、追击入侵者、攻击和逃跑四种行为之间智能切换。如果直接用if-else嵌套来实现代码很快就会变得难以维护。这时候有限状态机FSM就派上用场了。1. 有限状态机基础概念有限状态机Finite State Machine简称FSM是一种数学模型用于表示对象在其生命周期内可能处于的有限状态集合以及在这些状态之间转换的规则。在游戏开发中FSM特别适合用来管理角色行为。FSM的核心要素状态State对象所处的特定行为模式如巡逻、追击转移Transition从一个状态切换到另一个状态的条件动作Action进入、执行或退出状态时执行的操作// FSM基本结构伪代码 public class FSM { private DictionaryStateEnum, StateBase states; private StateBase currentState; public void TransitionTo(StateEnum newState) { currentState?.OnExit(); currentState states[newState]; currentState.OnEnter(); } public void Update() { currentState?.OnExecute(); } }2. Unity中实现FSM的架构设计在Unity中实现FSM我们需要设计一个清晰的架构。以下是推荐的类结构类结构设计StateMachine状态机管理器StateBase所有状态的基类具体状态类如PatrolState、ChaseState等// 状态枚举定义 public enum NPCState { Patrol, Chase, Attack, Flee } // 状态基类 public abstract class NPCStateBase { protected NPCController controller; protected NPCState stateType; public NPCStateBase(NPCController controller) { this.controller controller; } public abstract void OnEnter(); public abstract void OnUpdate(); public abstract void OnExit(); public NPCState StateType stateType; }3. NPC四种行为状态实现3.1 巡逻状态实现巡逻是NPC的基础状态通常表现为在特定路径或区域内移动。public class PatrolState : NPCStateBase { private Transform[] waypoints; private int currentWaypointIndex 0; private float waitTimer 0f; public PatrolState(NPCController controller) : base(controller) { stateType NPCState.Patrol; waypoints controller.PatrolWaypoints; } public override void OnEnter() { controller.NavAgent.speed controller.PatrolSpeed; MoveToNextWaypoint(); } public override void OnUpdate() { if (controller.NavAgent.remainingDistance controller.NavAgent.stoppingDistance) { waitTimer Time.deltaTime; if (waitTimer controller.WaypointWaitTime) { waitTimer 0f; MoveToNextWaypoint(); } } // 检查是否发现玩家 if (controller.CanSeePlayer()) { controller.TransitionToState(NPCState.Chase); } } private void MoveToNextWaypoint() { if (waypoints.Length 0) return; currentWaypointIndex (currentWaypointIndex 1) % waypoints.Length; controller.NavAgent.SetDestination(waypoints[currentWaypointIndex].position); } public override void OnExit() { // 清理工作 } }3.2 追击状态实现当NPC发现玩家时从巡逻状态切换到追击状态。public class ChaseState : NPCStateBase { private Transform playerTransform; private float lastSeenTime; public ChaseState(NPCController controller) : base(controller) { stateType NPCState.Chase; playerTransform GameObject.FindGameObjectWithTag(Player).transform; } public override void OnEnter() { controller.NavAgent.speed controller.ChaseSpeed; controller.NavAgent.stoppingDistance controller.AttackRange * 0.9f; lastSeenTime Time.time; } public override void OnUpdate() { if (controller.CanSeePlayer()) { lastSeenTime Time.time; controller.NavAgent.SetDestination(playerTransform.position); // 检查是否进入攻击范围 float distanceToPlayer Vector3.Distance( controller.transform.position, playerTransform.position); if (distanceToPlayer controller.AttackRange) { controller.TransitionToState(NPCState.Attack); } } else if (Time.time - lastSeenTime controller.ChaseDuration) { // 丢失玩家视线超过一定时间返回巡逻 controller.TransitionToState(NPCState.Patrol); } // 检查是否需要逃跑如生命值过低 if (controller.HealthPercent 0.3f) { controller.TransitionToState(NPCState.Flee); } } public override void OnExit() { // 可能播放停止追击的动画 } }3.3 攻击状态实现当NPC接近玩家到攻击范围时切换到攻击状态。public class AttackState : NPCStateBase { private Transform playerTransform; private float attackCooldown; public AttackState(NPCController controller) : base(controller) { stateType NPCState.Attack; playerTransform GameObject.FindGameObjectWithTag(Player).transform; } public override void OnEnter() { controller.NavAgent.isStopped true; attackCooldown 0f; } public override void OnUpdate() { // 面向玩家 Vector3 direction (playerTransform.position - controller.transform.position).normalized; Quaternion lookRotation Quaternion.LookRotation(new Vector3(direction.x, 0, direction.z)); controller.transform.rotation Quaternion.Slerp( controller.transform.rotation, lookRotation, Time.deltaTime * controller.RotationSpeed); // 攻击逻辑 attackCooldown - Time.deltaTime; if (attackCooldown 0f) { PerformAttack(); attackCooldown controller.AttackInterval; } // 检查玩家是否超出攻击范围 float distance Vector3.Distance( controller.transform.position, playerTransform.position); if (distance controller.AttackRange * 1.2f) { controller.NavAgent.isStopped false; controller.TransitionToState(NPCState.Chase); } // 检查是否需要逃跑 if (controller.HealthPercent 0.3f) { controller.TransitionToState(NPCState.Flee); } } private void PerformAttack() { // 播放攻击动画 controller.Animator.SetTrigger(Attack); // 实际伤害计算逻辑 if (Vector3.Distance(controller.transform.position, playerTransform.position) controller.AttackRange) { // 对玩家造成伤害 playerTransform.GetComponentPlayerHealth().TakeDamage(controller.AttackDamage); } } public override void OnExit() { controller.NavAgent.isStopped false; } }3.4 逃跑状态实现当NPC生命值过低时会切换到逃跑状态寻找安全位置。public class FleeState : NPCStateBase { private Transform safePoint; private float fleeTimer; public FleeState(NPCController controller) : base(controller) { stateType NPCState.Flee; safePoint GameObject.FindGameObjectWithTag(SafePoint).transform; } public override void OnEnter() { controller.NavAgent.speed controller.FleeSpeed; controller.NavAgent.SetDestination(safePoint.position); fleeTimer 0f; } public override void OnUpdate() { fleeTimer Time.deltaTime; // 到达安全点或逃跑时间过长 if (controller.NavAgent.remainingDistance controller.NavAgent.stoppingDistance || fleeTimer controller.MaxFleeTime) { // 生命值恢复后返回巡逻 if (controller.HealthPercent 0.6f) { controller.TransitionToState(NPCState.Patrol); } } } public override void OnExit() { // 可能播放恢复正常状态的动画 } }4. 状态机管理与集成4.1 状态机控制器实现状态机控制器负责管理所有状态和状态转换。public class NPCController : MonoBehaviour { [Header(Movement Settings)] public float PatrolSpeed 3.5f; public float ChaseSpeed 5f; public float FleeSpeed 6f; public float RotationSpeed 5f; [Header(Combat Settings)] public float AttackRange 2f; public float AttackDamage 10f; public float AttackInterval 2f; [Header(AI Settings)] public float ChaseDuration 5f; public float MaxFleeTime 10f; public float WaypointWaitTime 2f; public Transform[] PatrolWaypoints; [Header(Health)] public float HealthPercent currentHealth / maxHealth; private float maxHealth 100f; private float currentHealth 100f; // 组件引用 private NavMeshAgent navAgent; private Animator animator; // 状态机相关 private DictionaryNPCState, NPCStateBase states; private NPCStateBase currentState; private void Awake() { navAgent GetComponentNavMeshAgent(); animator GetComponentAnimator(); // 初始化状态 states new DictionaryNPCState, NPCStateBase { { NPCState.Patrol, new PatrolState(this) }, { NPCState.Chase, new ChaseState(this) }, { NPCState.Attack, new AttackState(this) }, { NPCState.Flee, new FleeState(this) } }; } private void Start() { TransitionToState(NPCState.Patrol); } private void Update() { currentState?.OnUpdate(); } public void TransitionToState(NPCState newState) { if (states.TryGetValue(newState, out var state)) { currentState?.OnExit(); currentState state; currentState.OnEnter(); } } public bool CanSeePlayer() { // 实现玩家检测逻辑 // 包括视线检测、距离检测等 return false; // 简化示例 } // 其他辅助方法和属性... }4.2 状态转换条件优化为了使状态转换更加灵活我们可以将转换条件抽象出来// 状态转换条件类 [System.Serializable] public class StateTransition { public NPCState FromState; public NPCState ToState; public System.Funcbool Condition; public int Priority; } // 在NPCController中添加 private ListStateTransition transitions; private void InitializeTransitions() { transitions new ListStateTransition { new StateTransition { FromState NPCState.Patrol, ToState NPCState.Chase, Condition () CanSeePlayer(), Priority 10 }, new StateTransition { FromState NPCState.Chase, ToState NPCState.Attack, Condition () { var player GameObject.FindGameObjectWithTag(Player); return player ! null Vector3.Distance(transform.position, player.transform.position) AttackRange; }, Priority 20 }, // 其他转换条件... }; } private void CheckTransitions() { var currentStateType currentState?.StateType ?? NPCState.Patrol; var applicableTransitions transitions .Where(t t.FromState currentStateType t.Condition()) .OrderByDescending(t t.Priority); foreach (var transition in applicableTransitions) { TransitionToState(transition.ToState); break; // 只执行最高优先级的转换 } }5. 高级技巧与优化5.1 使用ScriptableObject管理状态配置为了更方便地调整NPC行为参数可以使用ScriptableObject[CreateAssetMenu(menuName AI/NPC Config)] public class NPCConfig : ScriptableObject { [Header(Movement)] public float PatrolSpeed 3.5f; public float ChaseSpeed 5f; public float FleeSpeed 6f; [Header(Combat)] public float AttackRange 2f; public float AttackDamage 10f; public float AttackInterval 2f; [Header(AI Behavior)] public float ChaseDuration 5f; public float MaxFleeTime 10f; public float WaypointWaitTime 2f; } // 在NPCController中引用 public class NPCController : MonoBehaviour { public NPCConfig config; // 使用config中的值替代硬编码 public float PatrolSpeed config.PatrolSpeed; // 其他属性... }5.2 动画状态机与行为状态机同步确保动画状态与行为状态同步// 在每个状态的OnEnter中添加动画触发 public override void OnEnter() { controller.Animator.SetInteger(State, (int)stateType); // 其他初始化... } // 在Animator Controller中设置对应的状态参数5.3 性能优化建议减少每帧计算将一些昂贵的计算如物理检测分散到多帧执行对象池管理对于大量NPC使用对象池管理状态机实例事件驱动使用事件系统减少状态机轮询// 示例事件驱动的状态转换 public class NPCController : MonoBehaviour { // ... private void OnEnable() { HealthSystem.OnLowHealth HandleLowHealth; } private void OnDisable() { HealthSystem.OnLowHealth - HandleLowHealth; } private void HandleLowHealth() { if (currentState.StateType ! NPCState.Flee) { TransitionToState(NPCState.Flee); } } }通过以上实现我们建立了一个灵活、可维护的NPC行为系统。这种基于有限状态机的设计不仅适用于NPC AI也可以应用于游戏中的各种状态管理场景如UI流程、游戏状态管理等。