FSM有限状态机的使用

解释

有限状态机(Finite State Machine, FSM)管理器,主要包括状态机本身(StateMachine类)和状态基类(StateBase类)。这个设计提供了一种灵活的方式来管理对象的状态切换,使对象在不同状态下可以表现出不同的行为。

主要功能和类结构

  1. StateMachine类(有限状态机控制器)
    • 管理和控制状态的切换,负责进入和退出不同的状态。
    • 每个状态对应一个继承自StateBase的状态类,这些状态类可以实现具体的逻辑。
    • 使用对象池技术,通过对象池管理状态实例,提升性能。
  2. StateBase类(状态基类)
    • 所有状态类的基类,定义了状态的生命周期方法,如初始化(Init)、进入状态(Enter)、退出状态(Exit)等。
    • 可以通过重写这些方法为不同状态实现具体行为。

详细方法说明

StateMachine类:
  1. Init(IStateMachineOwner owner)

    • 初始化状态机,绑定宿主对象,宿主实现IStateMachineOwner接口。
  2. ChangeState<T>(int newStateType, bool reCurrstate = false)

    • 切换到指定的新状态。
    • 如果新状态和当前状态一致,且不强制重新进入当前状态(reCurrstate=false),则不进行状态切换。
    • 调用Exit方法退出当前状态,调用Enter方法进入新状态,并注册新状态的UpdateLateUpdateFixedUpdate回调函数到MonoMgr
  3. GetState<T>(int stateType)

    • 从对象池中获取或创建一个状态实例,并初始化状态。
    • 如果该状态之前没有被创建,使用对象池创建新的状态,并初始化它(调用StateBase.Init)。
  4. Stop()

    • 停止状态机,退出当前状态并清理所有状态。
    • 清除所有状态字典中的状态,并调用UnInit方法处理状态的反初始化(回收对象)。
  5. Destroy()

    • 销毁状态机。释放所有引用并将状态机对象推回到对象池中。
  6. ResetInfo()

    • 重置状态机的基本信息(当前状态等),不涉及核心逻辑,仅为接口的一部分。

StateBase类:
  1. Init(IStateMachineOwner owner, int stateType, StateMachine stateMachine)

    • 初始化状态,在状态第一次创建时调用,绑定状态机和宿主。
  2. UnInit()

    • 当状态不再使用时,进行反初始化,主要是释放资源并将对象推回到对象池。
  3. Enter()

    • 状态进入时调用,每次状态切换都会调用该方法。子类可以重写以实现状态的进入逻辑。
  4. Exit()

    • 状态退出时调用,子类可以重写以实现退出逻辑。
  5. Update()

    • 状态更新逻辑。可以重写这个方法来为每一帧实现自定义的更新行为。
  6. LateUpdate()

    • 状态的LateUpdate方法,可以在子类中重写,实现后期更新逻辑。
  7. FixedUpdate()

    • 状态的FixedUpdate方法,可以在子类中重写,实现物理更新逻辑。
  8. 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. 定义状态类(IdleStateMoveStateAttackState):

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,角色会执行攻击动作。

在这个过程中,状态机会调用EnterUpdateExit方法,来管理角色在不同状态下的行为。


每个公有方法的应用总结

  1. Init: 初始化状态机,将宿主传递给状态机。
  2. ChangeState: 切换到新的状态,如移动、攻击、闲置等。
  3. Stop: 停止状态机,清理当前和缓存的所有状态(如游戏暂停或重置时)。
  4. Destroy: 销毁状态机,释放状态机占用的资源。
  5. GetState: 从对象池中获取新的状态对象。
  6. 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. 定义状态类(IdleStateMoveStateAttackState):

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状态");
    }
}

总结

第一种方法通俗易懂,但是项目庞大起来会变得极度臃肿,且改变状态限制条件有限需要手动写逻辑。

第二种方法虽然麻烦复杂,若是一个或者多个状态需要进行一个状态的切换,可能会导致多写的情况。不过解决方法简单,只需要抽象出基类即可。

推荐使用第二种,锻炼逻辑,思维清晰,不过代码量会变多。

上一篇:2024.10月22日- MySql的 补充知识点


下一篇:算法Day-7-输入:ransomNote = "aa", magazine = "aab" 输出:true 提示: