游戏设计模式之有限状态机(FSM)
在有限状态机(FSM)的设计中,核心由4 个关键部分组成,它们分工明确且协同工作。同时,有限状态机与玩家对象(或其他被管理的实体)通过 “数据交互” 和 “行为委托” 实现协作
一、有限状态机的核心组成及职责
有限状态机(FSM)的核心目的是 “管理实体在不同状态下的行为,并处理状态间的切换”,其组成部分及职责如下:
1. 状态(State)- 自包含
-所谓自包含,简单理解就是每个状态类有自己的行为逻辑,是一个完整的个体
- 职责:定义 “进入状态”“状态中更新”“退出状态” 的具体逻辑,是行为的最小执行单元。
- 每个状态对应实体的一种行为模式(如玩家的 “站立”“移动”“跳跃”“攻击”),必须实现统一接口(如`IState`),包含以下核心方法:
- `Enter()`:进入状态时的初始化逻辑(如播放攻击动画、重置跳跃计时器)。
- `Update()`:每帧执行的状态逻辑(如移动时的位置更新、攻击时的伤害检测)。
- `Exit()`:退出状态时的清理逻辑(如停止攻击动画、保存当前状态数据)。
2. 状态机管理器(StateMachine) - 统一管理所有状态
- 职责:作为 “状态的管理者”,负责维护当前状态、处理状态切换、协调所有状态的生命周期。
- 核心功能:
- 持有所有可能的状态实例(如`idleState`、`moveState`),并记录当前激活的状态。
- 提供`ChangeState(IState newState)`方法,实现状态切换(先调用当前状态的`Exit()`,再调用新状态的`Enter()`,最后更新当前状态)。
- 每帧调用当前状态的`Update()`方法,驱动状态逻辑执行。
- 接收外部输入(如玩家操作、碰撞检测结果),判断是否需要触发状态转换。
3. 转换条件(Transition)
- 职责:定义 “从 A 状态切换到 B 状态” 的规则(即 “何时切换”),是状态切换的触发器。
- 表现形式:通常是一组判断条件(如 “玩家按下跳跃键且在地面上”→ 从 “站立” 切换到 “跳跃”),可以是硬编码逻辑,也可以封装成独立的条件类(如`JumpCondition`)。
- 由谁判断:可能由状态机管理器统一判断(适合简单场景),也可能由当前状态在`Update()`中自行判断(适合状态内专属条件)。
4.上下文(Context)- 实体对象
- 职责:即 “被状态机管理的实体”(如玩家、敌人),提供状态执行所需的数据和行为接口。
- 包含内容:实体的属性(如位置、生命值、是否在地面上)和方法(如`Move()`、`Jump()`、`PlayAnimation()`)。
- 作用:状态的逻辑需要依赖上下文的数据(如 “移动状态” 需要玩家的输入方向),并通过调用上下文的方法实现具体行为(如调用`player.Move(direction)`)。
二、各部分完整代码实现
1. 状态接口(IState)
using UnityEngine;
// 所有状态必须实现的接口(状态机的"行为规范")
public interface IPlayerState {
// 进入状态时调用(初始化)
void Enter(Player player);
// 每帧更新逻辑
void Update(Player player);
// 退出状态时调用(清理)
void Exit(Player player);
}
2. 玩家类(Player)—— 上下文(Context),提供数据和行为接口
using UnityEngine;
// 玩家对象(被FSM管理的实体,提供状态所需的数据和方法)
public class Player : MonoBehaviour {
[Header("组件引用")]
public Rigidbody2D rb; // 用于物理移动
public Animator anim; // 用于播放动画
[Header("移动参数")]
public float moveSpeed = 5f;
public float jumpForce = 7f;
[Header("状态数据(供FSM判断)")]
public Vector2 inputDirection; // 输入方向(由Input系统更新)
public bool isGrounded; // 是否在地面(由检测更新)
public bool isJumping; // 是否在跳跃中
private PlayerFSM fsm; // 玩家的状态机
void Awake() {
fsm = GetComponent<PlayerFSM>();
rb = GetComponent<Rigidbody2D>();
anim = GetComponent<Animator>();
}
void Update() {
// 1. 更新输入(供状态判断)
inputDirection = new Vector2(Input.GetAxisRaw("Horizontal"), 0);
// 2. 检测是否在地面(简化:检测与地面的碰撞)
isGrounded = Physics2D.OverlapCircle(transform.position - new Vector3(0, 0.6f, 0), 0.2f, LayerMask.GetMask("Ground"));
anim.SetBool("IsGrounded", isGrounded);
}
// 玩家的移动方法(供状态调用)
public void Move(Vector2 direction) {
if (direction.magnitude > 0) {
// 移动位置
rb.velocity = new Vector2(direction.x * moveSpeed, rb.velocity.y);
// 翻转朝向
transform.localScale = new Vector3(direction.x > 0 ? 1 : -1, 1, 1);
} else {
// 停止移动
rb.velocity = new Vector2(0, rb.velocity.y);
}
}
// 玩家的跳跃方法(供状态调用)
public void Jump() {
if (isGrounded) {
rb.velocity = new Vector2(rb.velocity.x, jumpForce);
isJumping = true;
anim.SetTrigger("Jump");
}
}
}
3. 具体状态类(实现 IPlayerState)
(1)站立状态(StandState)
// 站立状态:玩家静止时的逻辑
public class StandState : IPlayerState {
public void Enter(Player player) {
// 进入站立:播放站立动画
player.anim.SetBool("IsMoving", false);
}
public void Update(Player player) {
// 检测移动输入:有输入则切换到移动状态
if (player.inputDirection.magnitude > 0) {
player.fsm.ChangeState(PlayerStateType.Move);
}
// 检测跳跃输入:按空格则切换到跳跃状态
if (Input.GetKeyDown(KeyCode.Space)) {
player.fsm.ChangeState(PlayerStateType.Jump);
}
}
public void Exit(Player player) {
// 退出站立:无需额外操作
}
}
(2)移动状态(MoveState)
// 移动状态:玩家行走时的逻辑
public class MoveState : IPlayerState {
public void Enter(Player player) {
// 进入移动:播放移动动画
player.anim.SetBool("IsMoving", true);
}
public void Update(Player player) {
// 执行移动(调用玩家的Move方法)
player.Move(player.inputDirection);
// 检测停止移动:输入为0则切换到站立状态
if (player.inputDirection.magnitude == 0) {
player.fsm.ChangeState(PlayerStateType.Stand);
}
// 检测跳跃输入:按空格则切换到跳跃状态
if (Input.GetKeyDown(KeyCode.Space)) {
player.fsm.ChangeState(PlayerStateType.Jump);
}
}
public void Exit(Player player) {
// 退出移动:无需额外操作(动画在Enter中切换)
}
}
(3)跳跃状态(JumpState)
// 跳跃状态:玩家跳跃时的逻辑
public class JumpState : IPlayerState {
public void Enter(Player player) {
// 进入跳跃:执行跳跃逻辑(调用玩家的Jump方法)
player.Jump();
}
public void Update(Player player) {
// 在空中时,保持移动方向(可选逻辑)
if (!player.isGrounded) {
player.Move(player.inputDirection); // 空中可以微调方向
}
// 检测落地:落地后切换回站立状态
if (player.isGrounded && player.isJumping) {
player.isJumping = false; // 重置跳跃标记
player.fsm.ChangeState(PlayerStateType.Stand);
}
}
public void Exit(Player player) {
// 退出跳跃:无需额外操作
}
}
4. 状态机管理器(PlayerFSM)—— 管理状态切换
using UnityEngine;
// 玩家状态类型(枚举)
public enum PlayerStateType {
Stand, // 站立
Move, // 移动
Jump // 跳跃
}
// 玩家状态机(管理所有状态和切换)
public class PlayerFSM : MonoBehaviour {
// 所有状态
public StandState standState = new StandState();
public MoveState moveState = new MoveState();
public JumpState jumpState = new JumpState();
// 当前状态
public IPlayerState currentState;
//玩家实例
private Player player;
void Awake() {
player = GetComponent<Player>();
}
void Start() {
// 初始状态:站立
ChangeState(PlayerStateType.Stand);
}
void Update() {
// 每帧更新当前状态的逻辑
currentState?.Update(player);
}
// 切换状态的核心方法
public void ChangeState(PlayerStateType stateType) {
// 根据 stateType 类型获取对应状态赋值给 newState
//整个 switch { ... } 块会计算出一个值,赋值给左边的 newState。
IPlayerState newState = stateType switch {
//格式 “匹配条件” => “返回值”:
//如果 stateType 的值等于 PlayerStateType.Stand,就返回 standState 实例
PlayerStateType.Stand => standState,
PlayerStateType.Move => moveState,
PlayerStateType.Jump => jumpState,
//这里相当于传统 switch 中的 default
_ => null
};
if (newState == null) return;
// 退出当前状态
currentState?.Exit(player);
// 切换到新状态
currentState = newState;
// 进入新状态
currentState.Enter(player);
}
}
至此,最小的有限状态机系统实现 ^-^,谢谢阅读.
更多推荐
所有评论(0)