【AI】FSM有限状态机

在设计游戏AI时,我们需要找到一个简单,可扩展的编辑逻辑的方案,从而加速游戏开发的迭代速度。

案例:一个怪物在指定路径巡逻,当玩家怪物与玩家距离小于等于5米时会追逐玩家,当大于5米时会继续巡逻。这里就分为了巡逻、追逐两种状态。

FSM状态机主要使用多态来实现扩展。


首先状态切换一般分为三种进入状态触发、保持状态时触发,离开状态触发,进而我们创建基类:FSMState

public abstract class FSMState
{
    protected int stateID;
    protected FSMSystem fsm;

    public int StateID
    {
        get { return this.stateID; }
    }


    public FSMState(int id, FSMSystem fsm)
    {
         this.stateID = id;
         this.fsm = fsm;
    }

    //进入状态触发
    public virtual void OnEnter(params object[] args) { }
    //保持状态触发  每帧执行
    public virtual void OnStay(params object[] args) { }
    //离开状态触发
    public virtual void OnExit(params object[] args) { }

}

 


 

定义一个状态机,主要用于存储所属对象所有状态映射、切换状态方法等:FSMSystem

public class FSMSystem
{
    private Dictionary<int, FSMState> states = new Dictionary<int, FSMState>();

    private FSMState curFSMState;

    // 添加状态时,默认第一添加的时默认状态
    public void AddState(FSMState fsmState)
    {
        if (fsmState == null)
        {
            Debug.LogError("FSMSystem AddState is not null");
            return;
        }

        if (this.curFSMState == null)
        {
            this.curFSMState = fsmState;
            this.curFSMState.OnEnter();
        }

        if (this.states.ContainsKey(fsmState.StateID))
        {
            Debug.LogError("FSMSystem map is contains stateId:" + fsmState.StateID.ToString());
        }
        else
        {
            this.states.Add(fsmState.StateID, fsmState);
        }
    }

    public void DeleteState(int id)
    {

        if (!this.states.ContainsKey(id))
        {
            Debug.LogError("无法删除不存在的状态:" + id.ToString());
            return;
        }

        this.states.Remove(id);
    }

    // 切换状态
    public void TranslateState(int id)
    {

        if (this.curFSMState == null)
        {
            Debug.LogError("curFSMState is null");
            return;
        }

        if (!this.states.ContainsKey(id))
        {
            Debug.LogError("states is not contains id " + id.ToString());
            return;
        }

        FSMState state = this.states[id];

        this.curFSMState.OnExit();
        this.curFSMState = state;
        this.curFSMState.OnEnter();

    }

    //状态保持
    public void Update(GameObject npc)
    {
        if (this.curFSMState != null)
        {
            this.curFSMState.OnStay(npc);
        }
    }

}

 


巡逻状态脚本:PatrolState

// 巡逻状态
public class PatrolState : FSMState
{
    Transform[] paths;    // 巡逻路径
    private int index = 0;
    private Transform target;
    public PatrolState(int stateID, FSMSystem fsm, Transform target) : base(stateID, fsm)
    {
        this.paths = GameObject.Find("Path").GetComponentsInChildren<Transform>();

        this.target = target;
    }

    public override void OnStay(params object[] args)
    {
        if (args.Length == 0)
        {
            Debug.LogError("PatrolState args length is zero");
            return;
        }
        var npc = args[0] as GameObject;

        npc.transform.LookAt(paths[this.index].position);
        npc.transform.Translate(Vector3.forward * Time.deltaTime * 3);
        if (Vector3.Distance(npc.transform.position, this.paths[this.index].position) < 1)
        {
            this.index++;
            this.index %= this.paths.Length;
        }

        if (Vector3.Distance(npc.transform.position, this.target.position) <= 5)
        {
            //切换为追逐状态
            this.fsm.TranslateState((int)NPCState.Chase);
        }

    }

    public override void OnEnter(params object[] args)
    {
        Debug.LogError("进入巡逻状态");
    }

    public override void OnExit(params object[] args)
    {
        Debug.LogError("退出巡逻状态");
    }

}

 


怪物追逐状态脚本:ChaseState

//当怪物距离玩家小于5米的时候开始追逐,大于5米继续巡逻
public class ChaseState : FSMState
{
    private Transform target;       // 追逐目标对象
    public ChaseState(int stateID, FSMSystem fsm, Transform target) : base(stateID, fsm)
    {
        this.target = target;
    }


    public override void OnEnter(params object[] args)
    {
        Debug.LogError("进入追逐状态");
    }

    public override void OnStay(params object[] args)
    {
        if (args.Length == 0)
        {
            Debug.LogError("ChaseState args length is zero");
            return;
        }

        var npc = args[0] as GameObject;

        if (Vector3.Distance(npc.transform.position, this.target.position) > 5)
        {
            //切换巡逻状态
            this.fsm.TranslateState((int)NPCState.Patrol);
        }
        npc.transform.LookAt(this.target);
        npc.transform.Translate(Vector3.forward * Time.deltaTime * 3, Space.Self);
    }

    public override void OnExit(params object[] args)
    {
        Debug.LogError("离开追逐状态");
    }

}

 


创建怪物脚本,用来实例化 FSM状态机,然后将怪物所有状态添加到状态机中:Enemy

public enum NPCState
{
    Idle,
    Patrol,                         //巡逻状态
    Chase,                         // 追逐
}

public class Enemy : MonoBehaviour
{
    public Transform target;

    private FSMSystem fsm;
    void Start()
    {
        this.fsm = new FSMSystem();

        FSMState patrolState = new PatrolState((int)NPCState.Patrol, fsm, target);        // 巡逻状态
        FSMState chaseState = new ChaseState((int)NPCState.Chase, fsm, target);        // 追逐状态

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


    void Update()
    {
        this.fsm.Update(this.gameObject);
    }

}


如果需要添加新的状态只需要继承 FSMState写逻辑就可以了,包括FSMState类的输入函数都可根据需求进行增删,通过多态的特性使状态机扩展更方便。

FSM状态机优点:

       1.解决过于麻烦的状态转换(人物动画过多)。

       2.使的各个状态之间相互独立,互补影响。

       3.避免状态与状态之间的耦合度。

       4.规则简单,可读性强。

缺点:

       1.状态一多,各状态联系就会变复杂。

       2.代码量大。

 

随着开发进行,AI更智能,FSM已经不在适用,就有了行为树的抽象来解决这个问题。

 

 

 

 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值