01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南
02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖
03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南
04-玩转 LangChain:从文档加载到高效问答系统构建的全程实战
05-玩转 LangChain:深度评估问答系统的三种高效方法(示例生成、手动评估与LLM辅助评估)
06-从 0 到 1 掌握 LangChain Agents:自定义工具 + LLM 打造智能工作流!
07-【深度解析】从GPT-1到GPT-4:ChatGPT背后的核心原理全揭秘
01-C#与游戏开发的初次见面:从零开始的Unity之旅
02-C#入门:从变量与数据类型开始你的游戏开发之旅
03-C#运算符与表达式:从入门到游戏伤害计算实践
04-从零开始学C#:用if-else和switch打造智能游戏逻辑
05-掌握C#循环:for、while、break与continue详解及游戏案例
06-玩转C#函数:参数、返回值与游戏中的攻击逻辑封装
07-Unity游戏开发入门:用C#控制游戏对象移动
08-C#面向对象编程基础:类的定义、属性与字段详解
09-C#封装与访问修饰符:保护数据安全的利器
10-如何用C#继承提升游戏开发效率?Enemy与Boss案例解析
11-C#多态性入门:从零到游戏开发实战
12-C#接口王者之路:从入门到Unity游戏开发实战 (IAttackable案例详解)
13-C#静态成员揭秘:共享数据与方法的利器
14-Unity 面向对象实战:掌握组件化设计与脚本通信,构建玩家敌人交互
15-C#入门 Day15:彻底搞懂数组!从基础到游戏子弹管理实战
16-C# List 从入门到实战:掌握动态数组,轻松管理游戏敌人列表 (含代码示例)
17-C# 字典 (Dictionary) 完全指南:从入门到游戏属性表实战 (Day 17)
18-C#游戏开发【第18天】 | 深入理解队列(Queue)与栈(Stack):从基础到任务队列实战
19-【C# 进阶】深入理解枚举 Flags 属性:游戏开发中多状态组合的利器
20-C#结构体(Struct)深度解析:轻量数据容器与游戏开发应用 (Day 20)
21-Unity数据持久化进阶:告别硬编码,用ScriptableObject优雅管理游戏配置!(Day 21)
22-Unity C# 健壮性编程:告别崩溃!掌握异常处理与调试的 4 大核心技巧 (Day 22)
23-C#代码解耦利器:委托与事件(Delegate & Event)从入门到实践 (Day 23)
24-Unity脚本通信终极指南:从0到1精通UnityEvent与事件解耦(Day 24)
25-精通C# Lambda与LINQ:Unity数据处理效率提升10倍的秘诀! (Day 25)
26-# Unity C#进阶:掌握泛型编程,告别重复代码,编写优雅复用的通用组件!(Day26)
27-Unity协程从入门到精通:告别卡顿,用Coroutine优雅处理异步与时序任务 (Day 27)
28-搞定玩家控制!Unity输入系统、物理引擎、碰撞检测实战指南 (Day 28)
29-# Unity动画控制核心:Animator状态机与C#脚本实战指南 (Day 29)
30-Unity UI 从零到精通 (第30天): Canvas、布局与C#交互实战 (Day 30)
31-Unity性能优化利器:彻底搞懂对象池技术(附C#实现与源码解析)
32-Unity C#进阶:用状态模式与FSM优雅管理复杂敌人AI,告别Spaghetti Code!(Day32)
欢迎来到《C# for Unity开发者50天掌握》专栏的第32天!在前几周的学习中,我们掌握了C#的基础语法、面向对象编程以及常用的数据结构。今天,我们将深入探讨一个在游戏开发中至关重要的设计模式——状态模式,以及它的常见应用形式——有限状态机(FSM)。
想象一下,游戏中的一个敌人AI需要根据不同情况(如玩家距离、自身血量)做出不同的行为(巡逻、追击、攻击、逃跑)。如果使用大量的if-else
或switch
语句来管理这些行为,代码很快会变得臃肿、难以理解和维护,形成所谓的“意大利面条代码”(Spaghetti Code)。状态模式和FSM正是解决这类问题的利器,它们能帮助我们以结构化、可扩展的方式管理复杂对象的行为。本文将带你从概念到实践,彻底搞懂状态模式与FSM,并动手为敌人AI实现一个简单的状态机。
在进入具体的技术细节之前,我们先来理解引入状态管理的必要性。
随着游戏逻辑复杂度的提升,一个游戏对象(如玩家角色、NPC、敌人)可能拥有多种不同的状态,并且需要在不同状态下表现出不同的行为。
当状态数量增多时,如果仅仅依赖条件判断语句(if-else
或 switch
)来决定当前行为,会导致:
想象一个简单的门,它有“打开”和“关闭”两种状态。我们可以用一个布尔变量isOpen
来管理。但如果这个门还有“正在打开”、“正在关闭”、“已锁定”等状态,并且每个状态下对“开门”、“关门”、“锁门”等操作的响应都不同,简单的条件判断就会迅速膨胀。
为了应对上述挑战,开发者们总结出了**状态模式(State Pattern)和有限状态机(Finite State Machine, FSM)**这两种强大的设计思想。
状态模式允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。它将与特定状态相关的行为局部化,并且将不同状态的行为分割开来。
有限状态机是一种数学计算模型,也是实现状态转换逻辑的一种常用方式。它定义了一组有限的状态、状态之间的转换规则以及触发这些转换的事件。在游戏开发中,FSM是状态模式的一种非常直观和实用的实现方式。
状态模式是GoF(Gang of Four)经典设计模式之一,旨在解决对象状态变化与行为耦合的问题。
状态模式的意图是:允许一个对象在其内部状态改变时改变其行为。对象看起来似乎修改了它的类。
其本质是将每个状态的行为封装到独立的状态类中,并将对象(称为“上下文”)的行为委托给当前的状态对象。当对象的状态改变时,只需改变其引用的状态对象即可。
一个典型的状态模式实现包含以下角色:
HandleInput()
, UpdateState()
等)以及进入/退出该状态的方法(Enter()
, Exit()
)。// 状态接口
public interface IState
{
void EnterState(Context context); // 进入状态时的逻辑
void UpdateState(Context context); // 状态激活时的更新逻辑
void ExitState(Context context); // 退出状态时的逻辑
void HandleInput(Context context, InputType input); // 处理输入或事件
}
// 具体状态A
public class ConcreteStateA : IState
{
public void EnterState(Context context) { /* 初始化状态A */ }
public void UpdateState(Context context) { /* 状态A的持续行为 */ }
public void ExitState(Context context) { /* 清理状态A */ }
public void HandleInput(Context context, InputType input)
{
if (input == InputType.SomeTrigger)
{
// 条件满足,切换到状态B
context.TransitionToState(new ConcreteStateB());
}
}
}
// 具体状态B (类似实现)
public class ConcreteStateB : IState { /* ... */ }
// 上下文类
public class Context
{
private IState _currentState;
public Context(IState initialState)
{
_currentState = initialState;
_currentState.EnterState(this);
}
// 切换状态的方法
public void TransitionToState(IState newState)
{
_currentState.ExitState(this); // 执行旧状态的退出逻辑
_currentState = newState; // 更新当前状态
_currentState.EnterState(this); // 执行新状态的进入逻辑
}
// 将行为委托给当前状态
public void RequestUpdate()
{
_currentState.UpdateState(this);
}
public void ProcessInput(InputType input)
{
_currentState.HandleInput(this, input);
}
}
public enum InputType { Default, SomeTrigger }
有限状态机(FSM)是状态模式在实践中非常常用的一种具体实现模型,尤其在游戏AI、动画控制、流程管理等领域。
FSM可以看作是一个抽象机器,它在任何给定时间只能处于有限个状态中的一个。当接收到特定的**事件(Event)或满足某些条件(Condition)时,它会从当前状态转换(Transition)**到另一个状态。
理解FSM的关键在于掌握它的三个核心组成部分:
代表对象在某个时间点所处的具体情况或行为模式。例如,敌人的状态可以是:
Patrol
(巡逻)Chase
(追击)Attack
(攻击)Idle
(待机)Flee
(逃跑)导致状态发生变化的原因或信号。这些可以是外部输入(如玩家进入视野)、内部条件变化(如生命值低于阈值)、时间流逝等。例如:
PlayerDetected
(探测到玩家)PlayerLost
(丢失玩家目标)ReachedAttackRange
(进入攻击范围)HealthLow
(生命值低)TimerExpired
(计时器到期)定义了从一个状态迁移到另一个状态的规则。通常表示为:(当前状态 + 事件/条件) => 新状态。例如:
Patrol
+ PlayerDetected
) => Chase
Chase
+ ReachedAttackRange
) => Attack
Attack
+ PlayerOutOfRange
) => Chase
Chase
+ PlayerLost
) => Patrol
FSM通常使用**状态图(State Diagram)**进行可视化表示,这极大地增强了理解和沟通效率。状态图用圆圈(或方框)表示状态,用带箭头的线表示转换,线上标注触发转换的事件或条件。
下面是一个简单敌人AI状态机的流程图示例:
(注意:Mermaid图在某些Markdown编辑器或平台(如CSDN)可能需要特定语法或插件支持才能正确渲染)
在Unity C#项目中,我们可以通过多种方式实现FSM。下面介绍两种常见的方法:基于枚举的简单FSM和基于类的FSM。
这种方法适用于状态相对较少、逻辑不复杂的情况。
enum
)来表示所有可能的状态。MonoBehaviour
)中,持有一个该枚举类型的变量,表示当前状态。Update()
方法或一个专门的状态处理方法中,使用switch
语句根据当前状态变量的值来执行相应的行为逻辑和状态转换检查。switch
语句会变得庞大且难以维护。enum
和switch
语句。using UnityEngine;
public class SimpleEnemyAI_Enum : MonoBehaviour
{
// 定义状态枚举
public enum EnemyState { Patrol, Chase, Attack }
// 当前状态
public EnemyState currentState = EnemyState.Patrol;
public float patrolSpeed = 2f;
public float chaseSpeed = 5f;
public float attackRange = 1.5f;
public Transform player; // 假设已赋值
void Update()
{
// 根据当前状态执行行为和检查转换
switch (currentState)
{
case EnemyState.Patrol:
PatrolBehavior();
CheckForPlayer();
break;
case EnemyState.Chase:
ChaseBehavior();
CheckAttackRange();
CheckPlayerLost();
break;
case EnemyState.Attack:
AttackBehavior();
CheckPlayerOutOfRange();
break;
}
}
void PatrolBehavior()
{
// 简单的巡逻逻辑: 在某个区域来回移动 (示例简化)
transform.Translate(Vector3.forward * patrolSpeed * Time.deltaTime);
Debug.Log("Patrolling...");
}
void ChaseBehavior()
{
// 朝玩家移动
if (player != null)
{
Vector3 direction = (player.position - transform.position).normalized;
transform.position += direction * chaseSpeed * Time.deltaTime;
transform.LookAt(player); // 面向玩家
Debug.Log("Chasing Player!");
}
}
void AttackBehavior()
{
// 简单的攻击逻辑 (示例简化)
Debug.Log("Attacking Player!");
// 可能包含攻击动画触发、伤害计算等
}
// --- 状态转换检查 ---
void CheckForPlayer()
{
// 简单的探测逻辑 (示例简化)
if (player != null && Vector3.Distance(transform.position, player.position) < 10f) // 假设探测范围10米
{
Debug.Log("Player Detected! Switching to Chase.");
currentState = EnemyState.Chase;
}
}
void CheckAttackRange()
{
if (player != null && Vector3.Distance(transform.position, player.position) <= attackRange)
{
Debug.Log("Reached Attack Range! Switching to Attack.");
currentState = EnemyState.Attack;
}
}
void CheckPlayerLost()
{
// 简单的丢失逻辑 (示例简化)
if (player == null || Vector3.Distance(transform.position, player.position) > 15f) // 假设丢失距离15米
{
Debug.Log("Player Lost! Switching back to Patrol.");
currentState = EnemyState.Patrol;
// 可能需要返回巡逻点
}
}
void CheckPlayerOutOfRange()
{
if (player == null || Vector3.Distance(transform.position, player.position) > attackRange)
{
Debug.Log("Player out of Attack Range! Switching to Chase.");
currentState = EnemyState.Chase;
}
}
}
这种方法更符合面向对象的设计原则,尤其适合状态较多、逻辑复杂的场景。它实现了状态模式的核心思想。
State
基类(或接口IState
),包含所有状态共有的方法,如Enter()
(进入状态时调用)、Execute()
(状态激活时每帧调用)和Exit()
(退出状态时调用)。PatrolState
, ChaseState
, AttackState
)创建单独的类,继承自State
基类并实现其方法,封装该状态的行为逻辑和转换条件检查。EnemyController
)中,持有一个对当前State
对象的引用。EnemyController
的Update()
方法调用当前State
对象的Execute()
方法。ConcreteState
对象内部逻辑决定,并通过调用EnemyController
提供的状态切换方法来完成。using UnityEngine;
// 状态机控制器
public class EnemyController_Class : MonoBehaviour
{
public State currentState;
public PatrolState patrolState = new PatrolState();
public ChaseState chaseState = new ChaseState();
public AttackState attackState = new AttackState();
// 共享数据 (或通过构造函数传入State)
public Transform player;
public float patrolSpeed = 2f;
public float chaseSpeed = 5f;
public float attackRange = 1.5f;
public float detectionRange = 10f;
public float loseRange = 15f;
public Animator animator; // 可选,用于动画控制
void Start()
{
// 初始化状态
TransitionToState(patrolState);
}
void Update()
{
// 委托给当前状态执行
if (currentState != null)
{
currentState.Execute(this);
}
}
// 状态切换方法
public void TransitionToState(State nextState)
{
if (currentState != null)
{
currentState.Exit(this); // 执行退出逻辑
}
currentState = nextState;
currentState.Enter(this); // 执行进入逻辑
}
}
// --- 状态基类 ---
public abstract class State
{
public abstract void Enter(EnemyController_Class enemy);
public abstract void Execute(EnemyController_Class enemy);
public abstract void Exit(EnemyController_Class enemy);
}
// --- 具体状态类 ---
public class PatrolState : State
{
public override void Enter(EnemyController_Class enemy)
{
Debug.Log("Entering Patrol State");
// enemy.animator?.SetBool("IsPatrolling", true);
// 初始化巡逻路径等
}
public override void Execute(EnemyController_Class enemy)
{
// 执行巡逻逻辑
// transform.Translate(...)
Debug.Log("Patrolling...");
// 检查转换条件
if (enemy.player != null && Vector3.Distance(enemy.transform.position, enemy.player.position) < enemy.detectionRange)
{
enemy.TransitionToState(enemy.chaseState);
}
}
public override void Exit(EnemyController_Class enemy)
{
Debug.Log("Exiting Patrol State");
// enemy.animator?.SetBool("IsPatrolling", false);
}
}
public class ChaseState : State
{
public override void Enter(EnemyController_Class enemy)
{
Debug.Log("Entering Chase State");
// enemy.animator?.SetBool("IsChasing", true);
}
public override void Execute(EnemyController_Class enemy)
{
// 执行追击逻辑
if (enemy.player != null)
{
// Move towards player...
Debug.Log("Chasing Player!");
// 检查转换条件
if (Vector3.Distance(enemy.transform.position, enemy.player.position) <= enemy.attackRange)
{
enemy.TransitionToState(enemy.attackState);
}
else if (Vector3.Distance(enemy.transform.position, enemy.player.position) > enemy.loseRange)
{
enemy.TransitionToState(enemy.patrolState);
}
}
else // 如果玩家对象消失
{
enemy.TransitionToState(enemy.patrolState);
}
}
public override void Exit(EnemyController_Class enemy)
{
Debug.Log("Exiting Chase State");
// enemy.animator?.SetBool("IsChasing", false);
}
}
public class AttackState : State
{
private float attackTimer = 0f;
private float attackCooldown = 1f; // 攻击间隔
public override void Enter(EnemyController_Class enemy)
{
Debug.Log("Entering Attack State");
// enemy.animator?.SetTrigger("Attack");
attackTimer = 0f; // 重置攻击计时器
}
public override void Execute(EnemyController_Class enemy)
{
// 执行攻击逻辑
Debug.Log("Attacking...");
// 可能需要面向玩家
// enemy.transform.LookAt(enemy.player);
attackTimer += Time.deltaTime;
if (attackTimer >= attackCooldown)
{
Debug.Log("Perform Attack Action!");
// enemy.animator?.SetTrigger("Attack"); // 再次触发攻击动画或逻辑
attackTimer = 0f; // 重置计时器
}
// 检查转换条件
if (enemy.player == null || Vector3.Distance(enemy.transform.position, enemy.player.position) > enemy.attackRange)
{
enemy.TransitionToState(enemy.chaseState);
}
}
public override void Exit(EnemyController_Class enemy)
{
Debug.Log("Exiting Attack State");
}
}
Unity自身提供了一个强大的可视化状态机系统——Animator Controller,它主要用于控制动画,但也可以用来管理游戏逻辑状态。那么,何时使用Animator状态机,何时使用我们前面讨论的代码状态机呢?
Animator Controller是Unity内置的一个可视化工具,允许开发者通过拖拽和连接节点来创建状态机。节点代表动画片段(Animation Clip)或子状态机(Sub-State Machine),连线代表过渡(Transition),可以通过参数(Parameters)和条件(Conditions)来控制过渡的触发。
SetFloat
, SetBool
, SetTrigger
)来驱动状态转换,不如直接调用方法直观。指完全通过C#脚本实现的FSM,如前文所述的基于枚举或基于类的实现方式。
选择Animator状态机还是代码状态机,取决于具体需求:
优先选择Animator状态机的情况:
优先选择代码状态机的情况:
混合使用:
IsRunning
参数为true)。总结表格:
特性 | Animator状态机 | 代码状态机 (基于类) |
---|---|---|
可视化 | 强 (内置图形界面) | 弱 (依赖代码/文档) |
动画集成 | 强 (无缝) | 需要手动编码 |
灵活性 | 中等 (受限于Animator结构) | 高 (完全自定义) |
逻辑耦合 | 较高 (与动画系统) | 低 (可与表现层分离) |
可测试性 | 较弱 (依赖引擎) | 较强 (纯C#逻辑) |
实现复杂度 | 中等 (图形操作+少量代码) | 较高 (纯代码实现) |
适用场景 | 动画驱动的状态、美术参与度高 | 复杂AI逻辑、非动画状态、需解耦 |
现在,让我们结合所学,使用基于类的状态模式为敌人AI实现一个简单的状态机,包含巡逻(Patrol)、**追击(Chase)和攻击(Attack)**三种状态。我们将使用上一节的EnemyController_Class
结构。
我们已经有了EnemyController_Class
、State
基类以及PatrolState
、ChaseState
、AttackState
三个具体状态类的框架。接下来需要填充它们的具体逻辑。
(注意:以下代码是基于4.2.3节示例的进一步完善,假设EnemyController_Class
已挂载到敌人游戏对象上,并已在Inspector中设置好player
引用及相关参数。)
using UnityEngine;
public class PatrolState : State
{
private int currentWaypointIndex = 0;
public Transform[] waypoints; // 在EnemyController中定义并赋值
public override void Enter(EnemyController_Class enemy)
{
Debug.Log("Entering Patrol State");
enemy.GetComponent<UnityEngine.AI.NavMeshAgent>().speed = enemy.patrolSpeed; // (可选) 使用NavMeshAgent
// 如果需要路径点, 在 EnemyController 中定义 waypoints 数组
// waypoints = enemy.patrolWaypoints;
// GoToNextWaypoint(enemy);
}
public override void Execute(EnemyController_Class enemy)
{
// 简单的巡逻逻辑: 假设在路点间移动
// if (waypoints != null && waypoints.Length > 0)
// {
// if (Vector3.Distance(enemy.transform.position, waypoints[currentWaypointIndex].position) < 1.0f)
// {
// GoToNextWaypoint(enemy);
// }
// } else {
Debug.Log("Patrolling... (No waypoints defined, implement movement logic)");
// }
// 检查是否探测到玩家
Collider[] hits = Physics.OverlapSphere(enemy.transform.position, enemy.detectionRange, LayerMask.GetMask("Player")); // 假设玩家在Player层
if (hits.Length > 0)
{
enemy.player = hits[0].transform; // 获取玩家引用
Debug.Log("Player detected!");
enemy.TransitionToState(enemy.chaseState);
}
}
// private void GoToNextWaypoint(EnemyController_Class enemy)
// {
// if (waypoints.Length == 0) return;
// enemy.GetComponent().destination = waypoints[currentWaypointIndex].position;
// currentWaypointIndex = (currentWaypointIndex + 1) % waypoints.Length;
// }
public override void Exit(EnemyController_Class enemy)
{
Debug.Log("Exiting Patrol State");
// enemy.GetComponent().ResetPath(); // 停止导航
}
}
UnityEngine.AI.NavMeshAgent
组件或自定义移动逻辑。waypoints
需要在EnemyController_Class
中定义并赋值。探测玩家使用了Physics.OverlapSphere
,请确保玩家对象有正确的Layer。using UnityEngine;
public class ChaseState : State
{
public override void Enter(EnemyController_Class enemy)
{
Debug.Log("Entering Chase State");
enemy.GetComponent<UnityEngine.AI.NavMeshAgent>().speed = enemy.chaseSpeed;
// enemy.animator?.SetBool("IsChasing", true);
}
public override void Execute(EnemyController_Class enemy)
{
if (enemy.player != null)
{
// 使用NavMeshAgent追击玩家
enemy.GetComponent<UnityEngine.AI.NavMeshAgent>().destination = enemy.player.position;
Debug.Log("Chasing Player!");
float distanceToPlayer = Vector3.Distance(enemy.transform.position, enemy.player.position);
// 检查是否进入攻击范围
if (distanceToPlayer <= enemy.attackRange)
{
Debug.Log("Reached attack range!");
enemy.TransitionToState(enemy.attackState);
}
// 检查是否丢失玩家
else if (distanceToPlayer > enemy.loseRange)
{
Debug.Log("Player lost!");
enemy.player = null; // 清除玩家引用
enemy.TransitionToState(enemy.patrolState);
}
// 可选: 添加视线检查 Physics.Linecast
}
else // 如果意外丢失player引用
{
Debug.Log("Player reference lost unexpectedly!");
enemy.TransitionToState(enemy.patrolState);
}
}
public override void Exit(EnemyController_Class enemy)
{
Debug.Log("Exiting Chase State");
// enemy.GetComponent().ResetPath(); // 可选,看Attack状态是否需要移动
// enemy.animator?.SetBool("IsChasing", false);
}
}
using UnityEngine;
public class AttackState : State
{
private float attackTimer = 0f;
private float attackCooldown = 1.5f; // 攻击间隔
public override void Enter(EnemyController_Class enemy)
{
Debug.Log("Entering Attack State");
// 停止移动 (如果使用NavMeshAgent)
enemy.GetComponent<UnityEngine.AI.NavMeshAgent>().isStopped = true;
// enemy.GetComponent().velocity = Vector3.zero; // 确保速度归零
// enemy.transform.LookAt(enemy.player); // 面向玩家
// enemy.animator?.SetTrigger("Attack"); // 触发一次攻击动画
attackTimer = attackCooldown; // 进入时即可攻击一次或等待冷却
}
public override void Execute(EnemyController_Class enemy)
{
if (enemy.player != null)
{
// 保持面向玩家
enemy.transform.LookAt(enemy.player.position);
attackTimer += Time.deltaTime;
if (attackTimer >= attackCooldown)
{
PerformAttack(enemy);
attackTimer = 0f; // 重置计时器
}
// 检查玩家是否移出攻击范围
if (Vector3.Distance(enemy.transform.position, enemy.player.position) > enemy.attackRange)
{
Debug.Log("Player moved out of attack range!");
enemy.TransitionToState(enemy.chaseState);
}
}
else // 玩家消失
{
Debug.Log("Target player lost during attack!");
enemy.TransitionToState(enemy.patrolState); // 或 Chase? 取决于设计
}
}
private void PerformAttack(EnemyController_Class enemy)
{
Debug.Log($"Enemy attacks Player! Timestamp: {Time.time}");
// 在这里实现实际的伤害逻辑, 比如调用玩家身上的受击方法
// enemy.player.GetComponent()?.TakeDamage(10);
// enemy.animator?.SetTrigger("Attack"); // 再次触发攻击动画
}
public override void Exit(EnemyController_Class enemy)
{
Debug.Log("Exiting Attack State");
// 恢复移动 (如果使用NavMeshAgent)
enemy.GetComponent<UnityEngine.AI.NavMeshAgent>().isStopped = false;
// enemy.animator?.ResetTrigger("Attack"); // 重置攻击触发器
}
}
状态切换的逻辑已经包含在每个ConcreteState
的Execute
方法中。当满足特定条件时,它们会调用enemy.TransitionToState()
方法来改变EnemyController_Class
中的currentState
。TransitionToState
方法负责调用旧状态的Exit
和新状态的Enter
。
NavMeshAgent
组件(如果使用导航网格)。EnemyController_Class
脚本添加到敌人对象。EnemyController_Class
的Inspector面板中:
Player
字段(可以在运行时动态查找)。Patrol Speed
, Chase Speed
, Attack Range
, Detection Range
, Lose Range
等参数。EnemyController_Class
中一个public Transform[] waypoints;
字段(需要在EnemyController_Class
和PatrolState
中添加此字段并传递)。EnemyController_Class
中获取Animator
引用,在各状态的Enter/Exit/Execute
中调用animator.Set...
方法。这个实践环节展示了如何使用基于类的状态模式构建一个模块化、可扩展的敌人AI。你可以基于此框架轻松添加更多状态(如Flee、Stunned)或修改现有状态的行为。
今天,我们深入探讨了在Unity C#开发中管理复杂对象行为的利器——状态模式与有限状态机(FSM)。核心要点回顾:
if-else
或switch
管理多状态行为会导致代码难以维护和扩展(意大利面条代码)。Enter
, Execute
, Exit
生命周期方法是常见实践。掌握状态模式与FSM,能显著提升你构建复杂游戏系统(尤其是AI、角色控制器、游戏流程管理)的能力,编写出更健壮、更易于维护和扩展的代码。希望今天的学习能为你打下坚实的基础!