Unity Fsm 敌人有限状态机(一)

在场景中添加两个游戏物体,一个为玩家并修改其Tag为Player,另一个为NPC为其添加NPCControl脚本,并为其将玩家角色和路径添加上去。(该案例利用状态机简单的实现了一个NPC的简单AI---巡逻---看到玩家----追逐玩家----丢失玩家----巡逻)

原文链接:http://wiki.unity3d.com/index.php/Finite_State_Machine

效果:

状态基类:

using System;
using UnityEngine;
using System.Collections.Generic;


/// <summary>  
/// 状态机中的状态类:以字典的形式保存状态的转换  
/// 1. Reason() 决定触发哪个转换  
/// 2. Act() 决定NPC在当前状态下的行为  
/// </summary>  
public abstract class FSMState
{
    protected Dictionary<Transition, StateID> map = new Dictionary<Transition, StateID>();  // 状态转换映射  
    protected StateID stateID;                     // 状态ID  
    public StateID ID { get { return stateID; } }  // 获取当前状态ID  

    /// 添加转换  
    public void AddTransition(Transition trans, StateID id)
    {
        // 空值检验  
        if (trans == Transition.NullTransition)
        {
            Debug.LogError("FSMState ERROR: 不能添加空转换");
            return;
        }

        if (id == StateID.NullStateID)
        {
            Debug.LogError("FSMState ERROR: 不能添加空状态");
            return;
        }

        // 检查是否已经有该转换  
        if (map.ContainsKey(trans))
        {
            Debug.LogError("FSMState ERROR: 状态 " + stateID.ToString() + " 已经包含转换 " + trans.ToString() + "不可添加另一个状态");
            return;
        }

        map.Add(trans, id);
    }

    /// 删除状态转换  
    public void DeleteTransition(Transition trans)
    {
        // 空值检验  
        if (trans == Transition.NullTransition)
        {
            Debug.LogError("FSMState ERROR: 不能删除空转换");
            return;
        }

        // 检验是否有配对的转换  
        if (map.ContainsKey(trans))
        {
            map.Remove(trans);
            return;
        }
        Debug.LogError("FSMState ERROR: 转换 " + trans.ToString() + " - 状态 " + stateID.ToString() + " 不存在");
    }

    /// 获取下一个状态  
    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 Update(GameObject player, GameObject npc);

}

状态机管理类:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


/// <summary>
/// 为过渡加入枚举标签
/// 不要修改第一个标签,NullTransition会在FSMSytem类中使用
/// </summary>
public enum Transition
{
    NullTransition = 0, // Use this transition to represent a non-existing transition in your system
    //用这个过度来代表你的系统中不存在的状态
    SawPlayer,//这里配合NPCControl添加两个NPC的过渡
    LostPlayer,
}

/// <summary>
/// 状态id
/// 为状态加入枚举标签
/// 不要修改第一个标签,NullStateID会在FSMSytem中使用 
/// </summary>
public enum StateID
{
    NullStateID = 0, // Use this ID to represent a non-existing State in your syste
    //使用这个ID来代表你系统中不存在的状态ID    
    ChasingPlayer,//这里配合NPCControl添加两个状态
    FollowingPath,

}


/// <summary>
///状态机类:包含状态列表
/// 该类便是有限状态机类
/// 它持有者NPC的状态集合并且有添加,删除状态的方法,以及改变当前正在执行的状态
/// </summary>
/// <summary>  
/// 状态机类:包含状态列表  
/// 1. 删除状态  
/// 2. 改变当前状态  
/// </summary>  
public class FsmSystem
{
    private List<FSMState> states;  // 状态列表  

    // 在状态机中改变当前状态的唯一途径是通过转换,当前状态不可直接改变  
    private StateID currentStateID;
    public StateID CurrentStateID { get { return currentStateID; } }
    private FSMState currentState;
    public FSMState CurrentState { get { return currentState; } }

    

    public FsmSystem()
    {
        states = new List<FSMState>();
    }

    /// 添加状态  
    public void AddState(FSMState s)
    {
        // 空值检验  
        if (s == null)
        {
            Debug.LogError("FSM ERROR: 不可添加空状态");
        }

        // 当所添加状态为初始状态  
        if (states.Count == 0)
        {
            states.Add(s);
            currentState = s;
            currentStateID = s.ID;
            return;
        }

        // 遍历状态列表,若不存在该状态,则添加  
        foreach (FSMState state in states)
        {
            if (state.ID == s.ID)
            {
                Debug.LogError("FSM ERROR: 无法添加状态 " + s.ID.ToString() + " 因为该状态已存在");
                return;
            }
        }
        states.Add(s);
    }

    /// 删除状态  
    public void DeleteState(StateID id)
    {
        // 空值检验  
        if (id == StateID.NullStateID)
        {
            Debug.LogError("FSM ERROR: 状态ID 不可为空ID");
            return;
        }

        // 遍历并删除状态  
        foreach (FSMState state in states)
        {
            if (state.ID == id)
            {
                states.Remove(state);
                return;
            }
        }
        Debug.LogError("FSM ERROR: 无法删除状态 " + id.ToString() + ". 状态列表中不存在");
    }

    /// 执行转换过渡  
    public void PerformTransition(Transition trans)
    {
        // 空值检验  
        if (trans == Transition.NullTransition)
        {
            Debug.LogError("FSM ERROR: 转换不可为空");
            return;
        }

        // 获取当前状态ID  
        StateID id = currentState.GetOutputState(trans);
        if (id == StateID.NullStateID)
        {
            Debug.LogError("FSM ERROR: 状态 " + currentStateID.ToString() + " 不存在目标状态 " +
                           " - 转换: " + trans.ToString());
            return;
        }

        // 更新当前状态ID 与 当前状态        
        currentStateID = id;
        foreach (FSMState state in states)
        {
            if (state.ID == currentStateID)
            {
                // 执行当前状态后处理  
                currentState.DoBeforeLeaving();

                currentState = state;

                // 执行当前状态前处理  
                currentState.DoBeforeEntering();
                break;
            }
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 跟随
/// </summary>
public class FollowPathState : FSMState
{
    private int currentWayPoint;
    private Transform[] waypoints;

    //构造函数装填自己
    public FollowPathState(Transform[] wp)
    {
        waypoints = wp;
        currentWayPoint = 0;
        stateID = StateID.FollowingPath;//别忘设置自己的StateID
    }

    public override void DoBeforeEntering()
    {
        Debug.Log("进入FollowPathState状态之前执行--------");
        currentWayPoint = 0;
    }

    public override void DoBeforeLeaving()
    {
        Debug.Log("离开FollowPathState状态之前执行---------");
    }

    //重写动机方法
    public override void Reason(GameObject player, GameObject npc)
    {
        // If the Player passes less than 15 meters away in front of the NPC
        RaycastHit hit;
        if (Physics.Raycast(npc.transform.position, npc.transform.forward, out hit, 35F))
        {
            Debug.Log("与玩家的距离少于35");
            if (hit.transform.gameObject.tag == "Player")
            {
                Debug.Log("看到玩家 转换状态");
                npc.GetComponent<NPCControl>().SetTransition(Transition.SawPlayer);
            }

        }
    }

    //重写表现方法
    public override void Update(GameObject player, GameObject npc)
    {
        // Follow the path of waypoints
        // Find the direction of the current way point 
        Vector3 vel = npc.GetComponent<Rigidbody>().velocity;
        Vector3 moveDir = waypoints[currentWayPoint].position - npc.transform.position;

        if (moveDir.magnitude < 1)
        {
            currentWayPoint++;
            if (currentWayPoint >= waypoints.Length)
            {
                currentWayPoint = 0;
            }
        }
        else
        {
            vel = moveDir.normalized * npc.GetComponent<NPCControl>().Speed;

            // Rotate towards the waypoint
            npc.transform.rotation = Quaternion.Slerp(npc.transform.rotation,
                                                      Quaternion.LookRotation(moveDir),
                                                      5 * Time.deltaTime);
            npc.transform.eulerAngles = new Vector3(0, npc.transform.eulerAngles.y, 0);

        }

        // Apply the Velocity
        npc.GetComponent<Rigidbody>().velocity = vel;
    }

} // FollowPathState
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 巡逻状态
/// </summary>
public class ChasePlayerState : FSMState
{
    //构造函数装填自己
    public ChasePlayerState()
    {
        stateID = StateID.ChasingPlayer;
      
    }

    public override void DoBeforeEntering()
    {
        Debug.Log("进入ChasePlayerState状态之前执行--------");
    }

    public override void DoBeforeLeaving()
    {
        Debug.Log("离开ChasePlayerState状态之前执行 ---------");
    }

    public override void Reason(GameObject player, GameObject npc)
    {
        // If the player has gone 30 meters away from the NPC, fire LostPlayer transition
        if (Vector3.Distance(npc.transform.position, player.transform.position) >= 3)
            npc.GetComponent<NPCControl>().SetTransition(Transition.LostPlayer);
    }

    public override void Update(GameObject player, GameObject npc)
    {
        // Follow the path of waypoints
        // Find the direction of the player         
        Vector3 vel = npc.GetComponent<Rigidbody>().velocity;
        Vector3 moveDir = player.transform.position - npc.transform.position;

        // Rotate towards the waypoint
        npc.transform.rotation = Quaternion.Slerp(npc.transform.rotation,
                                                  Quaternion.LookRotation(moveDir),
                                                  5 * Time.deltaTime);
        npc.transform.eulerAngles = new Vector3(0, npc.transform.eulerAngles.y, 0);

        vel = moveDir.normalized * 10;

        // Apply the new Velocity
        npc.GetComponent<Rigidbody>().velocity = vel;
    }

} // ChasePlayerState

挂载在敌人物体上的添加状态类:

using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;

[RequireComponent(typeof(Rigidbody))]
public class NPCControl : MonoBehaviour
{
    public GameObject player;
    public Transform[] path;
    private FsmSystem fsm;


    public float Speed = 10f;

    

    public void SetTransition(Transition t)
    {
        //该方法用来改变有限状态机的状体,有限状态机基于当前的状态和通过的过渡状态。
        //如果当前的状态没有用来通过的过度状态,则会抛出错误
        fsm.PerformTransition(t);
    }

    public void Start()
    {
        MakeFSM();

        
    }

    public void FixedUpdate()
    {
        fsm.CurrentState.Reason(player, gameObject);
        fsm.CurrentState.Update(player, gameObject);
    }


    //NPC有两个状态分别是在路径中巡逻和追逐玩家
    //如果他在第一个状态并且SawPlayer 过度状态被出发了,它就转变到ChasePlayer状态
    //如果他在ChasePlayer状态并且LostPlayer状态被触发了,它就转变到FollowPath状态

    private void MakeFSM()//建造状态机
    {
        FollowPathState follow = new FollowPathState(path);
        follow.AddTransition(Transition.SawPlayer, StateID.ChasingPlayer);

        ChasePlayerState chase = new ChasePlayerState();
        chase.AddTransition(Transition.LostPlayer, StateID.FollowingPath);

        fsm = new FsmSystem();
        fsm.AddState(follow);//添加状态到状态机,第一个添加的状态将作为初始状态
        fsm.AddState(chase);
    }
}



猜你喜欢

转载自blog.csdn.net/lizhenxiqnmlgb/article/details/82555539