Unity 有限状态机 Finite State Machine

章盛
2023-12-01

为什么要使用状态机?

if-else 难以维护

通过 if-else 或者是 Switch 语句管理角色的状态,虽然说实现起来比较简单并且迅速,但是当状态多了之后,就变得难以管理和维护,每当新增了一个新的状态,都需要修改原来的代码,这就不符合我们所说的 开放-封闭原则。

开放封闭原则

开放封闭原则是所有面向对象原则的核心,软件设计所追求的目标就是封装变化、降低耦合。

状态机的实现思路

对角色的各种状态,抽象出一个状态基类。

设计一个状态管理器来存储和切换状态。

使用枚举存储角色的各种状态转换触发条件和角色的各种状态的索引。

需要新增状态的时候,不需要修改原来的脚本文件,只需要新建一个派生类就可以了。

基础类

只需要两个类和两个枚举

状态机

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

public class FiniteStateMachine
{
    public List<StateBase> states;
    public StateBase currentState;
    public StateIndex currentStateIndex;

    public FiniteStateMachine()
    {
        states = new List<StateBase>();
    }

    public void AddState(StateBase state)
    {
        if (state == null)
        {
            Debug.LogError("FSM ERROR:不允许添加空引用");
        }

        //初始化状态机
        if (states.Count == 0)
        {
            states.Add(state);
            currentState = state;
            currentStateIndex = state.stateIndex;
            return;
        }

        if (!states.Contains(state))
        {
            states.Add(state);
        }
    }

    public void DeleteState(StateIndex index)
    {
        if (index == StateIndex.NullStateID)
        {
            Debug.LogError("FSM ERROR: NullStateID is not allowed for a real state");
            return;
        }

        foreach (StateBase i in states)
        {
            if (i.stateIndex == currentStateIndex)
            {
                states.Remove(i);
                return;
            }
        }

        Debug.LogError("FSM ERROR: 无法删除状态 " + currentStateIndex.ToString() +
               ". 它不在状态列表中");
    }

    public void Transit(TransitCondition transitCondition)
    {
        if (transitCondition == TransitCondition.NullTransition)
        {
            Debug.LogError("FSM ERROR: 不执行空的转换条件");
            return;
        }

        // Check if the currentState has the transition passed as argument
        StateIndex stateIndex = currentState.GetTheStateIndexOfTheTransitConditionGiven(transitCondition);
        if (stateIndex == StateIndex.NullStateID)
        {
            Debug.LogError("FSM ERROR: 没有目标状态要转换");
            return;
        }
     
        currentStateIndex = stateIndex;
        foreach (StateBase i in states)
        {
            if (i.stateIndex == currentStateIndex)
            {
                currentState.OnExit();
                currentState = i;
                currentState.OnEnter();
                break;
            }
        }
    } 
}

状态基类

using System.Collections.Generic;
using UnityEngine;

public abstract class StateBase
{
    Dictionary<TransitCondition, StateIndex> dict = new Dictionary<TransitCondition, StateIndex>();
    public StateIndex stateIndex;

    public void AddTransition(TransitCondition transitCondition,StateIndex stateIndex)
    {
        // Check if anyone of the args is invalid
        if (transitCondition == TransitCondition.NullTransition)
        {
            Debug.LogError("FSMState ERROR: NullTransition is not allowed for a real transition");
            return;
        }

        if (stateIndex == StateIndex.NullStateID)
        {
            Debug.LogError("FSMState ERROR: NullStateID is not allowed for a real ID");
            return;
        }

        if (dict.ContainsKey(transitCondition))
        {
            Debug.LogError("FSMState ERROR: 当前transitCondition已添加");
            return;
        }

        dict.Add(transitCondition, stateIndex);
    }

    public void DeleteTransition(TransitCondition transitCondition)
    {
        if (transitCondition == TransitCondition.NullTransition)
        {
            Debug.LogError("FSMState ERROR: NullTransition is not allowed");
            return;
        }

        if (dict.ContainsKey(transitCondition))
        {
            dict.Remove(transitCondition);
            return;
        }

        Debug.LogError("FSMState ERROR: 找不到要删除的transitCondition");
    }

    public StateIndex GetTheStateIndexOfTheTransitConditionGiven(TransitCondition transitCondition)
    {
        if (dict.ContainsKey(transitCondition))
        {
            return dict[transitCondition];
        }
        return StateIndex.NullStateID;
    }

    public virtual void OnEnter() { }
    public virtual void OnExit() { }

    //以下两个函数要在Update中运行
    public abstract void Work(GameObject player, GameObject emeny);                                                //在当前状态下需要做的
    public abstract void isTransitable(GameObject player, GameObject emeny);    //判断是否要转换,参数可能用不到
}

状态转换触发条件

public enum TransitCondition
{
    NullTransition,
    LostPlayer,
    SawPlayer,
    AtSleepPosition,
    SleepTimesUp,
}

状态索引

public enum StateIndex
{
    NullStateID,
    WalkAround,
    ChasingPlayer,
    Sleep,
}

应用类

小怪控制脚本

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

public class EmenyController : MonoBehaviour
{
    public GameObject player;
    public FiniteStateMachine finiteStateMachine;
    public Transform[] wayPoints;

    public void SetTransition(TransitCondition t)
    {
        finiteStateMachine.Transit(t);
        Debug.Log(t.ToString());
    }

    void Awake()
    {
        InstantiateFiniteStateMachine();
    }

    void InstantiateFiniteStateMachine()
    {
        WalkAroundState walkAroundState = new WalkAroundState(wayPoints);
        walkAroundState.AddTransition(TransitCondition.SawPlayer,StateIndex.ChasingPlayer);
        walkAroundState.AddTransition(TransitCondition.AtSleepPosition, StateIndex.Sleep);

        ChaseState chaseState = new ChaseState();
        chaseState.AddTransition(TransitCondition.LostPlayer,StateIndex.WalkAround);

        RestState restState = new RestState();
        restState.AddTransition(TransitCondition.SleepTimesUp, StateIndex.WalkAround);

        finiteStateMachine = new FiniteStateMachine();
        finiteStateMachine.AddState(walkAroundState);
        finiteStateMachine.AddState(chaseState);
        finiteStateMachine.AddState(restState);
    }

    void FixedUpdate()
    {
        Debug.Log(finiteStateMachine.currentState.stateIndex);
        finiteStateMachine.currentState.Work(player, gameObject);
        finiteStateMachine.currentState.isTransitable(player, gameObject);
    }

    float sleepingTimer;
    public void Sleeping()
    {
        sleepingTimer += Time.deltaTime;
        Debug.Log("sleepingTimer:"+ sleepingTimer);
        GetComponent<Rigidbody>().velocity = Vector3.zero;
    }

    public bool IsSleepTimesUp()
    {
        if (sleepingTimer >= 3f)
        {
            sleepingTimer = 0f;
            return true;
        }
        else
        {
            return false;
        } 
    }
}

小怪巡逻状态

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

public class WalkAroundState : StateBase
{
    int nextWayPoint;
    Transform[] wayPoints;
    bool alreadySleep = true;

    public WalkAroundState(Transform[] wayPoints)
    {
        this.wayPoints = wayPoints;
        nextWayPoint = 0;
        stateIndex = StateIndex.WalkAround;
    }

    public override void Work(GameObject player, GameObject emeny)
    {
        //巡逻
        Vector3 velocity = emeny.GetComponent<Rigidbody>().velocity;
        Vector3 moveDirection = wayPoints[nextWayPoint].position - emeny.transform.position;

        //与路径点的距离小于0.1,认定为到达路径点
        if (moveDirection.magnitude < 0.1)
        {
            nextWayPoint++;

            //如果到达了最后一个路径点,那么就走向第一个路径点
            if (nextWayPoint >= wayPoints.Length)
            {
                nextWayPoint = 0;
            }

            //如果到达了起点
            if(nextWayPoint == 1)
            {
                //需要进入睡觉状态
                alreadySleep = false;
            }

            Debug.Log("下一个路径点:" + nextWayPoint);
        }
        else
        {
            //速度为10
            velocity = moveDirection.normalized * 10;

            //向路径点旋转
            emeny.transform.rotation = Quaternion.Slerp(emeny.transform.rotation,
                                                      Quaternion.LookRotation(moveDirection),
                                                      5 * Time.deltaTime);
            emeny.transform.eulerAngles = new Vector3(0, emeny.transform.eulerAngles.y, 0);
        }

        emeny.GetComponent<Rigidbody>().velocity = velocity;
    }

    public override void isTransitable(GameObject player,GameObject emeny)
    {
        //如果到达起点
        if(nextWayPoint == 1 && alreadySleep == false)
        {
            alreadySleep = true;
            emeny.GetComponent<EmenyController>().SetTransition(TransitCondition.AtSleepPosition);
        }

        //如果看到玩家,就切换到追逐的状态
        if(Vector3.Distance(emeny.transform.position,player.transform.position)< 5f)
        {
             Debug.Log("NpcSawPlayer");
             emeny.GetComponent<EmenyController>().SetTransition(TransitCondition.SawPlayer);
        }
    }
}

小怪追逐状态

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

public class ChaseState : StateBase
{
    public ChaseState()
    {
        stateIndex = StateIndex.ChasingPlayer;
    }

    public override void Work(GameObject player, GameObject emeny)
    {
        //追逐
        Vector3 velocity = emeny.GetComponent<Rigidbody>().velocity;
        Vector3 moveDirection = player.transform.position - emeny.transform.position;

        //向玩家旋转
        emeny.transform.rotation = Quaternion.Slerp(emeny.transform.rotation,
                                                  Quaternion.LookRotation(moveDirection),
                                                  5 * Time.deltaTime);
        emeny.transform.eulerAngles = new Vector3(0, emeny.transform.eulerAngles.y, 0);

        velocity = moveDirection.normalized * 10;
        emeny.GetComponent<Rigidbody>().velocity = velocity;
    }

    public override void isTransitable(GameObject player, GameObject emeny)
    {
        //如果距离大于10,就切换到巡逻状态
        if (Vector3.Distance(emeny.transform.position, player.transform.position) >= 10)
            emeny.GetComponent<EmenyController>().SetTransition(TransitCondition.LostPlayer);
    }
}

小怪休息状态

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

public class RestState : StateBase
{
    public RestState()
    {
        stateIndex = StateIndex.Sleep;
    }

    public override void Work(GameObject player, GameObject emeny)
    {
        emeny.GetComponent<EmenyController>().Sleeping();
    }

    public override void isTransitable(GameObject player, GameObject emeny)
    {
        //如果睡醒了,就转换状态
        if (emeny.GetComponent<EmenyController>().IsSleepTimesUp())
        {
            emeny.GetComponent<EmenyController>().SetTransition(TransitCondition.SleepTimesUp);
        }
    }
}

 类似资料: