Unity 中实现FSM(有限状态机)框架及简单应用

简介

本文实现有限状态机的一个基本框架,基于此框架实现一个物体巡逻以及追赶功能的演示

基本含义:有限状态机,在不同的阶段下会有不同运行状态的系统,这些状态是有限的、不重叠的。在某一时刻一定处于所有状态中的一种,可以受到影响,并可以做出反应,或者转移至其它状态。

在一个状态机中,主要具有

  • 状态
  • 条件
  • 转移
  • 行为
  • 事件

为何要使用状态机?实际上类似于设计模式里的状态模式,对于状态模式

摘取《大话设计模式》里的一些描述——将与特定状态相关的行为局部化,并将不同状态的行为分割开来。消除庞大的套件分支语句,把各种状态转移逻辑分布到State子类之间,来减少互相间的依赖。当一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为时,就可以考虑使用状态模式

简单的状态机实现

如果在应用过程中,不需要复杂的状态机实现,只需要按条件切换功能的话

下方实现也可算作一个非常简单的状态机实现

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

public enum State1
{
    Idle,
    Walk,
    Attack,
}

public class Simple_FSM : MonoBehaviour
{
    private State1 state = State1.Idle;


    void Update()
    {
        switch (state)
        {
            case State1.Idle: 
                ProcessStateIdle();
                break;                
            case State1.Walk: 
                ProcessStateWalk();
                break;
            case State1.Attack:
                ProcessStateAttack();
                break;
            default: 
                break;
        }

    }

    private void ProcessStateIdle() { }
    private void ProcessStateAttack() { }
    private void ProcessStateWalk() { }
}

高级状态机实现

父状态类:

每个状态中维护一个 状态-条件 列表。记录切换条件和状态的对应关系

拥有添加条件,删除条件,得到状态的方法

Entering, Leaving, Act, Reason 为具体行为,在子类中编写

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

/// <summary>
/// 状态
/// </summary>
public enum StateID
{
    NullState,
    Patrol,
    Chase
}

/// <summary>
/// 条件
/// </summary>
public enum Transition
{
    NullTransition,
    FindTarget,
    LostTarget
}

public abstract class FSMState 
{
    protected StateID stateID;
    public StateID ID => stateID;
    protected Dictionary<Transition, StateID> map = new Dictionary<Transition, StateID>();
    protected FSMSystem fsm;

    public FSMState(FSMSystem fsm)
    {
        this.fsm = fsm;
    }

    /// <summary>
    /// 添加条件-状态
    /// </summary>
    /// <param name="t"></param>
    /// <param name="s"></param>
    public void AddTransition(Transition t, StateID s)
    {
        if (t == Transition.NullTransition)
        {
            Debug.LogError("Transition is null");
            return;
        }
        if (s == StateID.NullState)
        {
            Debug.LogError("StateID is null");
            return;
        }
        if (map.ContainsKey(t))
        {
            Debug.LogError(t + " already exists in the map");
            return;
        }
        map.Add(t, s);
    }

    /// <summary>
    /// 删除条件
    /// </summary>
    /// <param name="t"></param>
    public void RemoveTransition(Transition t) 
    {
        if (t == Transition.NullTransition)
        {
            Debug.LogError("Transition is null");
            return;
        }
        if (map.ContainsKey(t) == false)
        {
            Debug.LogError(t + " is not found in map");
            return;
        }
        map.Remove(t);
    }

    /// <summary>
    /// 得到某条件对应状态
    /// </summary>
    /// <param name="t"></param>
    /// <returns></returns>
    public StateID GetState(Transition t)
    {
        if (map.ContainsKey(t))
        {
            return map[t];
        }
        return StateID.NullState;
    }

    /// <summary>
    /// 进入状态
    /// </summary>
    public virtual void Entering() { }
    /// <summary>
    /// 离开状态
    /// </summary>
    public virtual void Leaving() { }
    /// <summary>
    /// 执行状态
    /// </summary>
    public abstract void Act(GameObject npc);
    /// <summary>
    /// 判断转换条件
    /// </summary>
    public abstract void Reason(GameObject npc);
}

状态管理类

preformTransition用来进行状态转移

UpDate里不断执行 当前状态的Act行为,以及 Reason行为判断转移事件

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

public class FSMSystem
{
    protected FSMState currentState;
    protected StateID currentID;
    private Dictionary<StateID, FSMState> states = new Dictionary<StateID, FSMState>();
    
    public void Update(GameObject npc)
    {
        currentState.Act(npc);
        currentState.Reason(npc);   
    }

    public void AddState(FSMState s)
    {
        if (s is null)
        {
            Debug.LogError("s is null");
            return;
        }
        if (currentState is null)
        {
            currentState = s;
            currentID = s.ID;
        }
        if (s.ID == StateID.NullState)
        {
            Debug.LogError("ID is NullState");
            return;
        }
        states.Add(s.ID, s);
    }
    public void RemoveState(FSMState s) 
    {
        if (s is null)
        {
            Debug.LogError("s is null");
            return;
        }
        StateID stateID = s.ID;
        if(stateID == StateID.NullState)
        {
            Debug.LogError("ID is NullState");
            return;
        }
        if(!states.ContainsKey(stateID))
        {
            Debug.LogError("ID is not found in states");
            return;
        }
        states.Remove(stateID);
    }

    public void preformTransition(Transition t)
    {
        if(t is Transition.NullTransition)
        {
            Debug.LogError("t is NullTransition");
            return;
        }
        StateID stateID = currentState.GetState(t);
        if (stateID == StateID.NullState)
        {
            Debug.LogError("stateID is NullState");
            return;
        }
        if (!states.ContainsKey(stateID))
        {
            Debug.LogError("stateID is not found in states");
            return;
        }

        FSMState s = states[stateID];
        //离开当前状态
        currentState.Leaving();
        currentState = s;
        currentID = stateID;
        //进入新状态
        currentState.Entering();
    }
}

使用示例实现

创建新的状态——巡逻态

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

public class PatrolState : FSMState
{
    private List<Transform> path = new List<Transform>();
    private int index = 0;
    private Transform player;
    public PatrolState(FSMSystem fsm) : base(fsm)
    {
        stateID = StateID.Patrol;

        Transform path = GameObject.Find("Path").transform;
        Transform[] children = path.GetComponentsInChildren<Transform>();
        foreach (Transform item in children)
        {
            if (item != path)
            {
                this.path.Add(item);
            }
        }
        player = GameObject.Find("Player").transform;
    }

    public override void Act(GameObject npc)
    {
        npc.transform.LookAt(path[index].position);
        npc.transform.Translate(npc.transform.forward * 3.0f * Time.deltaTime, Space.World);
        if (Vector3.Distance(npc.transform.position, path[index].position) < 1.0f)
        {
            ++index;
            index %= path.Count;
        }
    }

    public override void Reason(GameObject npc)
    {
        float tmp = Vector3.Angle(npc.transform.forward, player.position - npc.transform.position);
        if (tmp <= 60 && Vector3.Distance(npc.transform.position, player.position) < 3.0f)
        {
            fsm.preformTransition(Transition.FindTarget);
        }
    }
}

创建追逐态

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

public class ChaseState : FSMState
{
    private Transform player;
    public ChaseState(FSMSystem fsm) : base(fsm)
    {
        stateID = StateID.Chase;
        player = GameObject.Find("Player").transform;
    }

    public override void Act(GameObject npc)
    {
        npc.transform.LookAt(player);
        npc.transform.Translate(npc.transform.forward * 3.5f * Time.deltaTime, Space.World);
    }

    public override void Reason(GameObject npc)
    {
        if (Vector3.Distance(npc.transform.position, player.transform.position) > 8.0f)
        {
            fsm.preformTransition(Transition.LostTarget);
        }
    }
}

创建一个敌人脚本,用来与状态建立联系

InitFSM中需要做:

  1. 新建一个状态机系统对象
  2. 新建一个状态类
  3. 将该状态类下所有 切换条件 与 对应切换状态 绑定
  4. 重复 2 - 3 步骤,直到所需状态类创建完毕
  5. 将新建的状态全部添加进状态机系统里
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;

public class Enemy : MonoBehaviour
{
    private FSMSystem fsm;

    // Start is called before the first frame update
    void Start()
    {
        InitFSM();
    }

    // Update is called once per frame
    void Update()
    {
        fsm.Update(gameObject);       
    }

    void InitFSM()
    {
        fsm = new FSMSystem();
        FSMState patrolState = new PatrolState(fsm);
        patrolState.AddTransition(Transition.FindTarget, StateID.Chase);

        FSMState chaseState = new ChaseState(fsm);
        chaseState.AddTransition(Transition.LostTarget, StateID.Patrol);

        fsm.AddState(patrolState);
        fsm.AddState(chaseState);
    }
}

在Unity中新建一个空对象Path,添加几个路径结点,新建一个Player对象 ,新建Enemy对象

在Enemy对象身上挂载Enemy脚本

效果演示


本文仅为学习内容总结,按需查看

  • 9
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值