游戏角色分层有限状态机(Hierarchical Finite StateMachine, HFSM)框架

发布时间:2026/7/9 2:33:01

游戏角色分层有限状态机(Hierarchical Finite StateMachine, HFSM)框架 目录一.HFSM的概述1.定义2.生命周期执行流程一.同父状态下的子状态切换二.跨父状态切换3.优势二.HFSM的实现1.实现角色具体状态的继承关系2.实现状态机管理叶子状态3.使用状态机参考项目https://github.com/Wafflus/unity-genshin-impact-movement-system一.HFSM的概述1.定义相较于一般的有限状态机HFSM多了一个“分层”的思想通过继承关系呈现层次化结构简化复杂系统的状态管理。即在普通平级有限状态机FSM基础上引入父子层级结构的状态机设计模式。HFSM的规则与普通FSM相似角色同一时刻必须且只能处于一种叶子状态但上层父状态处于半活跃状态状态机中的状态数目是有限的必然有一个初始状态每个状态只负责当前状态的逻辑逻辑解耦状态之间的切换需有明确的条件一般通过事件Event进行切换。2.生命周期执行流程根据面向对象的知识我们知道一.同父状态下的子状态切换退出旧叶子状态进入新叶子状态这种场景下输入回调不会重新解绑又绑定减少性能开销。二.跨父状态切换退出旧叶子状态退出旧父状态进入新父状态进入新叶子状态该场景中所有通用逻辑会自动执行。3.优势代码复用对于不同叶子状态的相同的功能可以将其写在他们的父类中以节省重写的时间。如Idle、Run、和Walk中的地面检测功能可写在Move中。可维护性当写的代码多了后碰巧又出现了bug你会发现找起来特别的折磨其实是我T-T。而HFSM的层级清晰不管是修改还是查bug通用逻辑只需要检查父状态具体行为只需要检查叶子状态即可。二.HFSM的实现1.实现角色具体状态的继承关系1.所有状态类的接口BaseStateusing System.Collections; using System.Collections.Generic; using UnityEngine; //使用接口强制要求子类实现接口方法 public interface IState { public void Enter(); public void Exit(); public void Update(); public void PhysicsUpdate(); }2.移动状态using System; using System.Collections; using System.Collections.Generic; using Unity.VisualScripting; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.Rendering.Universal; public class MoveState : IState { protected MovementStateMachine stateMachine; //构造函数 public MoveState(MovementStateMachine movementStateMachine) { stateMachine movementStateMachine; } public virtual void Enter() { //添加输入回调等 } public virtual void Exit() { //移除输入回调等 } public virtual void PhysicsUpdate() { //移动状态需要每物理帧更新的具体逻辑 } public virtual void Update() { } //移动状态特有的具体逻辑...... }3.移动状态的地面状态using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.InputSystem; public class GroundState : MoveState { public PlayerGroundedState(MovementStateMachine movementStateMachine) : base(movementStateMachine) { } public override void PhysicsUpdate() { base.PhysicsUpdate(); //地面移动状态需要每物理帧更新的具体逻辑 } public override void Enter() { base.Enter(); //其他逻辑... } public override void Exit() { base.Exit(); //其他逻辑... } //重写输入回调的添加和移除 //其他具体逻辑...... }4.待机状态using System; using System.Collections; using System.Collections.Generic; using Unity.VisualScripting; using UnityEngine; public class Idle : GroundState { public IdleState(MovementStateMachine movementStateMachine) : base(movementStateMachine) { } public override void Enter() { base.Enter(); //播放动画等其他具体逻辑... } public override void Exit() { base.Exit(); //停止动画等其他具体逻辑... } public override void Update() { base.Update(); if(stateMchine.player.inputVector2.zero) { return; } //状态的切换 if(canRun) { stateMachine.ChangeState(stateMachine.Run); } //其他切换和移动逻辑... } public override void PhysicsUpdate() { base.PhysicsUpdate(); //锁定角色速度等其他具体逻辑... } }其他状态同理。若用事件切换状态而不是每帧检测条件的话可在BaseState中加入一个方法用于实现不同状态间的切换。using System.Collections; using System.Collections.Generic; using UnityEngine; public interface IState { //原有方法... public void EventTransition(); }2.实现状态机管理叶子状态1.所有状态机的父类using UnityEngine; public abstract class StateMachine { //保存当前的状态 protected IState currentState; //用于状态间的切换状态机的核心 public void ChangeState(BaseState newState) { currentState?.Exit(); currentState newState; currentState.Enter(); } public void Update() { currentState?.Update(); } public void PhysicsUpdate() { currentState?.PhysicsUpdate(); } public void EventTransition() { currentState?.EventTransition(); } }2.移动状态机public class MovementStateMachine : StateMachine { public Player player{ get; } public Idle idle{ get; } public Walk walk{ get; } public Run run{ get; } public Jump jump{ get; } public Fall fall{ get; } public Land land{ get; } ... .... //构造函数 public MovementStateMachine(Player player) { this.player player; idle new Idle(this); ... .... //实例化所有叶子状态 } }3.战斗状态机public class CombatStateMachine : StateMachine { public Player player{ get; } public BaseAttack baseAttack{ get; } ... .... //构造函数 public MovementStateMachine(Player player) { this.player player; baseAttack new BaseAttack(); ... .... //实例化所有叶子状态 } }自此我们的状态机框架已经基本成型。那么我们怎么去使用它呢3.使用状态机在上述的框架中MovementStateMachine保存了一个Player的实例这是因为状态机和各状态需要获取角色的数据包括血量、蓝量等和组件刚体、碰撞体等。我们只需在player脚本将当前player实例传给状态机即可。而MoveStateCombatState同理保存MovementStateMachine实例不仅能用于实现状态切换还能间接获取player的数据和组件。简单来说player角色脚本只负责角色的数据状态机只负责管理状态这样实现依赖关系能实现逻辑解耦有利于我们后期的维护。由于FSM必然有一个初始状态且状态间的切换是在状态机内部自己实现的所以我们只需要给角色一个初始状态即可不用管后续状态的转换逻辑。在角色的脚本中加上public class Player:MonoBehaviour { private MovementStateMachine movementStateMachine; private CombatStateMachine combatStateMachine; private void Awake() { movementStateMachine new MovementStateMachine(this); combatStateMachine new CombatStateMachine(this); } private void Start() { movementStateMachine.ChangeState(movementStateMachine.Idle); } }让角色一开始进入待机状态然后我们的状态机就开始有条不紊地运行啦

相关新闻