欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

有限状态机的实现

程序员文章站 2022-04-08 09:10:38
...

1. 概述  

        在游戏中存在各种的“状态”,不仅是角色的行为状态,还有例如场景的开始、加载、游戏中等状态,此次采用一个小案例实现有限状态机——一个NPC一个角色player,当角色player不在NPC视野内时NPC在固定的四个点之间巡逻,当player在NPC视野内时,NPC会追逐player,丢失跟踪后继续巡逻,player的移动采用在scene中手动移动。

2. FSMState.cs

        FSMState为定义状态的基类,但也将状态转换条件和状态ID枚举定义放在了此类中,Transition中存了所有的状态转换条件,StateID中定义了所有的状态。状态定了一些状态的基本信息,有状态ID以及储存转换条件和状态ID的字典,持有一个状态管理的对象..........

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

/// <summary>
/// 状态转换的条件
/// </summary>
public enum Transition
{
    NullTransition=0,
    SawPlayer,//看到主角
    LostPlayer//看不到主角
}

/// <summary>
/// 状态ID,每个状态唯一表示,不重复,用于区分不同状态
/// </summary>
public enum StateID
{
    NullStateID=0,
    Patrol,//巡逻
    Chase//追逐主角
}

public abstract class FSMState{
    protected StateID stateID;
    public StateID ID
    {
        get { return stateID; }
    }

    protected Dictionary<Transition, StateID> map = new Dictionary<Transition, StateID>();

    public FSMSystem fsm;

    public void AddTransition(Transition tran,StateID id)
    {
        if(tran==Transition.NullTransition||id==StateID.NullStateID)
        {
            Debug.LogError("添加的状态转换条件或状态Id不能为空!");
            return;
        }
        if(map.ContainsKey(tran))
        {
            Debug.LogError("状态转换条件 " + id + " 已经存在" + tran);
            return;
        }

        map.Add(tran, id);
    }

    public void DeleteTransition(Transition tran)
    {
        if(map.ContainsKey(tran)==false)
        {
            Debug.LogWarning("想要删除的状态转换条件 " + tran + " 不存在!");
            return;
        }

        map.Remove(tran);
    }

    /// <summary>
    /// 根据传过来的转换条件,判断是否可以转换,然后返回状态ID
    /// </summary>
    /// <param name="tran"></param>
    /// <returns></returns>
    public StateID GetOutputState(Transition tran)
    {
        if(map.ContainsKey(tran))
        {
            return map[tran];
        }

        return StateID.NullStateID;
    }

    //进入当前状态前需做的
    public virtual void DoBeforeEntering() { }
    public virtual void DoBeforeLeaving() {  }

    public abstract void DoUpdate();//在当前状态机出于当前状态的时候,会一直调用
}

3. ChaseState.cs

        定义的追逐状态。

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

public class ChaseState : FSMState {
    private GameObject npc;
    private GameObject player;

    private Rigidbody npcRig;

    public ChaseState(GameObject npc,GameObject player)
    {
        stateID = StateID.Chase;

        this.player = player;
        this.npc = npc;

        npcRig = npc.GetComponent<Rigidbody>();
    }

    public override void DoBeforeEntering()
    {
        Debug.Log("进入 "+ID+" 状态");
    }

    public override void DoUpdate()
    {
        ChaseMove();
        CheckTransition();
    }

    public void CheckTransition()
    {
        if(Vector3.Distance(npc.transform.position,player.transform.position)>2)
        {
            fsm.PerformTransition(Transition.LostPlayer);
        }
    }

    public void ChaseMove()
    {
        npcRig.velocity = npc.transform.forward * 3;

        Transform targetTrans = player.transform;

        Vector3 targetPosition = targetTrans.position;

        targetPosition.y = npc.transform.position.y;

        npc.transform.LookAt(targetPosition);
    }
}

4. PatrolState.cs

        定义的巡逻状态。

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

public class PatrolState : FSMState {

    private int targetWaypoint;
    private Transform[] wayPoints;//巡逻的路径点
    private GameObject npc;

    private Rigidbody npcRig;

    private GameObject player;
    public PatrolState(Transform[] wp,GameObject npc,GameObject player)
    {
        stateID = StateID.Patrol;

        wayPoints = wp;

        this.npc = npc;
        targetWaypoint = 0;
        npcRig = npc.GetComponent<Rigidbody>();

        this.player = player;
    }

    public override void DoBeforeEntering()
    {
        Debug.Log("进入 " + ID + " 状态");
    }

    public override void DoUpdate()
    {
        PatrolMove();

        CheckTransition();
    }

    private void PatrolMove()
    {
        npcRig.velocity = npc.transform.forward * 3;

        Transform targetTrans = wayPoints[targetWaypoint];
        Vector3 targetPosition = targetTrans.position;

        targetPosition.y = npc.transform.position.y;

        npc.transform.LookAt(targetPosition);

        if (Vector3.Distance(npc.transform.position, targetPosition) < 1)
        {
            targetWaypoint++;
            targetWaypoint = targetWaypoint % wayPoints.Length;//使targetWaypoint循环
        }
    }

    private void CheckTransition()
    {
        if(Vector3.Distance(player.transform.position,npc.transform.position)<2)
        {
            fsm.PerformTransition(Transition.SawPlayer);
        }
    }
}

5. FSMSystem.cs

        对所有状态的管理,用字典储存了所有的状态,持有一个记录当前状态的变量。

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


/// <summary>
/// 有限状态机管理类,有限状态机系统类
/// </summary>
public class FSMSystem {

    //当前状态机下面所有状态
    private Dictionary<StateID, FSMState> statesDict;

    //状态机当前所处的状态
    private FSMState currentState;
    public FSMState CurrentState
    {
        get { return currentState; }
    }

    public FSMSystem()
    {
        statesDict = new Dictionary<StateID, FSMState>();
    }

    /// <summary>
    /// 向状态机中添加状态
    /// </summary>
    /// <param name="state"></param>
    public void AddState(FSMState state)
    {
        if(state==null)
        {
            Debug.LogError("要添加的状态为空!");
            return;
        }
        if(statesDict.ContainsKey(state.ID))
        {
            Debug.LogError("要添加的状态已经存在!StateID: "+state.ID);
            return;
        }

        state.fsm = this;
        statesDict.Add(state.ID, state);
    }

    /// <summary>
    /// 从状态机中删除状态
    /// </summary>
    /// <param name="state"></param>
    public void DeleteState(FSMState state)
    {
        if (state == null)
        {
            Debug.LogError("要删除的状态为空!");
            return;
        }
        if (statesDict.ContainsKey(state.ID)==false)
        {
            Debug.LogError("要删除的 "+state+" 状态不存在!");
            return;
        }

        statesDict.Remove(state.ID);
    }

    /// <summary>
    /// 根据所传的转换条件,控制状态之间的转换
    /// </summary>
    /// <param name="tran"></param>
    public void PerformTransition(Transition tran)
    {
        if(tran==Transition.NullTransition)
        {
            Debug.LogError("空的转换条件不能转换!");
            return;
        }

        StateID id = currentState.GetOutputState(tran);
        if(id==StateID.NullStateID)
        {
            Debug.Log("没有符合条件的转换!");
            return;
        }

        FSMState state;
        statesDict.TryGetValue(id,out state);
        currentState.DoBeforeLeaving();
        currentState = state;
        currentState.DoBeforeEntering();
    }

    /// <summary>
    /// 设置默认状态,启动状态机
    /// </summary>
    /// <param name="id"></param>
    public void Start(StateID id)
    {
        FSMState state;
        bool isGet = statesDict.TryGetValue(id, out state);

        if(isGet)
        {
            state.DoBeforeEntering();
            currentState = state;
        }
        else
        {
            Debug.LogError("状态 " + id + " 不存在");
        }
    }
}

6. NPCController.cs

        挂载于NPC上的脚本,且需要刚体组件,并将player的标签设置为Player,用于查找到player;且赋值四个路标位置。

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

public class NPCController : MonoBehaviour {

    private FSMSystem fsmSystem;

    private GameObject player;

    public Transform[] wayPoints;
	void Start () {
        player = GameObject.FindGameObjectWithTag("Player");
        InitFSM();

        
	}
	
	/// <summary>
    /// 初始化状态机
    /// </summary>
	void InitFSM () {
        fsmSystem = new FSMSystem();

        PatrolState patrolState = new PatrolState(wayPoints,this.gameObject,player);
        patrolState.AddTransition(Transition.SawPlayer, StateID.Chase);

        ChaseState chaseState = new ChaseState(this.gameObject,player);
        chaseState.AddTransition(Transition.LostPlayer, StateID.Patrol);

        fsmSystem.AddState(patrolState);
        fsmSystem.AddState(chaseState);

        fsmSystem.Start(StateID.Patrol);
	}

    private void Update()
    {
        fsmSystem.CurrentState.DoUpdate();
    }
}
有限状态机的实现

相关标签: 有限状态机