8.4 状态模式 (State) 8.4 状态模式 (State) 状态模式是一种行为型设计模式,它允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。 状态模式将与特定状态相关的行为局部化,并将不同状态之间的转换逻辑明确化,从而使得代码更易于理解和维护。在Unity3D游戏开发中,状态模式尤其有用,它可以帮助我们管理复杂的游戏对象行为,例如角色动画状态机、AI行为逻辑、UI界面状态等等。 8.4.1 状态模式的核心思想 状态模式的核心思想是将对象的行为委托给代表其当前状态的独立状态对象。当对象的状态发生改变时,它会切换到不同的状态对象,从而表现出不同的行为。
状态模式是一种行为型设计模式,它允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。 状态模式将与特定状态相关的行为局部化,并将不同状态之间的转换逻辑明确化,从而使得代码更易于理解和维护。在Unity3D游戏开发中,状态模式尤其有用,它可以帮助我们管理复杂的游戏对象行为,例如角色动画状态机、AI行为逻辑、UI界面状态等等。
状态模式的核心思想是将对象的行为委托给代表其当前状态的独立状态对象。当对象的状态发生改变时,它会切换到不同的状态对象,从而表现出不同的行为。这种模式的关键在于:
状态抽象: 定义一个状态接口或抽象类,声明所有具体状态类需要实现的方法,这些方法代表了对象在不同状态下可以执行的操作。
具体状态: 创建多个具体状态类,每个类都实现了状态接口或抽象类,并且封装了对象在特定状态下的行为逻辑。
上下文: 定义一个上下文类,该类持有当前状态对象的引用,并将客户端的请求委托给当前状态对象处理。上下文类还负责状态的切换。
通过这种方式,状态模式将状态相关的行为和状态转换逻辑从上下文对象中解耦出来,使得代码更加模块化、可扩展和易于维护。
以下是状态模式的经典结构,并结合Unity3D的背景进行解释:
结构角色说明:
Context (上下文): 在Unity3D中,上下文通常是继承自 MonoBehaviour 的游戏对象脚本。它维护一个对当前 State 对象的引用,并将客户端的请求委托给当前的 State 对象处理。上下文还负责状态的切换。
State (状态接口/抽象类): 在Unity3D中,可以是一个C#接口或抽象类。它定义了所有具体状态类必须实现的方法,这些方法代表了对象在不同状态下可以执行的操作。例如,在角色动画状态机中,可能包含 EnterState(), UpdateState(), ExitState() 等方法。
ConcreteStateA, ConcreteStateB (具体状态类): 在Unity3D中,这些是继承自 State 接口或抽象类的具体类。每个具体状态类都实现了 State 中定义的方法,并封装了对象在特定状态下的具体行为逻辑。例如,IdleState 类可能包含角色待机动画的播放逻辑,WalkingState 类可能包含角色行走动画的播放和移动逻辑。
工作原理:
客户端向 Context 对象发出请求 (Request())。
Context 对象将请求委托给当前持有的 State 对象 (currentState.Handle1(), currentState.Handle2())。
具体 State 对象 (例如 ConcreteStateA 或 ConcreteStateB) 根据自身状态执行相应的操作。
在某些情况下,State 对象可能会决定切换 Context 对象的状态,例如根据游戏逻辑或用户输入。状态切换通常通过调用 Context 对象提供的状态切换方法来实现。
在Unity3D游戏开发中,状态模式最常见的应用场景之一就是角色动画状态机。一个角色通常会有多种状态,例如待机 (Idle)、行走 (Walking)、奔跑 (Running)、跳跃 (Jumping)、攻击 (Attack) 等等。每种状态下,角色的动画播放和行为逻辑都不同。使用状态模式可以有效地管理这些复杂的状态转换和行为逻辑。
代码示例:
我们以一个简单的角色状态机为例,实现角色的待机、行走和奔跑三种状态。
1. 定义状态接口 CharacterState.cs:
using UnityEngine; // 状态接口 public interface CharacterState { void EnterState(Character context); // 进入状态时执行 void UpdateState(Character context); // 每帧更新状态时执行 void ExitState(Character context); // 退出状态时执行 }
2. 创建具体状态类:
IdleState.cs (待机状态):using UnityEngine; public class IdleState : CharacterState { public void EnterState(Character context) { Debug.Log("进入待机状态"); context.animator.Play("Idle"); // 播放待机动画 } public void UpdateState(Character context) { // 待机状态下的逻辑,例如检测输入,切换到其他状态 if (Input.GetAxis("Horizontal") != 0 || Input.GetAxis("Vertical") != 0) { context.TransitionToState(context.walkingState); // 切换到行走状态 } if (Input.GetKey(KeyCode.LeftShift)) { context.TransitionToState(context.runningState); // 切换到奔跑状态 } } public void ExitState(Character context) { Debug.Log("退出待机状态"); } }
WalkingState.cs (行走状态):using UnityEngine; public class WalkingState : CharacterState { private float moveSpeed = 2f; // 行走速度 public void EnterState(Character context) { Debug.Log("进入行走状态"); context.animator.Play("Walk"); // 播放行走动画 } public void UpdateState(Character context) { // 行走状态下的逻辑,例如角色移动,检测输入,切换到其他状态 float horizontalInput = Input.GetAxis("Horizontal"); float verticalInput = Input.GetAxis("Vertical"); Vector3 moveDirection = new Vector3(horizontalInput, 0f, verticalInput).normalized; context.transform.Translate(moveDirection * moveSpeed * Time.deltaTime); // 角色移动 if (moveDirection == Vector3.zero) { context.TransitionToState(context.idleState); // 切换到待机状态 } if (Input.GetKey(KeyCode.LeftShift)) { context.TransitionToState(context.runningState); // 切换到奔跑状态 } } public void ExitState(Character context) { Debug.Log("退出行走状态"); } }
RunningState.cs (奔跑状态):using UnityEngine; public class RunningState : CharacterState { private float moveSpeed = 5f; // 奔跑速度 public void EnterState(Character context) { Debug.Log("进入奔跑状态"); context.animator.Play("Run"); // 播放奔跑动画 } public void UpdateState(Character context) { // 奔跑状态下的逻辑,例如角色移动,检测输入,切换到其他状态 float horizontalInput = Input.GetAxis("Horizontal"); float verticalInput = Input.GetAxis("Vertical"); Vector3 moveDirection = new Vector3(horizontalInput, 0f, verticalInput).normalized; context.transform.Translate(moveDirection * moveSpeed * Time.deltaTime); // 角色移动 if (moveDirection == Vector3.zero) { context.TransitionToState(context.idleState); // 切换到待机状态 } if (!Input.GetKey(KeyCode.LeftShift) && moveDirection != Vector3.zero) { context.TransitionToState(context.walkingState); // 切换到行走状态 } } public void ExitState(Character context) { Debug.Log("退出奔跑状态"); } }
3. 创建上下文类 Character.cs (继承自 MonoBehaviour):
using UnityEngine; public class Character : MonoBehaviour { public Animator animator; // 角色动画组件 public CharacterState currentState; // 当前状态 public IdleState idleState = new IdleState(); // 待机状态 public WalkingState walkingState = new WalkingState(); // 行走状态 public RunningState runningState = new RunningState(); // 奔跑状态 void Start() { currentState = idleState; // 初始状态设置为待机状态 currentState.EnterState(this); // 进入初始状态 } void Update() { currentState.UpdateState(this); // 每帧更新当前状态 } // 状态切换方法 public void TransitionToState(CharacterState newState) { currentState.ExitState(this); // 退出当前状态 currentState = newState; // 设置新状态 currentState.EnterState(this); // 进入新状态 } }
4. 在Unity编辑器中配置:
创建一个3D角色对象,并添加 Animator 组件。
创建上述 C# 脚本 (CharacterState.cs, IdleState.cs, WalkingState.cs, RunningState.cs, Character.cs)。
将 Character.cs 脚本添加到角色对象上。
将角色对象的 Animator 组件拖拽到 Character 脚本的 Animator 字段中。
在 Animator 组件中创建相应的动画状态 (Idle, Walk, Run) 和状态转换。确保动画状态名称与代码中 animator.Play() 方法调用的名称一致。
运行效果:
运行游戏后,角色初始状态为待机状态,播放待机动画。按下方向键角色进入行走状态,播放行走动画并移动。按住 Shift 键并按下方向键角色进入奔跑状态,播放奔跑动画并快速移动。松开方向键或Shift键,角色会根据逻辑切换回相应的状态。
Mermaid 类图:
封装状态相关的行为: 状态模式将每个状态的行为都封装在独立的状态类中,使得代码更加模块化,易于理解和维护。
提高代码可读性和可维护性: 状态模式将大量的条件判断语句 (例如 if-else 或 switch) 替换为状态类之间的委托调用,减少了代码的复杂度,提高了代码的可读性和可维护性。
符合开闭原则: 当需要添加新的状态时,只需要创建新的具体状态类并实现状态接口即可,无需修改上下文类和已有的状态类,符合开闭原则。
状态转换逻辑清晰: 状态模式将状态转换逻辑明确化,使得状态之间的切换更加清晰可控。
避免状态蔓延: 在没有状态模式的情况下,状态相关的逻辑可能会散落在上下文类的各个角落,导致状态蔓延,状态模式可以有效地避免这种情况。
增加类的数量: 状态模式会增加类的数量,对于简单的状态场景可能显得过于复杂。
状态切换管理复杂性: 如果状态转换逻辑非常复杂,状态模式可能会使得状态切换的管理变得复杂,需要仔细设计状态转换的机制。
上下文与状态的耦合: 上下文类需要持有所有可能的状态对象,这可能会导致上下文类与状态类之间存在一定的耦合。
除了角色动画状态机,状态模式在Unity3D中还有很多其他的应用场景:
AI行为状态机: 游戏AI通常需要根据不同的游戏情况 (例如敌人距离、自身血量、周围环境等) 做出不同的行为决策。状态模式可以用于管理AI的各种行为状态,例如巡逻、攻击、逃跑、寻找掩体等等。
UI界面状态管理: 复杂的UI界面可能包含多种状态,例如加载中、显示数据、编辑数据、错误提示等等。状态模式可以用于管理UI界面的不同状态和状态转换,使得UI逻辑更加清晰和可维护。
游戏对象状态管理: 游戏中很多对象都有状态的概念,例如门 (打开/关闭/锁定)、开关 (开启/关闭)、宝箱 (关闭/打开/已打开) 等等。状态模式可以用于管理这些游戏对象的状态和行为。
网络状态管理: 在多人游戏中,客户端需要管理网络连接状态,例如连接中、已连接、断开连接、重连中等等。状态模式可以用于管理网络状态和相应的处理逻辑。
状态模式是一种强大的行为型设计模式,在Unity3D游戏开发中有着广泛的应用价值。它可以帮助我们有效地管理复杂的游戏对象行为,提高代码的可读性、可维护性和可扩展性。虽然状态模式会增加类的数量,但对于需要管理复杂状态和行为的场景来说,它带来的优势远大于其缺点。在实际开发中,我们需要根据具体的场景和需求,权衡使用状态模式的利弊,选择最合适的设计方案。
希望本章节对您理解和应用状态模式在Unity3D开发中有所帮助。 通过学习和实践状态模式,您可以构建更加健壮、灵活和易于维护的游戏系统。