延边朝鲜族自治州网站建设_网站建设公司_导航菜单_seo优化
2025/12/22 23:17:42 网站建设 项目流程

适合中小型项目


🎮 从零实现一个轻量级 FSM 状态机:让 Unity 角色行为更清晰

在游戏开发中,角色往往需要在“待机”、“奔跑”、“跳跃”、“攻击”等状态间切换。如果用大量 if-elseswitch 来管理,代码很快会变得混乱难维护。
本文将带你基于接口与泛型,实现一个轻量、类型安全、易于扩展的有限状态机(FSM)系统,并完整演示如何用它控制 Unity 玩家的行为与动画。


🔧 一、为什么需要 FSM?

想象一个简单的玩家逻辑:

void Update()
{if (isIdle) { /* 待机逻辑 */ }else if (isRunning) { /* 奔跑逻辑 */ }else if (isJumping) { /* 跳跃逻辑 */ }if (Input.GetButtonDown("Jump") && isGrounded && !isJumping)StartJump();
}

问题很明显:

  • 逻辑耦合:所有状态挤在一个脚本里;
  • 难以扩展:加个“滑铲”状态?代码更乱;
  • 生命周期模糊:何时初始化?何时清理?

FSM(Finite State Machine) 的核心思想是:

每个状态是一个独立对象,只关心自己的进入、退出和更新逻辑。状态切换由统一的状态机调度。


📦 二、FSM 核心组件设计

我们只需要两个核心文件,就能搭建一个通用 FSM 框架。

1. 状态接口:IFSMState.cs

public interface IFSMState
{void Enter();          // 进入状态(一次)void Exit();           // 离开状态(一次)void LogicalUpdate();  // 每帧更新
}

✅ 定义了所有状态必须遵守的“合同”。

2. 通用状态机:FSM.cs

using System.Collections.Generic;/// <summary>
/// 有限状态机(FSM)泛型实现类
/// 用于管理状态的切换、更新和状态间的流转
/// </summary>
/// <typeparam name="T">状态类型,必须实现IFSMState接口</typeparam>
public class FSM<T> where T : IFSMState
{/// <summary>/// 状态注册表,存储所有已注册的状态实例/// Key:状态类型(Type),Value:状态实例(T)/// </summary>public Dictionary<System.Type, T> StateTable { get; protected set; }/// <summary>/// 上一个激活的状态(只读)/// 用于状态回退等场景/// </summary>public T PrevState { get; protected set; }/// <summary>/// 当前激活的状态(受保护,仅内部或子类可直接修改)/// </summary>protected T curState;/// <summary>/// 有限状态机构造函数/// 初始化状态表和状态变量/// </summary>public FSM(){// 初始化状态字典StateTable = new Dictionary<System.Type, T>();// 初始化当前状态和上一状态为默认值(null)curState = PrevState = default;}/// <summary>/// 向状态机注册状态/// </summary>/// <param name="state">要注册的状态实例</param>public void AddState(T state){// 以状态类型为键,将状态实例存入注册表
        StateTable.Add(state.GetType(), state);}/// <summary>/// 启动状态机并进入指定初始状态(直接传入状态实例)/// </summary>/// <param name="startState">初始状态实例</param>public void SwitchOn(T startState){// 设置当前状态为初始状态curState = startState;// 执行状态进入逻辑
        curState.Enter();}/// <summary>/// 启动状态机并进入指定初始状态(通过状态类型)/// 需确保该状态已通过AddState注册/// </summary>/// <param name="startState">初始状态的Type类型</param>public void SwitchOn(System.Type startState){// 从状态表中获取对应类型的状态实例curState = StateTable[startState];// 执行状态进入逻辑
        curState.Enter();}/// <summary>/// 切换到指定的下一个状态(直接传入状态实例)/// 会先执行当前状态的退出逻辑,再执行新状态的进入逻辑/// </summary>/// <param name="nextState">要切换到的目标状态实例</param>public void ChangeState(T nextState){// 记录当前状态为上一状态PrevState = curState;// 执行当前状态的退出逻辑
        curState.Exit();// 更新当前状态为目标状态curState = nextState;// 执行目标状态的进入逻辑
        curState.Enter();}/// <summary>/// 切换到指定的下一个状态(通过状态类型)/// 需确保该状态已通过AddState注册/// </summary>/// <param name="nextState">要切换到的目标状态Type类型</param>public void ChangeState(System.Type nextState){// 记录当前状态为上一状态PrevState = curState;// 执行当前状态的退出逻辑
        curState.Exit();// 从状态表中获取目标状态实例并更新当前状态curState = StateTable[nextState];// 执行目标状态的进入逻辑
        curState.Enter();}/// <summary>/// 回退到上一个状态/// 若上一状态为null(无历史状态),则不执行任何操作/// </summary>public void RevertToPrevState(){// 检查上一状态是否有效if (PrevState != null){// 切换回上一状态
            ChangeState(PrevState);}}/// <summary>/// 状态机逻辑更新方法/// 需在每一帧调用,执行当前状态的逻辑更新/// </summary>public void OnUpdate(){// 执行当前状态的逻辑更新
        curState.LogicalUpdate();}
}

 

✅ 泛型设计确保类型安全;自动管理状态生命周期。


🎮 三、实战:用 FSM 控制 Unity 玩家

步骤 1:创建玩家状态基类

// PlayerState.cs
public abstract class PlayerState : IFSMState
{protected PlayerController player;public PlayerState(PlayerController p) => player = p;public abstract void Enter();public abstract void Exit();public abstract void LogicalUpdate();
}

步骤 2:实现具体状态

待机状态

public class IdleState : PlayerState
{public IdleState(PlayerController p) : base(p) { }public override void Enter(){player.animator.Play("Idle"); // 👈 动画在这里切换!}public override void LogicalUpdate(){if (Mathf.Abs(player.inputHorizontal) > 0.1f)player.fsm.ChangeState(new RunState(player));}public override void Exit() { }
}

奔跑状态

public class RunState : PlayerState
{public RunState(PlayerController p) : base(p) { }public override void Enter(){player.animator.Play("Run");}public override void LogicalUpdate(){player.transform.Translate(Vector3.right * player.moveSpeed * Time.deltaTime * player.inputHorizontal);if (Mathf.Abs(player.inputHorizontal) <= 0.1f)player.fsm.ChangeState(new IdleState(player));}public override void Exit() { }
}

💡 关键点:动画切换写在 Enter() 中,确保每次进入状态时播放正确动画。

步骤 3:特化玩家状态机

// PlayerFSM.cs
public class PlayerFSM : FSM<PlayerState>
{public PlayerState CurState => curState; // 安全暴露当前状态
}

步骤 4:挂载到 Unity GameObject

// PlayerController.cs
public class PlayerController : MonoBehaviour
{public Animator animator;public float moveSpeed = 5f;public float inputHorizontal { get; private set; }public PlayerFSM fsm;void Start(){fsm = new PlayerFSM();// 创建并注册状态(每个状态只实例化一次!)var idle = new IdleState(this);var run = new RunState(this);fsm.AddState(idle);fsm.AddState(run);fsm.SwitchOn(idle); // 启动状态机}void Update(){inputHorizontal = Input.GetAxisRaw("Horizontal");fsm.OnUpdate(); // 驱动状态更新}
}

🔗 四、各脚本关系图

PlayerController (MonoBehaviour)│└── 拥有 → PlayerFSM : FSM<PlayerState>│├── 管理 → IdleState : PlayerState└── 管理 → RunState  : PlayerState│└── 通过 player 引用操作 Animator/Transform
  • 状态机不包含逻辑,只负责调度;
  • 状态类不控制切换,只负责行为;
  • 动画、物理、输入全部由状态类通过 PlayerController 访问。

✅ 五、优势总结

特性说明
高内聚低耦合 每个状态逻辑独立,修改互不影响
类型安全 泛型约束防止状态混用(玩家状态不会误加到敌人机)
易于扩展 新增状态只需继承 PlayerState 并注册
生命周期清晰 Enter/Exit/Update 分离,避免资源泄漏
表现与逻辑分离 动画切换由状态控制,但 FSM 本身不依赖 Unity

🚀 六、后续可扩展方向

  • 支持 带参数的 Enter/Exit(如传递伤害值);
  • 实现 状态过渡表(数据驱动允许哪些状态能互相切换);
  • 加入 协程支持(用于等待动画结束再切换);
  • 构建 分层状态机(HFSM)(如“移动”下包含“走路/跑步”)。

💬 结语

这个轻量 FSM 系统仅需 200 行左右核心代码,却能极大提升角色行为系统的可维护性。它不依赖 Unity 特性,也可用于服务器逻辑、UI 状态管理等场景。

好的架构不是一开始就复杂,而是让复杂的事情变得简单。

如果你觉得有用,欢迎点赞、收藏或分享!也欢迎在评论区讨论你的 FSM 实践经验 😊


附:完整项目结构建议

Scripts/
├── FSM/
│   ├── IFSMState.cs
│   └── FSM.cs
├── Player/
│   ├── PlayerController.cs
│   ├── PlayerFSM.cs
│   ├── States/
│   │   ├── PlayerState.cs
│   │   ├── IdleState.cs
│   │   └── RunState.cs

希望这篇博客对你有帮助!如需 GitHub 示例工程或视频讲解,也可以告诉我~

 

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询