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

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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容