FSM有限状态机(AI 小案例)
2017-11-09 本文已影响185人
_凉笙
Wiki官网:http://wiki.unity3d.com/index.php/Main_Page
FSM源码地址:http://wiki.unity3d.com/index.php/Finite_State_Machine
在这里我以一个Demo为例演示有限状态机的作用,首先我这里要做的是一个NPC AI的巡逻功能,在这里NPC会有两个状态,一个状态是巡逻的功能,第二个状态是追逐主角的功能,我这里让NPC距离主角如果大于10的距离的时候让NPC进入巡逻状态,而当NPC距离主角如果小于5的时候,我让NPC进入追逐主角的状态。
下面自己来写一个FSM,首先创建一个脚本FSMState,写入一下代码,里面包含一些添加和删除判断转换条件的方法
using System.Collections.Generic;
using UnityEngine;
//有哪些状态转换的条件
public enum Transition
{
NullTransition=0,
SawPlayer,//看到主角
LostPlayer//看不到主角
}
//状态ID,是每一个状态的唯一标识,每一个状态有一个StateID,而且跟其他的状态不可以重复
public enum StateID
{
NullStateID=0,
Patrol,//巡逻状态
Chase//追主角状态
}
public abstract class FSMState {
protected StateID stateID;
public StateID ID { get { return stateID; } }
//存储在不同的条件下可以转换到什么样的状态
protected Dictionary<Transition, StateID> map = new Dictionary<Transition, StateID>();
public FSMSystem fsm;
//添加状态转换条件
public void AddTransition(Transition trans, StateID id)//表示由Transition这个条件可以装换到哪个StateID状态
{
if (trans == Transition.NullTransition || id == StateID.NullStateID)
{
//转换条件或者状态为空
Debug.LogError("Transition or stateid is null!!!");//输出错误
return;
}
//如果已经包含了这个状态
if (map.ContainsKey(trans))
{
//当前状态已经存在
Debug.LogError("State" + id + " is already has transition" + trans);
return;
}
map.Add(trans, id);
}
//删除状态转换条件
public void DeleteTransition(Transition trans)
{
//不包含这个trans转换条件
if (map.ContainsKey(trans) == false)
{
//我们想删除的条件不存在
Debug.LogWarning("The transition " + trans + "you want to delete is not exit in map!!!");
return;
}
map.Remove(trans);
}
//根据传递过来的转换条件,判断是否可发生转换
public StateID GetOutState(Transition trans)
{
//如果包含这个转换条件返回这个状态的StateID否则返回一个空的StateID
if (map.ContainsKey(trans))
{
return map[trans];
}
return StateID.NullStateID;
}
//在进入当前状态之前,需要做的事情 初始化
public virtual void DoBeforeEntering(){ }
//在离开当前状态的时候,需要做的事情 清理
public virtual void DoBeforeLeaving() { }
//在状态机处于当前状态的时候,会一直调用
public abstract void DoUpdata();
}
接着是创建脚本FSMSystem,这是一个状态机管理类,有限状态机的系统管理类,写入一下代码
using System.Collections.Generic;
using UnityEngine;
/// <summary>
///状态机管理类,有限状态机系统管理类
/// </summary>
public class FSMSystem {
//当前状态机下有哪些状态u
private Dictionary<StateID, FSMState> states;
//状态机处于什么状态
private FSMState currentState;
public FSMState CurentState
{
get { return currentState; }
}
public FSMSystem()
{
states = new Dictionary<StateID, FSMState>();
}
//添加状态
public void AddState(FSMState state)
{
if (state==null)
{
Debug.LogError("The state you went to add is null");return;
}
if (states.ContainsKey(state.ID))
{
Debug.LogError("The state "+state.ID+" you want to add has already been added.");return;
}
state.fsm = this;
states.Add(state.ID,state);
}
//从状态机移除状态
public void DeleteState(FSMState state)
{
if (state == null)
{
Debug.LogError("The state you went to Delete is null"); return;
}
if (states.ContainsKey(state.ID)==false)
{
Debug.LogError("The state " + state.ID + " you want to Delete is not exit."); return;
}
states.Remove(state.ID);
}
//控制状态之间的转换
public void PorformTransition(Transition trans)
{
if (trans==Transition.NullTransition)
{
Debug.LogError("NullTransition is not allowed for a real transition.");
}
StateID id = currentState.GetOutState(trans);//判断发生这个条件的时候是否可发生转换
if (id == StateID.NullStateID)
{
Debug.Log("Transition is not tobe happend!没有符合条件的转换");
return;
}
FSMState state;
states.TryGetValue(id, out state);
currentState.DoBeforeLeaving();
currentState = state;
currentState.DoBeforeEntering();
}
//设置默认状态 启动状态机
public void Start(StateID id)
{
FSMState state;
bool isGet= states.TryGetValue(id, out state);
if (isGet)
{
state.DoBeforeEntering();
currentState = state;
}
else
{
Debug.LogError("The state"+id+"is not exit in the fsm.");
}
}
}
接着是NPC的控制管理,里面写入一些初始化方法,在里面添加两个状态的转换条件和状态,我这里有两个状态,一个是巡逻的状态,一个是追主角的状态.
using UnityEngine;
public class NPCController : MonoBehaviour {
private FSMSystem fsm;
public Transform[] waypoints;
private GameObject player;
// Use this for initialization
void Start () {
player = GameObject.FindGameObjectWithTag("Player").gameObject;
InitFSM();
}
//初始化状态机 添加转换条件和状态
void InitFSM()
{
fsm = new FSMSystem();
PatrolState patrolState = new PatrolState(waypoints,this.gameObject, player);
//添加转换条件
patrolState.AddTransition(Transition.SawPlayer,StateID.Chase);//看到主角的时候转换到追主角状态
ChaseState chaseState = new ChaseState(this.gameObject,player);
chaseState.AddTransition(Transition.LostPlayer, StateID.Patrol);//没有看到主角的时候转换到巡逻状态
//添加状态
fsm.AddState(patrolState);
fsm.AddState(chaseState);
fsm.Start(StateID.Patrol);//设置默认状态为巡逻状态
}
private void Update()
{
fsm.CurentState.DoUpdata();
}
}
最后就是两个状态类了,里面写入控制的逻辑,当主角大于NPC10的位置时候,NPC就会在巡逻,进入巡逻状态,当主角位置小于NPC位置5的时候NPC就会追逐主角,进入追逐状态.这两个状态类脚本我分别命名为ChaseState(追逐状态)和PatrolState(巡逻状态),下面直接放上代码
using UnityEngine;
public class PatrolState : FSMState {
private int targetWaypoint;
private Transform[] waypoints;
private GameObject npc;
private Rigidbody npcRgb;
private GameObject player;
public PatrolState(Transform[] wp,GameObject npc,GameObject player)
{
stateID = StateID.Patrol;
waypoints = wp;
targetWaypoint = 0;
this.npc = npc;
this.player = player;
npcRgb = npc.GetComponent<Rigidbody>();
}
public override void DoBeforeEntering()
{
Debug.Log("Enting state "+ID );
}
public override void DoUpdata()
{
CheckTransition();
patrolMove();
}
//检查转换条件
private void CheckTransition()
{
if (Vector3.Distance(player.transform.position, npc.transform.position) < 5)
{
fsm.PorformTransition(Transition.SawPlayer);
}
}
//巡逻功能
private void patrolMove()
{
npcRgb.velocity = npc.transform.forward * 10;
Transform targetTrans = waypoints[targetWaypoint];
Vector3 tagetPosition = targetTrans.position;
tagetPosition.y = npc.transform.position.y;
npc.transform.LookAt(tagetPosition);
if (Vector3.Distance(npc.transform.position, tagetPosition) < 1)
{
targetWaypoint++;
targetWaypoint %= waypoints.Length;
}
}
using UnityEngine;
public class ChaseState : FSMState
{
private GameObject npc;
private Rigidbody npcRgb;
private GameObject player;
public ChaseState(GameObject npc,GameObject player)
{
stateID = StateID.Chase;
this.npc = npc;
npcRgb = npc.GetComponent<Rigidbody>();
this.player = player;
}
public override void DoBeforeEntering()
{
Debug.Log("Enting state " + ID);
}
public override void DoUpdata()
{
CheckTransition();
ChanseMove();
}
private void CheckTransition()
{
if (Vector3.Distance(player.transform.position, npc.transform.position) > 10)
{
fsm.PorformTransition(Transition.LostPlayer);
}
}
//追主角的运动
private void ChanseMove()
{
npcRgb.velocity = npc.transform.forward * 10;
Vector3 targetposition = player.transform.position;
targetposition.y = npc.transform.position.y;
npc.transform.LookAt(targetposition);
}
}
最后在Unity里面创建Player和NPC,我这里以Capsule代替,然后NPC上挂上Rigidbody刚体组件和NPCController脚本,然后再创建四个位置。表示巡逻的目标点。再将这四个目标点赋值给NPCController脚本里面,这样就可以运行了。
image.png
点4分13秒.gif