有限状态机实现简单的AI状态(巡逻,追逐玩家,攻击)

点击打开有限状态机的实现基类

去Wiki.Unity3D就可以搜索到有关基类,下面是我自己修改了一小部分的基类

public class FSMSystem
{
    private Dictionary<StateID, FSMState> states;

    private StateID currentStateID;
    public StateID CurrentStateID { get { return currentStateID; } }
    private FSMState currentState;
    public FSMState CurrentState { get { return currentState; } }

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

    public void AddState(FSMState s)
    {

        if (s == null)
        {
            Debug.LogError("FSMState不能为空");
        }
        
        if (states.ContainsKey(s.ID))
        {
            Debug.LogError("状态" + s.ID + "已存在,不能再添加");
            return;
        }
        //第一个设置的状态就是当前状态
        if (currentState == null)
        {
            currentState = s;
            currentStateID = s.ID;
        }
        states.Add(s.ID, s);    
    }

    public void DeleteState(StateID id)
    {
        if (id == StateID.NullStateID)
        {
            Debug.LogError("无法删除空状态");
            return;
        }
        if (states.ContainsKey(id)==false)
        {
            Debug.LogError("无法删除不存在的状态:"+id);
            return;
        }
        states.Remove(id);
    }

    public void PerformTransition(Transition trans)
    {
        if (trans == Transition.NullTransition)
        {
            Debug.LogError("无法执行空条件");
            return;
        }

        StateID id = currentState.GetOutputState(trans);
          
        if (id == StateID.NullStateID)
        {
            Debug.LogWarning("当前状态" + currentStateID + "无法根据转换条件" + trans+"发生转换");
            return;
        }
        if (states.ContainsKey(id) == false)
        {
            Debug.LogError("在状态机中不存在该状态,无法发生转换");
            return;
        }
	
        FSMState nextState = states[id];

        currentState.DoBeforeLeaving();
        currentState = nextState;
        currentStateID = id;
        currentState.DoBeforeEntering();

    } 

} 
public enum Transition
{
    NullTransition = 0, 
    RestOver,
    PatrolOver,
    OutAttackRange,
    SeePlayer,
    LostPlayer,
    ClosePlayer
}


public enum StateID
{
    NullStateID = 0, 
    Idle,
    Patrol,
    Chase,
    Attack,
    Dead
}


public abstract class FSMState
{

    protected Dictionary<Transition, StateID> map = new Dictionary<Transition, StateID>();
    protected StateID stateID;
    public StateID ID { get { return stateID; } }

    protected FSMSystem fsm;
    public FSMState(FSMSystem fsm)
    {
        this.fsm = fsm;
    }

    public void AddTransition(Transition trans, StateID id)
    {
        if (trans == Transition.NullTransition)
        {
            Debug.LogError("不允许添加NullTransition");
            return;
        }

        if (id == StateID.NullStateID)
        {
            Debug.LogError("不允许添加NullState");
            return;
        }

        if (map.ContainsKey(trans))
        {
            Debug.LogError("该条件"+trans+"已经存在");
            return;
        }

        map.Add(trans, id);
    }

    public void DeleteTransition(Transition trans)
    {
        if (trans == Transition.NullTransition)
        {
            Debug.LogError("不允许删除NullTransition");
            return;
        }

        if (map.ContainsKey(trans))
        {
            map.Remove(trans);
            return;
        }
        Debug.LogError("转换条件 " + trans + " 不存在于字典中 ");
    }

    public StateID GetOutputState(Transition trans)
    {
        if (map.ContainsKey(trans))
        {
            return map[trans];
        }
        return StateID.NullStateID;
    }

    public virtual void DoBeforeEntering() { }
    public virtual void DoBeforeLeaving() { }
    
    //在状态中判断是否转换至下一状态
    public abstract void Reason(GameObject player, GameObject npc);
    
    //该状态用来调用动画播放
    public abstract void Act(GameObject player, GameObject npc,Animation anim);
    
}

所有的动画状态继承FSMState,新建一个Enemy脚本继承Monobehavior用来调用生命周期函数,在其中初始化一个FSM状态机,并赋值给相关State类,让其他State类获得该状态机的引用,Enemy脚本将挂载在一只哥布林并在场景中启用。

public class Enemy : MonoBehaviour {

    private FSMSystem fsm;
    private GameObject player;

    private Animation anim;
    void Awake()
    {
        player = GameObject.Find("Player");
        anim = GetComponentInChildren<Animation>();

        InitFSM();
    }

    // Update is called once per frame
    void Update()
    {
        fsm.CurrentState.Act(player,gameObject,anim);
        fsm.CurrentState.Reason(player,gameObject);
    }

    void InitFSM()
    {
        fsm = new FSMSystem();

        IdleState idleState = new IdleState(fsm);
        idleState.AddTransition(Transition.RestOver, StateID.Patrol);
        idleState.AddTransition(Transition.SeePlayer, StateID.Chase);

        PatrolState patrolState = new PatrolState(fsm);
        patrolState.AddTransition(Transition.PatrolOver, StateID.Idle);
        patrolState.AddTransition(Transition.SeePlayer, StateID.Chase);

        ChaseState chaseState = new ChaseState(fsm);
        chaseState.AddTransition(Transition.LostPlayer, StateID.Idle);
        chaseState.AddTransition(Transition.ClosePlayer, StateID.Attack);

        AttackState attackState = new AttackState(fsm);
        attackState.AddTransition(Transition.OutAttackRange, StateID.Chase);
        

        fsm.AddState(idleState);
        fsm.AddState(patrolState);
        fsm.AddState(chaseState);
        fsm.AddState(attackState);
    }
}

最后是各个相关状态的脚本

public class IdleState : FSMState
{
    private float restTime = 2.0f;
    private float restTimer = 0.0f;


    private float viewDistance = 3.0f;


    public IdleState(FSMSystem fsm) : base(fsm)
    {
        stateID = StateID.Idle;
    }


    public override void Act(GameObject player, GameObject npc,Animation anim)
    {
        anim.CrossFade("idle");
    }


    public override void Reason(GameObject player, GameObject npc)
    {
        restTimer += Time.deltaTime;
        if (restTimer > restTime)
        {
            restTimer = 0;
            fsm.PerformTransition(Transition.RestOver); 
        }




        Vector3 vec = player.transform.position - npc.transform.position;
        if (vec.magnitude < viewDistance)
        {
            fsm.PerformTransition(Transition.SeePlayer);
        }
    }
}
public class PatrolState : FSMState
{
    private float patrolTime = 2.0f;
    private float patrolTimer = 0.0f;

    private float patrolSpeed = 2.0f;
    private Vector3 patrolDir=Vector3.zero;
    private float viewDistance = 3.0f;
    private bool turnOver = false;
    public PatrolState(FSMSystem fsm) : base(fsm)
    {
        stateID = StateID.Patrol;
    }

    public override void Act(GameObject player, GameObject npc,Animation anim)
    {
        anim.CrossFade("run");
        if (!turnOver)
        {
            npc.transform.Rotate(patrolDir);
            turnOver = true;
        }
        npc.transform.Translate(Vector3.forward * Time.deltaTime * patrolSpeed);
    }

    public override void Reason(GameObject player, GameObject npc)
    {
        patrolTimer += Time.deltaTime;
        if (patrolTimer > patrolTime)
        {
            patrolTimer = 0;
            turnOver = false;
            fsm.PerformTransition(Transition.PatrolOver);
        }


        Vector3 vec = player.transform.position - npc.transform.position;
        if (vec.magnitude < viewDistance)
        {
            fsm.PerformTransition(Transition.SeePlayer);
        }
    }
    public override void DoBeforeEntering()
    {
        patrolDir = new Vector3(0, Random.Range(-180.0f, 180.0f), 0);
    }
}

实际开发中可以把Idle和Patrol状态改为同一个状态例如NonIntruder,这里我是一开始为了试验状态机的基类有没有bug用的,所以现在的状态如下

             

public class ChaseState : FSMState
{
    private float chaseSpeed = 3.0f;

    private float lostViewDistance = 5.0f;
    private float attackRange = 1.0f;
    public ChaseState(FSMSystem fsm) : base(fsm)
    {
        stateID = StateID.Chase;
    }

    public override void Act(GameObject player, GameObject npc, Animation anim)
    {
        npc.transform.LookAt(player.transform);
        npc.transform.Translate(Vector3.forward * Time.deltaTime * chaseSpeed);
        anim.CrossFade("run");
    }

    public override void Reason(GameObject player, GameObject npc)
    {
        Vector3 vec = player.transform.position - npc.transform.position;

        if (vec.magnitude> lostViewDistance)
        {
            fsm.PerformTransition(Transition.LostPlayer);
        }
        if(vec.magnitude < attackRange)
        {
            fsm.PerformTransition(Transition.ClosePlayer);
        }
    }
}

public class AttackState : FSMState
{
    private float waitTime = 2.0f;
    private float waitTimer = 2.0f;

    private float attackRange = 1.0f;
    private float attackAngle = 60.0f;

    public AttackState(FSMSystem fsm) : base(fsm)
    {
        stateID = StateID.Attack;
    }

    public override void Act(GameObject player, GameObject npc, Animation anim)
    {
        if (waitTimer < waitTime)
        {
            waitTimer += Time.deltaTime;
            anim.CrossFadeQueued("idle");
        }
        else
        {
            npc.transform.LookAt(player.transform);
            anim.Play("attack");
            waitTimer = 0;
        }
       
    }

    public override void Reason(GameObject player, GameObject npc)
    {
        Vector3 vec = player.transform.position - npc.transform.position;
        if (vec.magnitude > attackRange)
        {
            fsm.PerformTransition(Transition.OutAttackRange);
        }
    }
    public override void DoBeforeEntering()
    {
        base.DoBeforeEntering();
        waitTimer = 2.0f;
    }
}

当前状态涉及到的只有GameObject,Animation,如果涉及到更复杂的情况就要传入许多参数,后面可以尝试新建一个EnemyData类,挂载在需要的哥布林身上,让Enemy类去获取哥布林身上的EnemyData组件,再直接传入给State类,就不需要每次Update都要传入给Act方法大量参数。







  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值