在有限状态机(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);
    }
}


至此,最小的有限状态机系统实现 ^-^,谢谢阅读.

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐