如何实现一个简单的FSM有限状态机

前言

       在游戏开发中,有限状态机(Finite State Machine,FSM)是一种强大的设计模式,用于描述对象在不同状态下的行为以及状态之间的转换,对于有限的状态,每个状态有自己独立的实现逻辑和过渡逻辑,每个状态可以切换至零-多个状态。通过描述对象在不同状态下的行为以及状态之间的转换,为游戏开发者提供了一种清晰而有效的方式来管理复杂的游戏逻辑。

一、FSM有限状态机简介

有限状态机是一种数学模型,用于描述一个系统在不同状态之间的转换。在游戏开发中,这个系统可以是任何具有多种行为的实体,比如角色、敌人、NPC等。每个状态代表一个特定的行为或状态,而状态之间的转换则取决于一些条件或触发事件。

FSM的基本元素

一个典型的FSM包含以下几个基本元素:

1. 状态(State)

状态是FSM的基本单元,代表对象在某一时刻的行为。在游戏中,一个状态可以对应角色的站立、行走、攻击等动作。

2. 转换(Transition)

转换定义了状态之间的关系,描述了在何种条件下从一个状态切换到另一个状态。条件可以是一些变量的取值、用户输入、时间等。

3. 动作(Action)

动作是与状态关联的具体行为,包括但不限于播放动画、改变属性、触发事件等。一个状态可能有多个关联的动作。

4. 状态机控制器(Controller)

状态机控制器是FSM的管理者,负责监控当前状态、添加状态、过渡状态。

二、应用场景

如下图:

对于传统的AI在空闲、巡逻、追逐、攻击等状态的切换,我们需要这样去实现:

        if (空闲条件)
        {
            //进入空闲状态
        }
        else if (巡逻条件)
        {
            //进入巡逻状态
        }
        else if (追逐条件)
        {
            //进入追逐状态
        }
        else if (攻击条件)
        {
            //进入攻击状态
        }

这样写需要频繁使用if else 或者switch语句,这样不仅代码的可读性和维护性差,还会导致每个状态耦合在一起,违背了高内聚低耦合的设计思想。

那么状态机是如何实现如上图几种状态之间的逻辑呢?

首先,我们需要定义一个状态的抽象类或者接口,也就是状态基类。里面需要包含三个核心方法,这里我使用抽象类来实现。

    /// <summary>
    /// 状态进入时执行
    /// </summary>
    public abstract void OnEnter();

    /// <summary>
    /// 每一帧调用
    /// </summary>
    public abstract void OnUpdate();

    /// <summary>
    /// 状态退出时执行
    /// </summary>
    public abstract void OnExit();

这三个方法分别表示一个状态在进入、执行中、退出,当某个状态进入时,将会调用一次OnEnter方法,可以用于处理该状态的初始化任务,当这个状态退出时调用一次OnExit方法,适合用于处理一些资源释放的操作,而OnUpdate方法是状态在执行的过程中每一帧都会调用,适合处理一些需要每帧做判断的逻辑。

然后我们还需要一个状态枚举,里面用于列举所需要的状态:

public enum StateType
{
    IDLE = 0,
    PATROL,
    CHASE,
    ATTACK
}

完整的状态基类如下:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public abstract class StateBase
{
    protected StateType _state;
    protected Action<StateType> Transition;
    protected Animator _animator;
    protected Transform _target;

    public StateType State
    {
        get => _state;
    }

    //防止外部实例化
    public StateBase(Animator animator, Transform target)
    {
        _animator = animator;
        _target = target;
    }

    /// <summary>
    /// 状态进入时执行
    /// </summary>
    public abstract void OnEnter();

    /// <summary>
    /// 每一帧调用
    /// </summary>
    public abstract void OnUpdate();

    /// <summary>
    /// 状态退出时执行
    /// </summary>
    public abstract void OnExit();

    /// <summary>
    /// 设置状态过渡的回调
    /// </summary>
    /// <param name="action"></param>
    public void SetTransitionAction(Action<StateType> action)
    {
        Transition = action;
    }
}

public enum StateType
{
    IDLE = 0,
    PATROL,
    CHASE,
    ATTACK
}

我们还需要一个用于管理状态的状态机管理类:

用于保存状态、添加状态、设置默认状态、切换状态等的管理。

状态机管理类代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class StateMachineSystem
{
    private StateBase _currentState;

    private Dictionary<StateType, StateBase> _allStates;

    public StateBase CurrentState
    {
        get => _currentState;
    }

    public StateMachineSystem()
    {
        _allStates = new Dictionary<StateType, StateBase>();
    }

    /// <summary>
    /// 构造函数设置默认状态
    /// </summary>
    /// <param name="stateType"></param>
    /// <param name="defaultState"></param>
    public StateMachineSystem(StateType stateType, StateBase defaultState)
    {
        _allStates = new Dictionary<StateType, StateBase>();
        AddState(stateType,defaultState);
        TransitionState(stateType);
    }

    public void OnUpdate()
    {
        _currentState.OnUpdate();
    }

    /// <summary>
    /// 添加状态
    /// </summary>
    /// <param name="stateType"></param>
    /// <param name="newState"></param>
    public void AddState(StateType stateType, StateBase newState)
    {
        if (_allStates.ContainsKey(stateType)) return;
        
        _allStates.Add(stateType,newState);
        _allStates[stateType].SetTransitionAction(TransitionState);
    }

    /// <summary>
    /// 切换状态
    /// </summary>
    /// <param name="stateType"></param>
    public void TransitionState(StateType stateType)
    {
        if (_currentState == _allStates[stateType]) return;
        if (!_allStates.ContainsKey(stateType))
        {
            Debug.LogErrorFormat("{0} does not exist!",stateType.ToString());
            return;
        }
        
        _currentState?.OnExit();
        _currentState = _allStates[stateType];
        _currentState?.OnEnter();
    }
}

然后分别创建空闲、巡逻、追逐、攻击等状态类:

空闲状态:

public class IdleState : StateBase
{
    private float time = 0f;
    public IdleState(Animator animator, Transform target) : base(animator, target)
    {
        _state = StateType.IDLE;
    }
    public override void OnEnter()
    {
        Debug.Log("进入Idle状态");
    }

    public override void OnUpdate()
    {
        Debug.Log("正在执行Idle状态");
        time += Time.deltaTime;
        if (time >= 2f)
        {
            Transition(StateType.PATROL);
            time = 0f;
        }
    }

    public override void OnExit()
    {
        Debug.Log("退出Idle状态");
    }
}

巡逻状态:

public class PatrolState : StateBase
{
    private float time = 0f;
    public PatrolState(Animator animator, Transform target) : base(animator, target)
    {
        _state = StateType.PATROL;
    }
    public override void OnEnter()
    {
        Debug.Log("进入巡逻状态");
    }

    public override void OnUpdate()
    {
        Debug.Log("正在执行巡逻状态");
        time += Time.deltaTime;
        if (time >= 5f)
        {
            Debug.Log("发现敌人!");
            Transition(StateType.CHASE);
            time = 0f;
        }
    }

    public override void OnExit()
    {
        Debug.Log("退出巡逻状态");
    }
}

追逐状态:

public class ChaseState : StateBase
{
    private float time = 0f;
    public ChaseState(Animator animator, Transform target) : base(animator, target)
    {
        _state = StateType.CHASE;
    }
    public override void OnEnter()
    {
        Debug.Log("进入追逐状态");
    }

    public override void OnUpdate()
    {
        Debug.Log("正在执行追逐状态");
        time += Time.deltaTime;
        if (time > 5f)
        {
            Debug.Log("敌人已到攻击范围");
            Transition(StateType.ATTACK);
            time = 0f;
        }
    }

    public override void OnExit()
    {
        Debug.Log("退出追逐状态");
    }
}

攻击状态:

public class AttackState : StateBase
{
    public AttackState(Animator animator, Transform target) : base(animator, target)
    {
        _state = StateType.ATTACK;
    }
    public override void OnEnter()
    {
        Debug.Log("进入攻击状态");
    }

    public override void OnUpdate()
    {
        Debug.Log("正在执行攻击状态");
    }

    public override void OnExit()
    {
        Debug.Log("退出攻击状态");
    }
}

定义一个Enemy测试类来测试:

public class Enemy : MonoBehaviour
{
    private StateMachineSystem _stateMachine;
    private Animator _animator;

    private void Awake()
    {
        _animator = GetComponent<Animator>();

        _stateMachine = new StateMachineSystem(StateType.IDLE, new IdleState(_animator, transform));
    }

    private void Start()
    {
        _stateMachine.AddState(StateType.PATROL,new PatrolState(_animator,transform));
        _stateMachine.AddState(StateType.CHASE,new ChaseState(_animator,transform));
        _stateMachine.AddState(StateType.ATTACK,new AttackState(_animator,transform));
    }

    private void Update()
    {
        _stateMachine.OnUpdate();
    }
}

通过上面的代码可以发现,这样每个状态就分离开了,我们不需要关心其它状态的逻辑,只需要设置一个满足条件去切换对应的状态,每个状态只需要关心自己的实现,从而实现高内聚低耦合。

三、总结

        当然上面的代码框架是最简单的FSM有限状态机的实现,本意只为理解其基本原理,大家可以发现虽然这样写进行了状态分离,但是每个状态还是需要在OnUpdate里面去做if esle的判断,这样写灵活度还是不够高,我们其实还可以将条件单独提取出来,再定义一个参数类,做成类似于Animator Controller那种状态过渡的形式,这样让状态机更灵活。不过这样写需要增加蛮多代码量,本文就不再过多阐述,大家可以根据这个思路自行扩展。

  • 20
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
有限状态机FSM)是一个抽象的计算模型,由一组状态和在这些状态之间进行转换的规则组成。它被广泛应用于各种领域,例如自动控制系统、编译器设计、游戏开发等。 在面试中,以下是一些与FSM相关的常见问题和答案: 问题1:什么是有限状态机FSM)? 答:有限状态机FSM)是一个抽象的计算模型,由一组状态和在这些状态之间进行转换的规则组成。它可以被看作是一个状态转换图,其中每个节点表示一个状态,每条边表示一个状态转换。 问题2:FSM有哪些基本元素? 答:FSM有三个基本元素: - 状态(State):表示系统在某一时刻的状态。 - 转移(Transition):定义了状态之间的转换规则。 - 事件(Event):触发状态转换的外部或内部事件。 问题3:什么是确定性有限状态机(DFA)和非确定性有限状态机(NFA)? 答:确定性有限状态机(DFA)是指每个输入符号只能引起一个状态转换的FSM。而非确定性有限状态机(NFA)允许在某些情况下存在多个可能的转换路径。DFA和NFA在理论上等价,但在实际应用中有不同的使用场景和特点。 问题4:如何实现一个简单有限状态机? 答:可以使用编程语言来实现一个简单有限状态机。基本的实现方式是使用状态和转移规则的数据结构,并在每个状态转换时更新当前状态。 问题5:FSM有哪些应用领域? 答:FSM在许多领域都有应用,包括但不限于自动控制系统、编译器设计、游戏开发、网络协议等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值