解释
有限状态机(Finite State Machine, FSM)管理器,主要包括状态机本身(StateMachine
类)和状态基类(StateBase
类)。这个设计提供了一种灵活的方式来管理对象的状态切换,使对象在不同状态下可以表现出不同的行为。
主要功能和类结构
- StateMachine类(有限状态机控制器)
- 管理和控制状态的切换,负责进入和退出不同的状态。
- 每个状态对应一个继承自
StateBase
的状态类,这些状态类可以实现具体的逻辑。 - 使用对象池技术,通过对象池管理状态实例,提升性能。
- StateBase类(状态基类)
- 所有状态类的基类,定义了状态的生命周期方法,如初始化(
Init
)、进入状态(Enter
)、退出状态(Exit
)等。 - 可以通过重写这些方法为不同状态实现具体行为。
- 所有状态类的基类,定义了状态的生命周期方法,如初始化(
详细方法说明
StateMachine
类:
-
Init(IStateMachineOwner owner)
- 初始化状态机,绑定宿主对象,宿主实现
IStateMachineOwner
接口。
- 初始化状态机,绑定宿主对象,宿主实现
-
ChangeState<T>(int newStateType, bool reCurrstate = false)
- 切换到指定的新状态。
- 如果新状态和当前状态一致,且不强制重新进入当前状态(
reCurrstate=false
),则不进行状态切换。 - 调用
Exit
方法退出当前状态,调用Enter
方法进入新状态,并注册新状态的Update
、LateUpdate
、FixedUpdate
回调函数到MonoMgr
。
-
GetState<T>(int stateType)
- 从对象池中获取或创建一个状态实例,并初始化状态。
- 如果该状态之前没有被创建,使用对象池创建新的状态,并初始化它(调用
StateBase.Init
)。
-
Stop()
- 停止状态机,退出当前状态并清理所有状态。
- 清除所有状态字典中的状态,并调用
UnInit
方法处理状态的反初始化(回收对象)。
-
Destroy()
- 销毁状态机。释放所有引用并将状态机对象推回到对象池中。
-
ResetInfo()
- 重置状态机的基本信息(当前状态等),不涉及核心逻辑,仅为接口的一部分。
StateBase
类:
-
Init(IStateMachineOwner owner, int stateType, StateMachine stateMachine)
- 初始化状态,在状态第一次创建时调用,绑定状态机和宿主。
-
UnInit()
- 当状态不再使用时,进行反初始化,主要是释放资源并将对象推回到对象池。
-
Enter()
- 状态进入时调用,每次状态切换都会调用该方法。子类可以重写以实现状态的进入逻辑。
-
Exit()
- 状态退出时调用,子类可以重写以实现退出逻辑。
-
Update()
- 状态更新逻辑。可以重写这个方法来为每一帧实现自定义的更新行为。
-
LateUpdate()
- 状态的
LateUpdate
方法,可以在子类中重写,实现后期更新逻辑。
- 状态的
-
FixedUpdate()
- 状态的
FixedUpdate
方法,可以在子类中重写,实现物理更新逻辑。
- 状态的
-
ResetInfo()
- 该方法不包含实际逻辑,仅作为对象池中重置信息的一部分。
示例案例:角色移动状态机
假设我们在一个游戏中为角色设计了移动和攻击两个状态。
1. 定义宿主类,实现IStateMachineOwner
接口:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class StateCharacter : MonoBehaviour,IStateMachineOwner
{private StateMachine stateMachine;private StateCharacterState currentState;private void Awake(){stateMachine = new StateMachine();stateMachine.Init(this);stateMachine.ChangeState<StateCharacter_IdleState>(0);}private void Update(){if (Input.GetKeyUp(KeyCode.W)){stateMachine.ChangeState<StateCharacter_MoveState>(1);}if (Input.GetMouseButtonDown(0)){stateMachine.ChangeState<StateCharacter_AttackState>(2);}}
}
2. 定义状态类(IdleState
、MoveState
、AttackState
):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class StateCharacterStateBase : StateBase
{protected StateCharacter stateCharacter;public override void Init(IStateMachineOwner owner, int stateType, StateMachine stateMachine){base.Init(owner, stateType, stateMachine);stateCharacter = (StateCharacter)owner;}
}using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class StateCharacter_IdleState : StateCharacterStateBase
{public override void Enter(){Debug.Log("进入Idle状态");}public override void Update(){Debug.Log("执行Idle状态");}public override void Exit(){Debug.Log("退出Idle状态");}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class StateCharacter_AttackState : StateCharacterStateBase
{public override void Enter(){Debug.Log("进入Attack状态");}public override void Update(){Debug.Log("执行Attack状态");}public override void Exit(){Debug.Log("退出Attack状态");}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class StateCharacter_MoveState : StateCharacterStateBase
{public override void Enter(){Debug.Log("进入Move状态");}public override void Update(){Debug.Log("执行Move状态");}public override void Exit(){Debug.Log("退出Move状态");}
}
3. 切换状态:
- 当游戏角色按下
W
键时,状态机会切换到MoveState
,角色会执行移动逻辑。 - 当点击鼠标左键时,状态机会切换到
AttackState
,角色会执行攻击动作。
在这个过程中,状态机会调用Enter
、Update
和Exit
方法,来管理角色在不同状态下的行为。
每个公有方法的应用总结
Init
: 初始化状态机,将宿主传递给状态机。ChangeState
: 切换到新的状态,如移动、攻击、闲置等。Stop
: 停止状态机,清理当前和缓存的所有状态(如游戏暂停或重置时)。Destroy
: 销毁状态机,释放状态机占用的资源。GetState
: 从对象池中获取新的状态对象。ResetInfo
: 可用于复位状态机的当前状态(没有实际逻辑)。
通过这套状态机管理系统,可以方便地管理角色或游戏对象在不同状态下的行为逻辑。
注:第二种使用方法
将StateMachine在包一层
1. 定义宿主类,实现IStateMachineOwner
接口:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class StateCharacter : MonoBehaviour,IStateMachineOwner
{private StateMachine stateMachine;private StateCharacterState currentState;private void Awake(){stateMachine = new StateMachine();stateMachine.Init(this);ChangeState(StateCharacterState.Idle);}public void ChangeState(StateCharacterState state, bool reCurrent = false){currentState = state;switch (state){case StateCharacterState.Idle:stateMachine.ChangeState<StateCharacter_IdleState>((int)state, reCurrent);break;case StateCharacterState.Move:stateMachine.ChangeState<StateCharacter_MoveState>((int)state, reCurrent);break;case StateCharacterState.Attack:stateMachine.ChangeState<StateCharacter_AttackState>((int)state, reCurrent);break;}}
}
2. 定义状态类(IdleState
、MoveState
、AttackState
):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class StateCharacterStateBase : StateBase
{protected StateCharacter stateCharacter;public override void Init(IStateMachineOwner owner, int stateType, StateMachine stateMachine){base.Init(owner, stateType, stateMachine);stateCharacter = (StateCharacter)owner;}
}using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class StateCharacter_IdleState : StateCharacterStateBase
{public override void Enter(){Debug.Log("进入Idle状态");}public override void Update(){Debug.Log("执行Idle状态");if (Input.GetKeyUp(KeyCode.W)){stateCharacter.ChangeState(StateCharacterState.Move);}if (Input.GetMouseButtonDown(0)){stateCharacter.ChangeState(StateCharacterState.Attack);}}public override void Exit(){Debug.Log("退出Idle状态");}
}using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class StateCharacter_MoveState : StateCharacterStateBase
{float time;float timer = 3;public override void Enter(){Debug.Log("进入Move状态");}public override void Update(){Debug.Log("执行Move状态");time += Time.deltaTime;if (time > timer){time = 0;stateCharacter.ChangeState(StateCharacterState.Idle);}if (Input.GetMouseButtonDown(0)){stateCharacter.ChangeState(StateCharacterState.Attack);}}public override void Exit(){Debug.Log("退出Move状态");}
}using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class StateCharacter_AttackState : StateCharacterStateBase
{float time;float timer = 3;public override void Enter(){Debug.Log("进入Attack状态");}public override void Update(){Debug.Log("执行Attack状态");time += Time.deltaTime;if (time > timer){time = 0;stateCharacter.ChangeState(StateCharacterState.Idle);}if (Input.GetKeyUp(KeyCode.W)){stateCharacter.ChangeState(StateCharacterState.Move);}}public override void Exit(){Debug.Log("退出Attack状态");}
}
总结
第一种方法通俗易懂,但是项目庞大起来会变得极度臃肿,且改变状态限制条件有限需要手动写逻辑。
第二种方法虽然麻烦复杂,若是一个或者多个状态需要进行一个状态的切换,可能会导致多写的情况。不过解决方法简单,只需要抽象出基类即可。
推荐使用第二种,锻炼逻辑,思维清晰,不过代码量会变多。