前言
本文为 Udemy 课程 The Ultimate Guide to Creating an RPG Game in Unity 学习笔记。
前面开发模式的问题
之前的开发中,我们的代码都集中在 Player2
这个文件下,这种开发模式存在以下缺点:
- 代码在同一个文件中,代码量大后非常难以维护。
- 角色的行为逻辑直接在对应的方法中维护,后续新增功能会频繁修改这些方法,容易导致大量 Bug。
例如:
一开始实现的移动代码
csharp
private void Movement() {
rb.velocity = new Vector2(xInput * moveSpeed, rb.velocity.y);
}
加入冲刺后
csharp
private void Movement() {
xInput = Input.GetAxisRaw("Horizontal");
if (dashTime > 0) {
rb.velocity = new Vector2(facingDir * dashSpeed, 0);
} else {
rb.velocity = new Vector2(xInput * moveSpeed, rb.velocity.y);
}
}
再加入攻击后
csharp
private void Movement() {
xInput = Input.GetAxisRaw("Horizontal");
if (isAttacking) {
rb.velocity = new Vector2(0, 0);
} else if (dashTime > 0) {
rb.velocity = new Vector2(facingDir * dashSpeed, 0);
} else {
rb.velocity = new Vector2(xInput * moveSpeed, rb.velocity.y);
}
}
每次新增功能,都需要修改 Movement()
方法,导致代码越来越臃肿,维护起来非常困难。
使用状态模式解决
为了避免上述问题,可以使用**状态模式(状态机)**来实现。
什么是状态模式(State Pattern)?
状态模式是一种面向对象的设计模式,其核心思想是:
当对象的内部状态改变时,其行为也随之改变。
通过将对象的行为和状态分离,每个状态封装为独立的类,从而实现状态切换的清晰性和易扩展性。
状态模式的结构
状态模式通常包括以下几个关键部分:
1. 上下文(Context)
- 表示当前工作的对象,包含当前状态的引用。
- 通过调用当前状态的方法完成具体的行为。
2. 状态接口(State)
- 定义接口或抽象类,用于表示所有可能的状态。
- 每个状态都实现该接口。
3. 具体状态(Concrete States)
- 实现状态接口的类,每个类表示一种特定状态并定义该状态的具体行为。
4. 状态切换
- 状态切换通过上下文类完成,可以由上下文主动切换,也可以由状态自身决定何时切换。
状态模式的示例代码
以下是使用状态模式模拟角色行为(站立、行走、跑步)的代码示例:
1. 定义状态接口
csharp
public interface IState {
void EnterState(Player player);
void UpdateState(Player player);
}
2. 定义具体状态
csharp
public class IdleState : IState {
public void EnterState(Player player) {
Debug.Log("进入站立状态");
}
public void UpdateState(Player player) {
if (player.Speed > 0) {
player.SetState(new WalkState());
}
}
}
public class WalkState : IState {
public void EnterState(Player player) {
Debug.Log("进入行走状态");
}
public void UpdateState(Player player) {
if (player.Speed == 0) {
player.SetState(new IdleState());
} else if (player.Speed > 5) {
player.SetState(new RunState());
}
}
}
public class RunState : IState {
public void EnterState(Player player) {
Debug.Log("进入跑步状态");
}
public void UpdateState(Player player) {
if (player.Speed <= 5) {
player.SetState(new WalkState());
}
}
}
3. 上下文类
csharp
public class Player {
public float Speed { get; set; }
private IState currentState;
public Player() {
currentState = new IdleState();
currentState.EnterState(this);
}
public void SetState(IState newState) {
currentState = newState;
currentState.EnterState(this);
}
public void Update() {
currentState.UpdateState(this);
}
}
运行流程
Player
是上下文类,包含当前状态的引用currentState
。IdleState
、WalkState
、RunState
是具体状态,实现了IState
接口。- 根据
Speed
值切换状态:Speed == 0
:切换到IdleState
。0 < Speed <= 5
:切换到WalkState
。Speed > 5
:切换到RunState
。
Unity 中的状态机
Unity Animator 中的状态机
Animator 状态机 是 Unity 提供的用于动画控制的可视化工具。其核心包括:
-
状态(State)
- 每个状态代表一种动画或行为。
- 例如:
- Idle:站立状态。
- Walk:行走状态。
- Run:跑步状态。
-
过渡(Transition)
- 状态之间通过过渡连接,过渡规则决定状态切换条件。
-
参数(Parameters)
- 用于控制状态切换的输入值,如:
- Float(如
speed
)。 - Bool(如
isJumping
)。 - Trigger(如
jumpTrigger
)。
- Float(如
- 用于控制状态切换的输入值,如:
Animator 的使用步骤
-
创建 Animator Controller
- 在 Unity 中右键 ->
Create -> Animator Controller
。 - 将 Animator Controller 添加到角色的
Animator
组件中。
- 在 Unity 中右键 ->
-
创建动画状态
- 双击 Animator Controller 打开 Animator 窗口。
- 右键 ->
Create State
-> 添加动画状态,将动画片段拖入状态。
-
设置参数
- 在 Animator 的
Parameters
面板中,添加所需的参数(Float、Bool、Trigger 等)。
- 在 Animator 的
-
添加过渡
- 在状态之间右键 ->
Make Transition
,设置条件。
- 在状态之间右键 ->
-
通过代码控制
csharpAnimator animator; void Start() { animator = GetComponent<Animator>(); } void Update() { animator.SetFloat("speed", Input.GetAxis("Vertical")); if (Input.GetKeyDown(KeyCode.Space)) { animator.SetBool("isJumping", true); } if (Input.GetMouseButtonDown(0)) { animator.SetTrigger("attackTrigger"); } }
状态模式与Unity中的状态机的关系
Unity中的状态机(如Animator状态机)并非严格的状态模式实现,但它是状态模式的一种具体应用。以下是两者的异同点:
特性 | 状态模式(State Pattern) | Unity Animator 状态机 |
---|---|---|
状态实现 | 通过代码实现,每个状态是一个独立的类 | 通过可视化界面实现,每个状态对应一个动画片段 |
状态切换 | 显式调用代码切换状态 | 基于参数和条件,Animator自动完成状态切换 |
扩展性 | 添加新状态需要定义新的类,代码管理灵活 | 新状态需要通过界面添加,逻辑复杂时可能难以维护 |
复杂逻辑 | 适合处理复杂的状态切换和行为定义 | 主要用于控制动画状态,逻辑层次较浅 |
行为与状态绑定 | 每个状态都可以实现独特的行为 | 状态通常只是动画播放,不直接控制行为 |