AI自主决策——有限状态机

本文章参考自Unity3D人工智能编程精粹,转载请注明出处。

有限状态机的FSM图

  有限状态机(FSM)由一组状态(包括一个初始状态)、输入和根据输入及现有状态转换为下一个状态的转换函数组成。

  什么是状态?

    飞翔,行走,攻击,跑步,游泳,这些动词是状态。高兴,伤心,生气这些形容词也是状态,一些名词也能表示状态。状态的关键意义是:不同的状态对应不同的行为。

  关于有限状态机(FSM),需要了解以下几点:

  1. 有限状态机是AI系统中最简单的,同时也是最为有效和最常用的方法。对于游戏中的每个对象,都可以在其生命周期内区分出一些状态。

  2. 当某些条件发生时,状态机从当前状态转换为其他状态。在不同的状态下,游戏对象对外部激励做出不同的反应或执行不同的动作。有限状态机方法让我们可以很容易地把游戏对象的行为划分为小块,这样更容易调试和扩展。

  3. 用户编写的每个程序都是状态机。当没写下一个if语句的时候,就创造出了一段至少拥有两个状态的代码——写的代码越多,程序就可能具有越多的状态。Switch和if语句数量的爆发会让事情很快失去控制,程序会出现奇怪的BUG。

  4. 有限状态机是AI中最容易的部分,但也很容易出错。在设计有限状态机的时候,一定要认真地考虑清楚其中的每个状态和转换部分。

  简易的FSM图

有限状态机的状态转移矩阵

  状态转移矩阵是简介表明FSM图的转移矩阵,分别有当前状态,输入,输出状态。如上图的状态转移矩阵可以为

用FSM框架实现通用的有限状态机

  介绍一个通用的FSM框架,可以在unitycommunity.com上找到,地址是http://wiki.unity3d.com/index/php?title=Finite_State_Machine。

  其实仔细想想,这个FSM框架是不是跟Animator动画状态机非常像,对!动画状态机、有限状态机,你没看错。但是我们还是得会写才是王道!

  上面的状态UML图为

  FSM: AdvancedFSM的父类,继承MonoBehaviour,封装了其中的Start,Update,FixedUpdate方法。定义了巡逻数组等。

  AdvanceFSM: 管理所有的状态类,FSM的派生类,负责管理FSMState的派生类,并且随着当前状态和输入,进行状态更新。

  FSMState: 所有状态的基类,状态类中具有添加转换,删除转换的方法,用于管理记录这些转换。在FSMState中有一个字典对象,用来存储 “转换—状态” 对,表明在当前状态(即这个类所代表的状态)下,如果发生某个 “转换” ,FSM将会转移到何种状态。可以通过AddTransition方法和DeleteTransition方法(自己写)添加或删除 “转换—状态” 对。另外这个类中还包括Reason方法和Act方法,Reason用来确定是否需要转换状态。Act方法定义了在本状态角色行为,例如移动,动画等。

  AICotroller: AdvanceFSM的派生类,负责创建有限状态机,通过它来控制角色。

  代码如下:

  FSM.cs

public class FSM : MonoBehaviour {

    /// <summary>
    /// 玩家Transfrom组件
    /// </summary>
    protected Transform playerTransform;

    /// <summary>
    /// 下一个目标
    /// </summary>
    protected Vector3 destPos;

    /// <summary>
    /// 攻击速率
    /// </summary>
    protected float attackRate;
    /// <summary>
    /// 攻击间隔
    /// </summary>
    protected float elapsedTime;

    /// <summary>
    /// 初始化
    /// </summary>
    protected virtual void Init() { }
    /// <summary>
    /// Update
    /// </summary>
    protected virtual void FSMUpdate() { }
    /// <summary>
    /// FixedUpdate
    /// </summary>
    protected virtual void FSMFixedUpdate() { }

    void Start()
    {
        Init();
    }

    void Update()
    {
        FSMUpdate();
    }

    void FixedUpdate()
    {
        FSMFixedUpdate();
    }

}

  

  AdvanceFSM.cs

public class AdvanceFSM : FSM {

    private List<FSMState> fsmStates;

    private FSMStateID currentStateID;
    public FSMStateID CurrentStateID { get { return currentStateID; } }

    private FSMState currentState;
    public FSMState CurrentState { get { return currentState; } }

    public AdvanceFSM()
    {
        fsmStates = new List<FSMState>();
    }

    /// <summary>
    /// 添加状态
    /// </summary>
    /// <param name="fsmState"></param>
    public void AddFSMState(FSMState fsmState)
    {
        if (fsmState == null)
        {
            return;
        }

        if (fsmStates.Count == 0)
        {
            fsmStates.Add(fsmState);
            currentState = fsmState;
            currentStateID = fsmState.ID;
            return;
        }

        foreach (FSMState state in fsmStates)
        {
            if (state.ID == fsmState.ID)
            {
                return;
            }
        }
        fsmStates.Add(fsmState);
    }

    /// <summary>
    /// 删除状态
    /// </summary>
    /// <param name="fsmState"></param>
    public void DelFSMState(FSMStateID fsmState)
    {
        foreach (FSMState state in fsmStates)
        {
            if (state.ID == fsmState)
            {
                fsmStates.Remove(state);
                return;
            }
        }
    }

    /// <summary>
    /// 转移新状态
    /// </summary>
    /// <param name="transition"></param>
    public void PerformTransition(Transition transition)
    {
        FSMStateID id = currentState.GetOutputState(transition);

        currentStateID = id;

        foreach (FSMState state in fsmStates)
        {
            if (state.ID == currentStateID)
            {
                currentState = state;
                return;
            }
        }
    }

}

public enum Transition
{
    /// <summary>
    /// 看到玩家
    /// </summary>
    SawPlayer = 0,
    /// <summary>
    /// 接近玩家
    /// </summary>
    ReachPlayer,
    /// <summary>
    /// 玩家离开视线
    /// </summary>
    LostPlayer,
    /// <summary>
    /// 被伤害
    /// </summary>
    Injured,
    /// <summary>
    /// 死亡
    /// </summary>
    NoHealth,
}

public enum FSMStateID
{
    /// <summary>
    /// 巡逻状态
    /// </summary>
    Idling = 0,
    /// <summary>
    /// 追逐状态
    /// </summary>
    Chasing,
    /// <summary>
    /// 攻击状态
    /// </summary>
    Attacking,
    /// <summary>
    /// 受伤状态
    /// </summary>
    Injuring,
    /// <summary>
    /// 死亡状态
    /// </summary>
    Dead,
}

  FSMState.cs

public abstract class FSMState
{
    
    /// <summary>
    /// 转换-状态,信息字典
    /// </summary>
    protected Dictionary<Transition, FSMStateID> map = new Dictionary<Transition, FSMStateID>();

    /// <summary>
    /// 状态编号ID
    /// </summary>
    protected FSMStateID stateID;
    /// <summary>
    /// ID公开
    /// </summary>
    public FSMStateID ID
    {
        get
        {
            return stateID;
        }
    }

    /// <summary>
    /// 目标位置
    /// </summary>
    protected Vector3 destPos;

    /// <summary>
    /// 转向速度
    /// </summary>
    protected float rotSpeed;
    /// <summary>
    /// 移动速度
    /// </summary>
    protected float speed;
    /// <summary>
    /// 追逐距离
    /// </summary>
    protected float chaseDistance = 7.0f;
    /// <summary>
    /// 攻击距离
    /// </summary>
    protected float attackDistance = 1.4f;
    /// <summary>
    /// 到达距离
    /// </summary>
    protected float arriveDistance = 1.4f;

    /// <summary>
    /// 添加“转换-状态”
    /// </summary>
    /// <param name="transition"></param>
    /// <param name="id"></param>
    public void AddTransition(Transition transition, FSMStateID id)
    {
        if (map.ContainsKey(transition))
        {
            Debug.LogWarning("GG");
        }

        map.Add(transition, id);
    }

    /// <summary>
    /// 删除“转换-状态”
    /// </summary>
    /// <param name="transition"></param>
    public void DelTransition(Transition transition)
    {
        if (map.ContainsKey(transition))
        {
            map.Remove(transition);
        }
    }

    /// <summary>
    /// 获取转换后新状态编号
    /// </summary>
    /// <param name="transition"></param>
    /// <returns></returns>
    public FSMStateID GetOutputState(Transition transition)
    {
        Debug.Log(transition);
        return map[transition];
    }

    /// <summary>
    /// 是否需要转换,如何转换
    /// </summary>
    /// <param name="player"></param>
    /// <param name="monster"></param>
    public abstract void Reason(Transform player, Transform monster);
    
    /// <summary>
    /// 角色行为
    /// </summary>
    /// <param name="player"></param>
    /// <param name="monster"></param>
    public abstract void Act(Transform player, Transform monster);

}

  AIController.cs

public class AIController : AdvanceFSM {

    /// <summary>
    /// 角色生命值
    /// </summary>
    private int health;

    /// <summary>
    /// 初始化AI角色的FSM,在FSM基类的Start函数中调用。
    /// </summary>
    protected override void Init()
    {
        health = 100;
        Blood.Instance.Change(health);

        elapsedTime = 0.0f;

        attackRate = 2.0f;

        GameObject objPlayer = GameObject.FindGameObjectWithTag("Player");
        playerTransform = objPlayer.transform;

        if (!playerTransform)
            print("GG");

        ConstructFSM();
    }

    /// <summary>
    /// 这个函数在初始化Init方法中调用,为AI角色构造FSM。
    /// </summary>
    private void ConstructFSM()
    {
        IdleState idle = new IdleState();
        idle.AddTransition(Transition.SawPlayer, FSMStateID.Chasing);
        idle.AddTransition(Transition.Injured, FSMStateID.Injuring);
        idle.AddTransition(Transition.NoHealth, FSMStateID.Dead);

        ChaseState chase = new ChaseState();
        chase.AddTransition(Transition.LostPlayer, FSMStateID.Idling);
        chase.AddTransition(Transition.ReachPlayer, FSMStateID.Attacking);
        chase.AddTransition(Transition.Injured, FSMStateID.Injuring);
        chase.AddTransition(Transition.NoHealth, FSMStateID.Dead);

        AttackState attack = new AttackState();
        attack.AddTransition(Transition.LostPlayer, FSMStateID.Idling);
        attack.AddTransition(Transition.SawPlayer, FSMStateID.Chasing);
        attack.AddTransition(Transition.Injured, FSMStateID.Injuring);
        attack.AddTransition(Transition.NoHealth, FSMStateID.Dead);

        InjuryState injury = new InjuryState();
        injury.AddTransition(Transition.LostPlayer, FSMStateID.Idling);
        injury.AddTransition(Transition.SawPlayer, FSMStateID.Chasing);
        injury.AddTransition(Transition.NoHealth, FSMStateID.Dead);

        DeadState dead = new DeadState();
        dead.AddTransition(Transition.NoHealth, FSMStateID.Dead);

        AddFSMState(idle);
        AddFSMState(chase);
        AddFSMState(attack);
        AddFSMState(injury);
        AddFSMState(dead);
    }

    protected override void FSMUpdate()
    {
        elapsedTime += Time.deltaTime;
    }

    protected override void FSMFixedUpdate()
    {
        CurrentState.Act(playerTransform, transform);
        CurrentState.Reason(playerTransform, transform);

    }

    /// <summary>
    /// 这个方法在每个状态类的Reason中被调用。
    /// </summary>
    /// <param name="t"></param>
    public void SetTransition(Transition t)
    {
        PerformTransition(t);
    }

    /// <summary>
    /// AI角色与其他物体碰撞时,调用这个函数。
    /// </summary>
    /// <param name="collider"></param>
    void OnTriggerEnter(Collider collider)
    {
        if (collider.gameObject.tag.Equals("Player"))
        {
            health -= 40;
            Blood.Instance.Change(health);

            if (health <= 0)
            {
                SetTransition(Transition.NoHealth);
            }
            else
            {
                SetTransition(Transition.Injured);
            }
        }
    }

    public void Attack(Animator anim)
    {
        if (elapsedTime >= attackRate)
        {
            anim.SetTrigger("Surround Attack");
            elapsedTime = 0f;
        }
    }

    public void Injury(Animator anim)
    {
        anim.SetTrigger("Take Damage");
    }
}

  IdleState.cs(没有做随机移动,用站立表示默认状态)

public class IdleState : FSMState
{
    /// <summary>
    /// 初始化状态
    /// </summary>
    public IdleState()
    {
        stateID = FSMStateID.Idling;
        rotSpeed = 6.0f;
        speed = 10.0f;
    }

    /// <summary>
    /// 播放动画
    /// </summary>
    /// <param name="player"></param>
    /// <param name="monster"></param>
    public override void Act(Transform player, Transform monster)
    {
        Animator anim = monster.GetComponent<Animator>();
    }

    /// <summary>
    /// 是否进行状态转移
    /// </summary>
    /// <param name="player"></param>
    /// <param name="monster"></param>
    public override void Reason(Transform player, Transform monster)
    {
        //如果玩家与怪物距离小于等于追逐距离,那么转移状态为追逐
        if (Vector3.Distance(player.position,monster.position) <= chaseDistance)
        {
            monster.GetComponent<AIController>().SetTransition(Transition.SawPlayer);
        }
    }
}

  

  

原文地址:https://www.cnblogs.com/SHOR/p/6622557.html