Unity3D 成神之路unity工具类

FSM有限状态机(AI 小案例)

2017-11-09  本文已影响185人  _凉笙

Wiki官网:http://wiki.unity3d.com/index.php/Main_Page

FSM源码地址:http://wiki.unity3d.com/index.php/Finite_State_Machine

image.png

在这里我以一个Demo为例演示有限状态机的作用,首先我这里要做的是一个NPC AI的巡逻功能,在这里NPC会有两个状态,一个状态是巡逻的功能,第二个状态是追逐主角的功能,我这里让NPC距离主角如果大于10的距离的时候让NPC进入巡逻状态,而当NPC距离主角如果小于5的时候,我让NPC进入追逐主角的状态。
下面自己来写一个FSM,首先创建一个脚本FSMState,写入一下代码,里面包含一些添加和删除判断转换条件的方法

using System.Collections.Generic;
using UnityEngine;

//有哪些状态转换的条件
public enum Transition
{
    NullTransition=0,
    SawPlayer,//看到主角
    LostPlayer//看不到主角  
}
//状态ID,是每一个状态的唯一标识,每一个状态有一个StateID,而且跟其他的状态不可以重复
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 trans, StateID id)//表示由Transition这个条件可以装换到哪个StateID状态
    {
        if (trans == Transition.NullTransition || id == StateID.NullStateID)
        {
            //转换条件或者状态为空
            Debug.LogError("Transition or stateid is null!!!");//输出错误
            return;
        }
        //如果已经包含了这个状态
        if (map.ContainsKey(trans))
        {
            //当前状态已经存在
            Debug.LogError("State" + id + " is already has transition" + trans);
            return;
        }
        map.Add(trans, id);
    }
    //删除状态转换条件
    public void DeleteTransition(Transition trans)
    {
        //不包含这个trans转换条件
        if (map.ContainsKey(trans) == false)
        {
            //我们想删除的条件不存在
            Debug.LogWarning("The transition " + trans + "you want to delete is not exit in map!!!");
            return;
        }
        map.Remove(trans);
    }

    //根据传递过来的转换条件,判断是否可发生转换
    public StateID GetOutState(Transition trans)
    {
        //如果包含这个转换条件返回这个状态的StateID否则返回一个空的StateID
        if (map.ContainsKey(trans))
        {
            return map[trans];
        }
        return StateID.NullStateID;
    }
    //在进入当前状态之前,需要做的事情  初始化
    public virtual void DoBeforeEntering(){ }   

    //在离开当前状态的时候,需要做的事情  清理
    public virtual void DoBeforeLeaving() { }

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

接着是创建脚本FSMSystem,这是一个状态机管理类,有限状态机的系统管理类,写入一下代码

using System.Collections.Generic;
using UnityEngine;


/// <summary>
///状态机管理类,有限状态机系统管理类
/// </summary>
public class FSMSystem  {
    //当前状态机下有哪些状态u
    private Dictionary<StateID, FSMState> states;

    //状态机处于什么状态
    private FSMState currentState;

    public FSMState CurentState
    {
        get { return currentState; }
    }
    public FSMSystem()
    {
        states = new Dictionary<StateID, FSMState>();
    }
    //添加状态
    public void AddState(FSMState state)
    {
        if (state==null)
        {
            Debug.LogError("The state you went to add is null");return;

        }
        if (states.ContainsKey(state.ID))
        {
            Debug.LogError("The state "+state.ID+" you want to add has already been added.");return;
        }
        state.fsm = this;
        states.Add(state.ID,state);
        
    }
    //从状态机移除状态

    public void DeleteState(FSMState state)
    {
        if (state == null)
        {
            Debug.LogError("The state you went to Delete is null"); return;

        }
        if (states.ContainsKey(state.ID)==false)
        {
            Debug.LogError("The state " + state.ID + " you want to Delete is not exit."); return;
        }
        states.Remove(state.ID);
    }

    //控制状态之间的转换
    public void PorformTransition(Transition trans)
    {
        if (trans==Transition.NullTransition)
        {
            Debug.LogError("NullTransition is not allowed for a real transition.");
        }
      StateID id =  currentState.GetOutState(trans);//判断发生这个条件的时候是否可发生转换
        if (id == StateID.NullStateID)
        {
            Debug.Log("Transition is not tobe happend!没有符合条件的转换");
            return;
        }
        FSMState state;
        states.TryGetValue(id, out state);
        currentState.DoBeforeLeaving();
        currentState = state;
        currentState.DoBeforeEntering();
    }
    //设置默认状态 启动状态机
    public void Start(StateID id)
    {
        FSMState state;
        bool isGet= states.TryGetValue(id, out state);
        if (isGet)
        {
            state.DoBeforeEntering();
            currentState = state;

        }
        else
        {
            Debug.LogError("The state"+id+"is not exit in the fsm.");
        }
    }

}

接着是NPC的控制管理,里面写入一些初始化方法,在里面添加两个状态的转换条件和状态,我这里有两个状态,一个是巡逻的状态,一个是追主角的状态.

using UnityEngine;

public class NPCController : MonoBehaviour {

    private FSMSystem fsm;

    public Transform[] waypoints;
    private GameObject player;

    // Use this for initialization
    void Start () {
        player = GameObject.FindGameObjectWithTag("Player").gameObject;
        InitFSM();

    }

    //初始化状态机  添加转换条件和状态
    void InitFSM()
    {
        fsm = 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);//没有看到主角的时候转换到巡逻状态

        //添加状态
        fsm.AddState(patrolState);
        fsm.AddState(chaseState);

        fsm.Start(StateID.Patrol);//设置默认状态为巡逻状态                                   
    }

    private void Update()
    {
        fsm.CurentState.DoUpdata();
    }
}

最后就是两个状态类了,里面写入控制的逻辑,当主角大于NPC10的位置时候,NPC就会在巡逻,进入巡逻状态,当主角位置小于NPC位置5的时候NPC就会追逐主角,进入追逐状态.这两个状态类脚本我分别命名为ChaseState(追逐状态)和PatrolState(巡逻状态),下面直接放上代码

using UnityEngine;

public class PatrolState : FSMState {

    private int targetWaypoint;
    private Transform[] waypoints;
    private GameObject npc;
    private Rigidbody npcRgb;
    private GameObject player;

    public PatrolState(Transform[] wp,GameObject npc,GameObject player)
    {
        stateID = StateID.Patrol;
        waypoints = wp;
        targetWaypoint = 0;
        this.npc = npc;
        this.player = player;
        npcRgb = npc.GetComponent<Rigidbody>();
    }
    public override void DoBeforeEntering()
    {
        Debug.Log("Enting state "+ID );
    }

    
    public override void DoUpdata()
    {
        CheckTransition();
        patrolMove();
    }
    //检查转换条件
    private void CheckTransition()
    {
        if (Vector3.Distance(player.transform.position, npc.transform.position) < 5)
        {
            fsm.PorformTransition(Transition.SawPlayer);
        }
    }
    //巡逻功能
    private void  patrolMove()
    {

        npcRgb.velocity = npc.transform.forward * 10;
        Transform targetTrans = waypoints[targetWaypoint];
        Vector3 tagetPosition = targetTrans.position;
        tagetPosition.y = npc.transform.position.y;
        npc.transform.LookAt(tagetPosition);
        if (Vector3.Distance(npc.transform.position, tagetPosition) < 1)
        {
            targetWaypoint++;
            targetWaypoint %= waypoints.Length;
        }

    }
using UnityEngine;

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

    public ChaseState(GameObject npc,GameObject player)
    {
        stateID = StateID.Chase;
        this.npc = npc;
        npcRgb = npc.GetComponent<Rigidbody>();
        this.player = player;
    }
    public override void DoBeforeEntering()
    {
        Debug.Log("Enting state " + ID);
    }

    public override void DoUpdata()
    {
        CheckTransition();
        ChanseMove();
     
    }
    private void CheckTransition()

    {
        if (Vector3.Distance(player.transform.position, npc.transform.position) > 10)
        {
            fsm.PorformTransition(Transition.LostPlayer);
        }
    }
    //追主角的运动
    private void ChanseMove()
    {
        npcRgb.velocity = npc.transform.forward * 10;
        Vector3 targetposition = player.transform.position;
        targetposition.y = npc.transform.position.y;
        npc.transform.LookAt(targetposition);
    }
  
}
最后在Unity里面创建Player和NPC,我这里以Capsule代替,然后NPC上挂上Rigidbody刚体组件和NPCController脚本,然后再创建四个位置。表示巡逻的目标点。再将这四个目标点赋值给NPCController脚本里面,这样就可以运行了。 image.png 点4分13秒.gif
上一篇下一篇

猜你喜欢

热点阅读