在场景中添加两个游戏物体,一个为玩家并修改其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);
}
}