解释
有限状态机(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状态");
}
}
总结
第一种方法通俗易懂,但是项目庞大起来会变得极度臃肿,且改变状态限制条件有限需要手动写逻辑。
第二种方法虽然麻烦复杂,若是一个或者多个状态需要进行一个状态的切换,可能会导致多写的情况。不过解决方法简单,只需要抽象出基类即可。
推荐使用第二种,锻炼逻辑,思维清晰,不过代码量会变多。