FSM介绍
FSM 即有限状态机,它是一个状态管理系统,表示一个对象的几种状态在指定条件下转移行为,即随着条件的不断改变内部状态不断地切换。
用途
通常使用FSM去实现一些简单的AI逻辑,对于游戏中的每个对象都可以在其生命周期中分出一些状态,比如一个怪物,他可能在休息,或者巡逻,当玩家出现时,他的状态可能切换为追逐敌人或者攻击敌人,当某些条件成立时,状态机从当前状态转移到下一状态,在不同状态下有不同的任务,所以要使用有限状态机去实现。
优点
使整个状态切换逻辑比较清晰,增强代码的可扩展性,也便于后期维护
具体代码示例
创建脚本命名:StateID.cs 。使用枚举类型 管理所有的状态
public enum StateID {
NullStateID=0,//空(系统默认状态)
IdleState,//空闲状态
PatrolState,//巡逻状态
ChaseState //追逐状态
}
创建脚本命名:Transiton.cs 。使用枚举类型管理所有转换条件
public enum Transiton {
NullTransition=0,//空(系统默认条件)
NoSeePlayer,//没有发现玩家时
SeePlayer,//发现玩家时
GamePause //与玩家距离超出追逐范围时
}
创建脚本命名:FSMState.cs 。状态基类,负责处理一个状态的周期,状态的进入前,状态中,离开状态等。以及状态切换条件的增删。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public abstract class FSMState {
protected FSMSystem fsm;
protected Dictionary<Transiton, StateID> map = new Dictionary<Transiton, StateID>();//存储所有的转换条件和状态
protected StateID stateID;
public StateID ID { get { return stateID; } }
public FSMState(FSMSystem fsm) {
this.fsm = fsm;
}
/// <summary>
/// 添加转换条件
/// </summary>
/// <param name="trans"></param>
/// <param name="id"></param>
public void AddTransition(Transiton trans,StateID id) {
if (trans==Transiton.NullTransition) {
Debug.Log("错误:不能添加空的转换条件");
return;
}
if (map.ContainsKey(trans)) {
Debug.Log("错误:转换条件已存在");
return;
}
if (id==StateID.NullStateID) {
Debug.Log("错误:不能添加空的状态");
return;
}
map.Add(trans,id);
}
/// <summary>
/// 删除转换条件
/// </summary>
/// <param name="trans"></param>
public void DeleteTransition(Transiton trans) {
if (trans==Transiton.NullTransition) {
Debug.Log("错误:禁止删除空的转换条件");
return;
}
if (!map.ContainsKey(trans)) {
Debug.Log("错误:不存在该转换条件"+trans);
return;
}
map.Remove(trans);
}
/// <summary>
/// 通过转换条件获取状态
/// </summary>
/// <param name="trans"></param>
/// <returns></returns>
public StateID GetOutputState(Transiton trans) {
if (map.ContainsKey(trans)) {
return map[trans];
}
return StateID.NullStateID;
}
/// <summary>
/// 进入到状态之前 需要做的事情
/// </summary>
public virtual void DoBeforeEntering() { }
/// <summary>
/// 离开状态之前 需要做的事情
/// </summary>
public virtual void DoBeforeLeaving() { }
/// <summary>
/// 当前状态时需要处理的事情
/// </summary>
/// <param name="player"></param>
/// <param name="npc"></param>
public abstract void Action(GameObject player, GameObject npc);
/// <summary>
/// 检测是否切换状态
/// </summary>
/// <param name="player"></param>
/// <param name="npc"></param>
public abstract void Reason(GameObject player,GameObject npc);
}
创建脚本命名:FSMSystem.cs。用来管理所有的状态(状态的添加,删除,切换,更新等)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
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; } }//提供外界获取当前状态的接口
/// <summary>
/// 构造函数 初始化FSMSystem
/// </summary>
public FSMSystem() {
states = new List<FSMState>();
}
/// <summary>
/// 执行当前状态的处理的事情和检测是否切换状态
/// </summary>
/// <param name="player"></param>
/// <param name="npc"></param>
public void Update(GameObject player,GameObject npc) {
currentState.Action(player,npc);
currentState.Reason(player,npc);
}
/// <summary>
/// 添加状态
/// </summary>
/// <param name="s"></param>
public void AddState(FSMState s) {
if (s==null)
{
Debug.Log(s+"状态不存在");
return;
}
if (states.Count==0) {
states.Add(s);
currentState = s;
currentStateID = s.ID;
return;
}
foreach (FSMState state in states)
{
if (state.ID==s.ID) {
Debug.Log("错误:已存在状态"+state.ID);
return;
}
}
states.Add(s);
}
/// <summary>
/// 删除状态
/// </summary>
/// <param name="id"></param>
public void DeleteState(StateID id) {
if (id==StateID.NullStateID)
{
Debug.Log("NullStateID不可以删除");
}
foreach (FSMState state in states)
{
if (state.ID==id) {
states.Remove(state);
return;
}
}
Debug.Log(id+"该状态不存在");
}
/// <summary>
/// 切换状态
/// </summary>
/// <param name="trans"></param>
public void PerformTransition(Transiton trans) {
if (trans==Transiton.NullTransition)
{
Debug.Log("不能转为NullTransition");
return;
}
StateID id = currentState.GetOutputState(trans);
if (id==StateID.NullStateID)
{
Debug.LogError(currentStateID.ToString()+"不存在状态"+trans.ToString());
return;
}
currentStateID = id;
foreach (FSMState state in states)
{
if (state.ID==currentStateID) {
currentState.DoBeforeLeaving();
currentState = state;
currentState.DoBeforeEntering();
break;
}
}
}
}
创建脚本:IdleState.cs。怪物空闲状态 时的行为和转换条件。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class IdleState : FSMState{
/// <summary>
/// 空闲状态
/// </summary>
/// <param name="fsm"></param>
public IdleState(FSMSystem fsm):base(fsm) {
stateID = StateID.IdleState;
}
public override void Action(GameObject player, GameObject npc)
{
}
/// <summary>
/// 转换条件:2秒后转为巡逻状态
/// </summary>
/// <param name="player"></param>
/// <param name="npc"></param>
public override void Reason(GameObject player, GameObject npc)
{
if (Time.time>2)
{
fsm.PerformTransition(Transiton.NoSeePlayer);
}
}
public override void DoBeforeLeaving()
{
}
}
创建脚本:PatrolState.cs。怪物巡逻状态 时行为和装换条件
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PatrolState : FSMState
{
GameObject[] path;//巡逻路径点
int index = 0;//路径点编号
/// <summary>
/// 巡逻模式
/// </summary>
/// <param name="fsm"></param>
public PatrolState(FSMSystem fsm):base(fsm) {
stateID = StateID.PatrolState;
path = GameObject.FindGameObjectsWithTag("Path");
}
/// <summary>
/// 沿着路径点进行循环移动 巡逻
/// </summary>
/// <param name="player"></param>
/// <param name="npc"></param>
public override void Action(GameObject player, GameObject npc)
{
if (Vector3.Distance(npc.transform.position,path[index].transform.position)<1) {
index++;
index %= path.Length;
}
npc.transform.LookAt(path[index].transform);
npc.transform.Translate(Vector3.forward*3*Time.deltaTime);
}
/// <summary>
/// 切换条件;当玩家与NPC 距离小于3米时,转换为追逐状态
/// </summary>
/// <param name="player"></param>
/// <param name="npc"></param>
public override void Reason(GameObject player, GameObject npc)
{
if (Vector3.Distance(player.transform.position,npc.transform.position)<3) {
fsm.PerformTransition(Transiton.SeePlayer);
}
}
}
创建脚本:ChaseState.cs 。怪物追逐状态 时的行为和转换条件。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ChaseState :FSMState {
/// <summary>
/// 追逐状态
/// </summary>
/// <param name="fsm"></param>
public ChaseState(FSMSystem fsm):base(fsm) {
stateID = StateID.ChaseState;
}
/// <summary>
/// 追逐状态时 npc 面向玩家进行移动
/// </summary>
/// <param name="player"></param>
/// <param name="npc"></param>
public override void Action(GameObject player, GameObject npc)
{
npc.transform.LookAt(player.transform);
npc.transform.Translate(Vector3.forward*2*Time.deltaTime);
}
/// <summary>
/// 转换条件为: 当玩家和NPC距离超出5米时,切换为巡逻模式
/// </summary>
/// <param name="player"></param>
/// <param name="npc"></param>
public override void Reason(GameObject player, GameObject npc)
{
if (Vector3.Distance(npc.transform.position,player.transform.position)>=5)
{
Debug.Log(Vector3.Distance(npc.transform.position, player.transform.position));
fsm.PerformTransition(Transiton.GamePause);
}
}
}
创建脚本:Enemy.cs 怪物实现类,怪物有限状态机初始化和逻辑控制 (脚本挂载 怪物物体上)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : MonoBehaviour {
FSMState state;
FSMSystem fsm;
// Use this for initialization
void Start () {
//添加状态转换条件
fsm = new FSMSystem();
state = new IdleState(fsm);
state.AddTransition(Transiton.NoSeePlayer,StateID.PatrolState);
fsm.AddState(state);
state = new PatrolState(fsm);
state.AddTransition(Transiton.SeePlayer,StateID.ChaseState);
fsm.AddState(state);
state = new ChaseState(fsm);
state.AddTransition(Transiton.GamePause,StateID.IdleState);
fsm.AddState(state);
}
// Update is called once per frame
void Update () {
print("当前的状态是:" + fsm.CurrentState);
fsm.Update(GameController.player,this.gameObject);//使FSM系统运行起来
}
}
创建脚本:GameController.cs。游戏管理类 (脚本挂载摄像机上)
#endregion
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameController : MonoBehaviour {
/// <summary>
/// 游戏玩家
/// </summary>
public static GameObject player;
private GameController() { }
/// <summary>
/// 单例模式
/// </summary>
private static GameController gameController;
public static GameController GetGameController { get { return gameController; } }
private void Awake() {
gameController = this;
player = GameObject.Find("Player");
}
}
创建脚本:Player.cs 控制玩家移动 (脚本挂载到玩家物体(命名为:Player)上)
#endregion
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 键盘控制玩家
/// </summary>
public class Player : MonoBehaviour {
Rigidbody rb;
// Use this for initialization
void Start () {
rb= gameObject.GetComponent<Rigidbody>();
if(rb==null){
gameObject.AddComponent<Rigidbody>();
rb = gameObject.GetComponent<Rigidbody>();
}//获取rigidbody组件
}
// Update is called once per frame
void Update () {
float BallX = Input.GetAxis("Horizontal");
float BallY = Input.GetAxis("Vertical");
rb.AddForce(new Vector3(BallX, 0, BallY) * 5); //根据键盘的输入,来给物体加入各个方向的力,实现移动
}
}