# 观前提醒
项目会在 GitHub 中开源,链接:https://github.com/Maikire/Unity/tree/main/UnityFramework/FSM
# 有限状态机(Finite State Machine)
有限状态机是一种用来模拟系统行为的数学模型,通常被用于处理离散事件的控制问题。它由一组有限状态、转移函数和输入输出等组件构成,一个有限状态机在任一时刻只能处于一种状态。有限状态机能够对角色的行为进行抽象和编排,使得游戏角色能够根据当前状态自动切换到相应的行为,并且可以通过规定的条件进行状态的转换。这样一来,角色在游戏中的表现更加智能、灵活,同时也增强了游戏的可玩性。
可以将它理解为一个流程控制系统。
# 状态机设计
有限状态机的实现方式有很多种,这主要取决于具体的需求是什么,本文给出一个例子作为参考。
# 需求分析
需求:实现游戏中敌人(NPC)的基本功能,具体功能请看下面的配置表
# 设计
- 根据配置表可以得到三个主要的类
- 条件类(抽象类):逻辑的处理,判断条件是否达成,比如:生命值是否为 0、是否发现目标......
 - 状态类(抽象类):储存条件到状态的映射,处理进入状态、在状态中、退出状态的情况
 - 状态机(脚本):储存所有的状态,切换状态
 
 - 为了方便储存映射,条件类和状态类需要有 ID 做标识
 - 状态机有很多数据,可以将一部分数据封装起来
 - 根据配置表配置状态机
 


# 代码
# 数据类
条件的 ID
namespace AI.FSM  | |
{ | |
    /// <summary> | |
    /// TriggerID | |
    /// </summary> | |
public enum FSMTriggerID  | |
    { | |
        /// <summary> | |
        /// 没有生命 | |
        /// </summary> | |
        NoHealth, | |
        /// <summary> | |
        /// 发现目标 | |
        /// </summary> | |
        FoundTarget, | |
        /// <summary> | |
        /// 到达目标 | |
        /// </summary> | |
        ReachTarget, | |
        /// <summary> | |
        /// 击杀目标 | |
        /// </summary> | |
        KilledTarget, | |
        /// <summary> | |
        /// 超出攻击范围 | |
        /// </summary> | |
        OutOfAttackRange, | |
        /// <summary> | |
        /// 丢失目标 | |
        /// </summary> | |
        LostTarget, | |
        /// <summary> | |
        /// 触发巡逻状态 | |
        /// </summary> | |
        Patrol, | |
        /// <summary> | |
        /// 完成巡逻 | |
        /// </summary> | |
        CompletePatrol, | |
        /// <summary> | |
        /// 复活 | |
        /// </summary> | |
        Revive, | |
    } | |
} | 
状态的 ID
using UnityEngine;  | |
namespace AI.FSM  | |
{ | |
    /// <summary> | |
    /// StateID | |
    /// </summary> | |
public enum FSMStateID  | |
    { | |
[Tooltip("默认")]  | |
        Default, | |
[Tooltip("死亡")]  | |
        Dead, | |
[Tooltip("闲置")]  | |
        Idle, | |
[Tooltip("追逐")]  | |
        Pursuit, | |
[Tooltip("攻击")]  | |
        Attack, | |
[Tooltip("巡逻")]  | |
        Patrol, | |
    } | |
} | 
状态机脚本的数据类
using System;  | |
using UnityEngine;  | |
namespace AI.FSM  | |
{ | |
[Serializable]  | |
    /// <summary> | |
    /// 有限状态机数据类 | |
    /// </summary> | |
public class FSMData  | |
    { | |
[Tooltip("状态配置表(在StreamingAssets中的路径)")]  | |
public string FSMConfigPath;  | |
[Tooltip("移动速度")]  | |
public float MoveSpeed;  | |
[Tooltip("视野距离")]  | |
public float ViewDistance;  | |
[Tooltip("视野角度")]  | |
public float ViewAngle;  | |
[Tooltip("可以被发现的目标的标签")]  | |
public string[] TargetTags = { "Player" };  | |
[Tooltip("进入攻击状态的距离")]  | |
public float AttackStateDistance;  | |
[Tooltip("攻击时间间隔")]  | |
public float AttackTimeInterval;  | |
[Tooltip("攻击状态下释放技能的距离的偏移量")]  | |
public float SkillDistanceEffect;  | |
[Tooltip("触发巡逻的概率,每秒判定一次,取值范围:0-1")]  | |
public float PatrolProbability;  | |
[Tooltip("判断是否到达路点的偏移量")]  | |
public float PatrolEffect;  | |
[Tooltip("寻路模式")]  | |
public FSMPatrolModes PatrolMode;  | |
[Tooltip("巡逻的路点")]  | |
public Transform[] WayPoints;  | |
[HideInInspector]  | |
[Tooltip("完成巡逻")]  | |
public bool CompletePatrol;  | |
    } | |
} | 
自动寻路的模式
using System;  | |
using UnityEngine;  | |
namespace AI.FSM  | |
{ | |
[Serializable]  | |
    /// <summary> | |
    /// 寻路模式 | |
    /// </summary> | |
public enum FSMPatrolModes  | |
    { | |
[Tooltip("无(在巡逻状态下,触发 完成巡逻)")]  | |
        None, | |
[Tooltip("单次")]  | |
        Once, | |
[Tooltip("循环")]  | |
        Loop, | |
[Tooltip("往返")]  | |
        RoundTrip, | |
    } | |
} | 
# 条件类
- ID
 - 提供用于判断是否满足条件的方法
 
实现类必须给 ID 赋值
namespace AI.FSM  | |
{ | |
    /// <summary> | |
    /// FSM 条件类 | |
    /// </summary> | |
public abstract class FSMTrigger  | |
    { | |
        /// <summary> | |
        /// TriggerID | |
        /// 子类必须给 TriggerID 赋值 | |
        /// </summary> | |
public abstract FSMTriggerID TriggerID { get; }  | |
public FSMTrigger()  | |
        { | |
Init();  | |
        } | |
        /// <summary> | |
        /// 初始化 | |
        /// </summary> | |
public virtual void Init() { }  | |
        /// <summary> | |
        /// 是否满足条件 | |
        /// </summary> | |
        /// <returns></returns> | |
public abstract bool HandleTrigger(FSMBase fsm);  | |
    } | |
} | 
# 状态类
- ID
 - 储存某状态拥有的所有的条件
 - 储存条件到状态的映射表
 - 提供切换状态的方法
 - 提供进入状态、在状态中、退出状态的方法
 - 添加条件对象的时候会用到 创建工厂(FSMCreateFactory),这个会在后面讲到
 
代码如下
using System.Collections.Generic;  | |
namespace AI.FSM  | |
{ | |
    /// <summary> | |
    /// FSM 状态类 | |
    /// </summary> | |
public abstract class FSMState  | |
    { | |
        /// <summary> | |
        /// StateID | |
        /// 子类必须给 StateID 赋值 | |
        /// </summary> | |
public abstract FSMStateID StateID { get; }  | |
        /// <summary> | |
        ///true: 搜索目标的时候忽略墙体 | |
        /// </summary> | |
public virtual bool IgnoreWalls { get => false; }  | |
        /// <summary> | |
        /// 条件列表 | |
        /// </summary> | |
private List<FSMTrigger> Triggers;  | |
        /// <summary> | |
        /// 映射表:条件 —— 目标状态 | |
        /// </summary> | |
private Dictionary<FSMTriggerID, FSMStateID> Map;  | |
public FSMState()  | |
        { | |
Map = new Dictionary<FSMTriggerID, FSMStateID>();  | |
Triggers = new List<FSMTrigger>();  | |
Init();  | |
        } | |
        /// <summary> | |
        /// 初始化 | |
        /// </summary> | |
public virtual void Init() { }  | |
        /// <summary> | |
        /// 添加映射、添加 FSMTrigger 对象 | |
        /// 由状态机调用 | |
        /// </summary> | |
        /// <param name="triggerID"></param> | |
        /// <param name="stateID"></param> | |
public void Add(FSMTriggerID triggerID, FSMStateID stateID)  | |
        { | |
Map.Add(triggerID, stateID);  | |
Triggers.Add(FSMCreateFactory.CreateTrigger(triggerID));  | |
        } | |
        /// <summary> | |
        /// 判断当前状态的条件是否满足 | |
        /// 如果条件满足,则切换状态 | |
        /// </summary> | |
public void JudgeState(FSMBase fsm)  | |
        { | |
foreach (FSMTrigger trigger in Triggers)  | |
            { | |
if (trigger.HandleTrigger(fsm))  | |
                { | |
fsm.ChangeActiveState(Map[trigger.TriggerID]);  | |
return;  | |
                } | |
            } | |
        } | |
        /// <summary> | |
        /// 进入状态 | |
        /// </summary> | |
        /// <param name="fsm"> 状态机 & lt;/param> | |
public virtual void EnterState(FSMBase fsm) { }  | |
        /// <summary> | |
        /// 在状态中 | |
        /// </summary> | |
        /// <param name="fsm"> 状态机 & lt;/param> | |
public virtual void ActionState(FSMBase fsm) { }  | |
        /// <summary> | |
        /// 退出状态 | |
        /// </summary> | |
        /// <param name="fsm"> 状态机 & lt;/param> | |
public virtual void ExitState(FSMBase fsm) { }  | |
    } | |
} | 
# 状态机脚本
- 状态机会用到角色系统、技能系统、动画系统,本文借用这个角色框架和这个技能框架为例
 - 储存所有的状态
 - 切换状态
 - 为状态和条件提供成员
 - 利用变换组件助手类持续搜索目标
 - 为状态提供基础的方法,例如:自动寻路
 - 利用创建工厂(FSMCreateFactory)和配置文件读取工厂(FSMConfigReaderFactory)处理配置表,这两个会在后面讲到
 
代码如下
using Character;  | |
using SkillSystem;  | |
using Common;  | |
using System;  | |
using System.Collections.Generic;  | |
using UnityEngine;  | |
using UnityEngine.AI;  | |
namespace AI.FSM  | |
{ | |
[RequireComponent(typeof(NavMeshAgent), typeof(CharacterStatus), typeof(CharacterSkillSystemNPC))]  | |
    /// <summary> | |
    /// 状态机 | |
    /// </summary> | |
public class FSMBase : MonoBehaviour  | |
    { | |
        // 本文使用的是手动填写状态机数据配置 | |
        // 想要通过配置表自动填写数据,只需要额外写一个类实现读取和配置数据,然后在 ConfigFSM () 中调用即可 | |
[Tooltip("默认状态")]  | |
public FSMStateID DefaultStateID = FSMStateID.Idle;  | |
[Tooltip("有限状态机数据")]  | |
public FSMData Data;  | |
        /// <summary> | |
        /// 状态表 | |
        /// </summary> | |
private List<FSMState> States;  | |
        /// <summary> | |
        /// 当前状态 | |
        /// </summary> | |
private FSMState CurrentState;  | |
        /// <summary> | |
        /// 导航 | |
        /// </summary> | |
private NavMeshAgent Navigation;  | |
        #region 为状态和条件提供的成员 | |
[HideInInspector]  | |
[Tooltip("角色的信息")]  | |
public CharacterStatus Character;  | |
[HideInInspector]  | |
[Tooltip("角色的动画")]  | |
public Animator Anim;  | |
[HideInInspector]  | |
[Tooltip("发现的目标")]  | |
public Transform FoundTarget;  | |
[HideInInspector]  | |
[Tooltip("技能系统")]  | |
public CharacterSkillSystemNPC NPCSkillSystem;  | |
[HideInInspector]  | |
[Tooltip("当前的技能")]  | |
public SkillData CurrentSkill;  | |
        #endregion | |
private void Awake()  | |
        { | |
Character = this.GetComponent<CharacterStatus>();  | |
Anim = this.GetComponentInChildren<Animator>();  | |
Navigation = GetComponent<NavMeshAgent>();  | |
NPCSkillSystem = this.GetComponent<CharacterSkillSystemNPC>();  | |
        } | |
private void Start()  | |
        { | |
SetParameters();  | |
ConfigFSM();  | |
InitDefaultState();  | |
        } | |
private void Update()  | |
        { | |
SearchTarget(); // 搜索目标  | |
CurrentState.JudgeState(this); // 判断当前状态的条件  | |
CurrentState.ActionState(this); // 执行当前状态的逻辑  | |
        } | |
        /// <summary> | |
        /// 设置参数 | |
        /// </summary> | |
private void SetParameters()  | |
        { | |
FoundTarget = null;  | |
CurrentSkill = null;  | |
        } | |
        /// <summary> | |
        /// 配置状态机 | |
        /// </summary> | |
private void ConfigFSM()  | |
        { | |
States = new List<FSMState>();  | |
var map = FSMConfigReaderFactory.GetConfig(Data.FSMConfigPath);  | |
foreach (var state in map)  | |
            { | |
FSMState tempState = FSMCreateFactory.CreateState((FSMStateID)Enum.Parse(typeof(FSMStateID), state.Key));  | |
States.Add(tempState);  | |
foreach (var item in state.Value)  | |
                { | |
FSMTriggerID tempTriggerID = (FSMTriggerID)Enum.Parse(typeof(FSMTriggerID), item.Key);  | |
FSMStateID tempStateID = (FSMStateID)Enum.Parse(typeof(FSMStateID), item.Value);  | |
tempState.Add(tempTriggerID, tempStateID);  | |
                } | |
            } | |
        } | |
        /// <summary> | |
        /// 初始化默认状态 | |
        /// </summary> | |
private void InitDefaultState()  | |
        { | |
CurrentState = States.Find(s => s.StateID == DefaultStateID);  | |
CurrentState.EnterState(this);  | |
        } | |
        /// <summary> | |
        /// 改变状态 | |
        /// </summary> | |
        /// <param name="stateID"></param> | |
public void ChangeActiveState(FSMStateID stateID)  | |
        { | |
CurrentState.ExitState(this);  | |
if (stateID == FSMStateID.Default)  | |
            { | |
CurrentState = States.Find(s => s.StateID == DefaultStateID);  | |
            } | |
            else | |
            { | |
CurrentState = States.Find(s => s.StateID == stateID);  | |
            } | |
CurrentState.EnterState(this);  | |
        } | |
        /// <summary> | |
        /// 搜索目标 | |
        /// </summary> | |
public void SearchTarget()  | |
        { | |
            // 查找目标 | |
List<Transform> targets = this.transform.SelectTargets(Data.ViewDistance, Data.ViewAngle, Data.TargetTags);  | |
            // 选取活动的目标 | |
targets = targets.FindAll(t => t.GetComponent<CharacterStatus>().HP > 0);  | |
if (targets.Count == 0)  | |
            { | |
FoundTarget = null;  | |
return;  | |
            } | |
            // 是否忽略墙体 | |
if (CurrentState.IgnoreWalls)  | |
            { | |
FoundTarget = targets[0];  | |
return;  | |
            } | |
            // 利用射线判断是否有墙体 | |
RaycastHit hit;  | |
Physics.Raycast(this.transform.position, targets[0].position + Vector3.up * 0.5f - this.transform.position, out hit, Data.ViewDistance);  | |
if (hit.transform != null && hit.transform.name == targets[0].name)  | |
            { | |
FoundTarget = targets[0];  | |
            } | |
            else | |
            { | |
FoundTarget = null;  | |
            } | |
        } | |
        /// <summary> | |
        /// 自动寻路 | |
        /// </summary> | |
        /// <param name="targetPosition"> 目标位置 & lt;/param> | |
        /// <param name="stoppingDistance"> 停止移动的距离 & lt;/param> | |
        /// <param name="speed"> 移动速度 & lt;/param> | |
public void MoveToTarget(Vector3 targetPosition, float stoppingDistance, float speed)  | |
        { | |
Navigation.stoppingDistance = stoppingDistance;  | |
Navigation.speed = speed;  | |
Navigation.SetDestination(targetPosition);  | |
        } | |
    } | |
} | 
# 读取配置表
- 利用配置文件读取器处理配置表
 - 储存配置表的内容,一个配置表只需要解析一次
 
FSM 配置文件读取器
using Common;  | |
using System.Collections.Generic;  | |
namespace AI.FSM  | |
{ | |
    /// <summary> | |
    /// FSM 配置文件读取器 | |
    /// </summary> | |
public class FSMConfigReader  | |
    { | |
        /// <summary> | |
        /// 大字典:状态 -- 映射 | |
        /// 小字典:条件编号 -- 状态编号 | |
        /// </summary> | |
public Dictionary<string, Dictionary<string, string>> Map  | |
        { | |
get;  | |
private set;  | |
        } | |
        /// <summary> | |
        /// 大字典的键值 | |
        /// </summary> | |
private string MainKey;  | |
public FSMConfigReader(string path)  | |
        { | |
Map = new Dictionary<string, Dictionary<string, string>>();  | |
string configfile = ConfigReader.GetConfig(path);  | |
ConfigReader.ReadConfig(configfile, BulidLine);  | |
        } | |
        /// <summary> | |
        /// 读取一行 | |
        /// </summary> | |
        /// <param name="line"></param> | |
private void BulidLine(string line)  | |
        { | |
            // 去除空白 | |
line = line.Trim();  | |
            // 判断字符串是否为空,有两种方法 | |
            //if (line == "" || line == null) return; | |
if (string.IsNullOrEmpty(line)) return;  | |
if (line.StartsWith("["))  | |
            { | |
MainKey = line.Substring(1, line.Length - 2);  | |
Map.Add(MainKey, new Dictionary<string, string>());  | |
            } | |
            else | |
            { | |
string[] values = line.Split("->");  | |
Map[MainKey].Add(values[0], values[1]);  | |
            } | |
        } | |
    } | |
} | 
FSM 配置文件读取工厂
using System.Collections.Generic;  | |
namespace AI.FSM  | |
{ | |
    /// <summary> | |
    /// FSM 配置文件读取工厂 | |
    /// </summary> | |
public class FSMConfigReaderFactory  | |
    { | |
        /// <summary> | |
        /// 储存已生成的对象,循环利用 | |
        /// </summary> | |
private static Dictionary<string, Dictionary<string, Dictionary<string, string>>> Cache = new Dictionary<string, Dictionary<string, Dictionary<string, string>>>();  | |
        /// <summary> | |
        /// 获取配置表 | |
        /// </summary> | |
        /// <param name="path"> 路径 & lt;/param> | |
        /// <returns></returns> | |
public static Dictionary<string, Dictionary<string, string>> GetConfig(string path)  | |
        { | |
if (Cache.ContainsKey(path))  | |
            { | |
return Cache[path];  | |
            } | |
            else | |
            { | |
FSMConfigReader configReader = new FSMConfigReader(path);  | |
Cache.Add(path, configReader.Map);  | |
return configReader.Map;  | |
            } | |
        } | |
    } | |
} | 
# 创建工厂
- 根据指定的命名规则,使用反射创建对象(在测试部分有配置示例)
 - 条件类的对象只涉及各种情况的触发判定,所以可以循环利用
 
代码如下
using System;  | |
using System.Collections.Generic;  | |
namespace AI.FSM  | |
{ | |
    /// <summary> | |
    /// FSM 创建工厂 | |
    /// 创建 FSMTrigger 和 FSMState 对象 | |
    /// </summary> | |
public class FSMCreateFactory  | |
    { | |
        /// <summary> | |
        /// 储存已生成的 FSMTrigger 对象,循环利用 | |
        /// </summary> | |
private static Dictionary<string, FSMTrigger> Cache = new Dictionary<string, FSMTrigger>();  | |
        /// <summary> | |
        /// 创建 FSMTrigger 对象 | |
        /// </summary> | |
        /// <param name="triggerID">ID</param> | |
        /// <returns></returns> | |
public static FSMTrigger CreateTrigger(FSMTriggerID triggerID)  | |
        { | |
            // 命名规则:AI.FSM. + triggerID + Trigger | |
string className = String.Format("AI.FSM.{0}Trigger", triggerID);  | |
if (Cache.ContainsKey(className))  | |
            { | |
return Cache[className];  | |
            } | |
            else | |
            { | |
Type type = Type.GetType(className);  | |
FSMTrigger temp = Activator.CreateInstance(type) as FSMTrigger;  | |
Cache.Add(className, temp);  | |
return temp;  | |
            } | |
        } | |
        /// <summary> | |
        /// 创建 FSMState 对象 | |
        /// </summary> | |
        /// <param name="stateID">ID</param> | |
        /// <returns></returns> | |
public static FSMState CreateState(FSMStateID stateID)  | |
        { | |
            // 命名规则:AI.FSM. + stateID + State | |
Type type = Type.GetType(String.Format("AI.FSM.{0}State", stateID));  | |
object temp = Activator.CreateInstance(type);  | |
return temp as FSMState;  | |
        } | |
    } | |
} | 
# 具体的条件类
- 继承抽象类,处理对应的逻辑
 
完成巡逻
namespace AI.FSM  | |
{ | |
    /// <summary> | |
    /// 完成巡逻 | |
    /// </summary> | |
public class CompletePatrolTrigger : FSMTrigger  | |
    { | |
public override FSMTriggerID TriggerID => FSMTriggerID.CompletePatrol;  | |
public override bool HandleTrigger(FSMBase fsm)  | |
        { | |
return fsm.Data.CompletePatrol;  | |
        } | |
    } | |
} | 
发现目标
namespace AI.FSM  | |
{ | |
    /// <summary> | |
    /// 发现目标 | |
    /// </summary> | |
public class FoundTargetTrigger : FSMTrigger  | |
    { | |
public override FSMTriggerID TriggerID => FSMTriggerID.FoundTarget;  | |
public override bool HandleTrigger(FSMBase fsm)  | |
        { | |
return fsm.FoundTarget != null;  | |
        } | |
    } | |
} | 
击杀目标
using Character;  | |
namespace AI.FSM  | |
{ | |
    /// <summary> | |
    /// 击杀目标 | |
    /// </summary> | |
public class KilledTargetTrigger : FSMTrigger  | |
    { | |
public override FSMTriggerID TriggerID => FSMTriggerID.KilledTarget;  | |
public override bool HandleTrigger(FSMBase fsm)  | |
        { | |
return fsm.FoundTarget.GetComponent<CharacterStatus>().HP <= 0;  | |
        } | |
    } | |
} | 
丢失目标
namespace AI.FSM  | |
{ | |
    /// <summary> | |
    /// 丢失目标 | |
    /// </summary> | |
public class LostTargetTrigger : FSMTrigger  | |
    { | |
public override FSMTriggerID TriggerID => FSMTriggerID.LostTarget;  | |
public override bool HandleTrigger(FSMBase fsm)  | |
        { | |
return fsm.FoundTarget == null;  | |
        } | |
    } | |
} | 
没有生命
namespace AI.FSM  | |
{ | |
    /// <summary> | |
    /// 没有生命 | |
    /// </summary> | |
public class NoHealthTrigger : FSMTrigger  | |
    { | |
public override FSMTriggerID TriggerID => FSMTriggerID.NoHealth;  | |
public override bool HandleTrigger(FSMBase fsm)  | |
        { | |
return fsm.Character.HP <= 0;  | |
        } | |
    } | |
} | 
巡逻
using UnityEngine;  | |
namespace AI.FSM  | |
{ | |
    /// <summary> | |
    /// 巡逻 | |
    /// </summary> | |
public class PatrolTrigger : FSMTrigger  | |
    { | |
        /// <summary> | |
        /// 计时器 | |
        /// </summary> | |
private float Timer;  | |
public override FSMTriggerID TriggerID => FSMTriggerID.Patrol;  | |
public override void Init()  | |
        { | |
Timer = 0;  | |
        } | |
public override bool HandleTrigger(FSMBase fsm)  | |
        {             | |
if (fsm.Data.PatrolMode == FSMPatrolModes.None)  | |
            { | |
return false;  | |
            } | |
Timer += Time.deltaTime;  | |
if (Timer >= 1)  | |
            { | |
if (Random.Range(0, 1f) <= fsm.Data.PatrolProbability)  | |
                { | |
Timer = 0;  | |
return true;  | |
                } | |
Timer = 0;  | |
            } | |
return false;  | |
        } | |
    } | |
} | 
到达目标
using UnityEngine;  | |
namespace AI.FSM  | |
{ | |
    /// <summary> | |
    /// 到达目标 | |
    /// </summary> | |
public class ReachTargetTrigger : FSMTrigger  | |
    { | |
public override FSMTriggerID TriggerID => FSMTriggerID.ReachTarget;  | |
public override bool HandleTrigger(FSMBase fsm)  | |
        { | |
return Vector3.Distance(fsm.transform.position, fsm.FoundTarget.position) <= fsm.Data.AttackStateDistance;  | |
        } | |
    } | |
} | 
复活
namespace AI.FSM  | |
{ | |
    /// <summary> | |
    /// 复活 | |
    /// </summary> | |
public class ReviveTrigger : FSMTrigger  | |
    { | |
public override FSMTriggerID TriggerID => FSMTriggerID.Revive;  | |
public override bool HandleTrigger(FSMBase fsm)  | |
        { | |
return fsm.Character.HP > 0;  | |
        } | |
    } | |
} | 
# 具体的状态类
- 继承抽象类,处理对应的逻辑
 
闲置状态
namespace AI.FSM  | |
{ | |
    /// <summary> | |
    /// 闲置状态 | |
    /// </summary> | |
public class IdleState : FSMState  | |
    { | |
public override FSMStateID StateID => FSMStateID.Idle;  | |
public override bool IgnoreWalls => false;  | |
public override void EnterState(FSMBase fsm)  | |
        { | |
base.EnterState(fsm);  | |
fsm.Anim.SetBool(fsm.Character.PlayerAnimationParameter.Revive, true);  | |
fsm.Anim.SetBool(fsm.Character.PlayerAnimationParameter.Idle, true);  | |
        } | |
    } | |
} | 
死亡状态
namespace AI.FSM  | |
{ | |
    /// <summary> | |
    /// 死亡状态 | |
    /// </summary> | |
public class DeadState : FSMState  | |
    { | |
public override FSMStateID StateID => FSMStateID.Dead;  | |
public override bool IgnoreWalls => false;  | |
    } | |
} | 
追逐状态
namespace AI.FSM  | |
{ | |
    /// <summary> | |
    /// 追逐状态 | |
    /// </summary> | |
public class PursuitState : FSMState  | |
    { | |
public override FSMStateID StateID => FSMStateID.Pursuit;  | |
public override bool IgnoreWalls => true;  | |
public override void EnterState(FSMBase fsm)  | |
        { | |
base.EnterState(fsm);  | |
fsm.Anim.SetBool(fsm.Character.PlayerAnimationParameter.Run, true);  | |
        } | |
public override void ActionState(FSMBase fsm)  | |
        { | |
base.ActionState(fsm);  | |
fsm.MoveToTarget(fsm.FoundTarget.position, fsm.Data.AttackStateDistance, fsm.Data.MoveSpeed);  | |
        } | |
public override void ExitState(FSMBase fsm)  | |
        { | |
base.ExitState(fsm);  | |
fsm.MoveToTarget(fsm.transform.position, 1, fsm.Data.MoveSpeed);  | |
        } | |
    } | |
} | 
巡逻状态
using System;  | |
using UnityEngine;  | |
namespace AI.FSM  | |
{ | |
    /// <summary> | |
    /// 巡逻状态 | |
    /// </summary> | |
public class PatrolState : FSMState  | |
    { | |
public override FSMStateID StateID => FSMStateID.Patrol;  | |
public override bool IgnoreWalls => false;  | |
        /// <summary> | |
        /// 路点数组的索引 | |
        /// </summary> | |
private int Index;  | |
public override void Init()  | |
        { | |
Index = 0;  | |
        } | |
public override void EnterState(FSMBase fsm)  | |
        { | |
base.EnterState(fsm);  | |
fsm.Data.CompletePatrol = false;  | |
        } | |
public override void ActionState(FSMBase fsm)  | |
        { | |
base.ActionState(fsm);  | |
switch (fsm.Data.PatrolMode)  | |
            { | |
case FSMPatrolModes.None:  | |
NoneMode(fsm);  | |
break;  | |
case FSMPatrolModes.Once:  | |
OnceMode(fsm);  | |
break;  | |
case FSMPatrolModes.Loop:  | |
LoopMode(fsm);  | |
break;  | |
case FSMPatrolModes.RoundTrip:  | |
RoundTripMode(fsm);  | |
break;  | |
            } | |
        } | |
public override void ExitState(FSMBase fsm)  | |
        { | |
base.ExitState(fsm);  | |
fsm.MoveToTarget(fsm.transform.position, 1, fsm.Data.MoveSpeed);  | |
        } | |
        /// <summary> | |
        /// 无巡逻模式 | |
        /// </summary> | |
private void NoneMode(FSMBase fsm)  | |
        { | |
fsm.Data.CompletePatrol = true;  | |
        } | |
        /// <summary> | |
        /// 单次模式 | |
        /// </summary> | |
private void OnceMode(FSMBase fsm)  | |
        { | |
if (Vector3.Distance(fsm.transform.position, fsm.Data.WayPoints[Index].position) <= fsm.Data.PatrolEffect)  | |
            { | |
Index++;  | |
            } | |
if (Index >= fsm.Data.WayPoints.Length)  | |
            { | |
fsm.Data.CompletePatrol = true;  | |
return;  | |
            } | |
fsm.MoveToTarget(fsm.Data.WayPoints[Index].position, 0, fsm.Data.MoveSpeed);  | |
        } | |
        /// <summary> | |
        /// 循环模式 | |
        /// </summary> | |
private void LoopMode(FSMBase fsm)  | |
        { | |
if (Vector3.Distance(fsm.transform.position, fsm.Data.WayPoints[Index].position) <= fsm.Data.PatrolEffect)  | |
            { | |
Index = (Index + 1) % fsm.Data.WayPoints.Length;  | |
            } | |
fsm.MoveToTarget(fsm.Data.WayPoints[Index].position, 0, fsm.Data.MoveSpeed);  | |
        } | |
        /// <summary> | |
        /// 往返模式 | |
        /// </summary> | |
private void RoundTripMode(FSMBase fsm)  | |
        { | |
if (Vector3.Distance(fsm.transform.position, fsm.Data.WayPoints[Index].position) <= fsm.Data.PatrolEffect)  | |
            { | |
if (Index >= fsm.Data.WayPoints.Length - 1)  | |
                { | |
Array.Reverse(fsm.Data.WayPoints);  | |
Index++;  | |
                } | |
Index = (Index + 1) % fsm.Data.WayPoints.Length;  | |
            } | |
fsm.MoveToTarget(fsm.Data.WayPoints[Index].position, 0, fsm.Data.MoveSpeed);  | |
        } | |
    } | |
} | 
攻击状态
using Common;  | |
using UnityEngine;  | |
namespace AI.FSM  | |
{ | |
    /// <summary> | |
    /// 攻击状态 | |
    /// </summary> | |
public class AttackState : FSMState  | |
    { | |
public override FSMStateID StateID => FSMStateID.Attack;  | |
public override bool IgnoreWalls => true;  | |
        /// <summary> | |
        /// 计时器 | |
        /// </summary> | |
private float Timer;  | |
        /// <summary> | |
        /// 用于判断攻击距离 | |
        /// </summary> | |
private float JudgeAttackDistance;  | |
public override void EnterState(FSMBase fsm)  | |
        { | |
base.EnterState(fsm);  | |
Timer = 0;  | |
JudgeAttackDistance = 0;  | |
GetSkill(fsm);  | |
        } | |
public override void ActionState(FSMBase fsm)  | |
        { | |
base.ActionState(fsm);  | |
Attack(fsm);  | |
        } | |
public override void ExitState(FSMBase fsm)  | |
        { | |
base.ExitState(fsm);  | |
fsm.MoveToTarget(fsm.transform.position, 1, fsm.Data.MoveSpeed);  | |
        } | |
        /// <summary> | |
        /// 获取技能 | |
        /// </summary> | |
        /// <param name="fsm"></param> | |
private void GetSkill(FSMBase fsm)  | |
        { | |
fsm.CurrentSkill = fsm.NPCSkillSystem.GetRandomSkillData();  | |
if (fsm.CurrentSkill.MoveDistance != 0)  | |
            { | |
JudgeAttackDistance = fsm.CurrentSkill.MoveDistance;  | |
            } | |
            else | |
            { | |
JudgeAttackDistance = fsm.CurrentSkill.AttackDistance;  | |
            } | |
        } | |
        /// <summary> | |
        /// 攻击 | |
        /// </summary> | |
        /// <param name="fsm"></param> | |
private void Attack(FSMBase fsm)  | |
        { | |
            // 看向目标 | |
fsm.transform.LookAtTarget(fsm.FoundTarget);  | |
Timer += Time.deltaTime;  | |
if (Timer > fsm.Data.AttackTimeInterval)  | |
            { | |
if (fsm.CurrentSkill != null && Vector3.Distance(fsm.transform.position, fsm.FoundTarget.position) <= JudgeAttackDistance)  | |
                { | |
                    // 放技能 | |
fsm.NPCSkillSystem.UseSkill(fsm.CurrentSkill.SkillID);  | |
SetPosition(fsm);  | |
GetSkill(fsm);  | |
Timer = 0;  | |
                } | |
                else | |
                { | |
                    // 靠近目标,达到能释放技能的位置 | |
fsm.MoveToTarget(fsm.FoundTarget.position, JudgeAttackDistance * fsm.Data.SkillDistanceEffect, fsm.Data.MoveSpeed);  | |
                } | |
            } | |
        } | |
        /// <summary> | |
        /// 设置释放位置 | |
        /// </summary> | |
        /// <param name="fsm"></param> | |
private void SetPosition(FSMBase fsm)  | |
        { | |
if (fsm.NPCSkillSystem.Skill != null)  | |
            { | |
Transform owner = fsm.NPCSkillSystem.Skill.Owner.transform;  | |
fsm.NPCSkillSystem.SetPosition(owner.position, owner.rotation);  | |
            } | |
        } | |
    } | |
} | 
# 类图
最后给出类图:
# 测试
配置
[Idle]  | |
NoHealth->Dead  | |
FoundTarget->Pursuit  | |
Patrol->Patrol  | |
[Dead]  | |
Revive->Default  | |
[Pursuit]  | |
NoHealth->Dead  | |
LostTarget->Default  | |
FoundTarget->Attack  | |
[Attack]  | |
NoHealth->Dead  | |
LostTarget->Default  | |
KilledTarget->Default  | |
[Patrol]  | |
NoHealth->Dead  | |
CompletePatrol->Default  | |
FoundTarget->Pursuit  | 
添加组件
设置参数
结果演示