# 观前提醒

项目会在 GitHub 中开源,链接:https://github.com/Maikire/Unity/tree/main/UnityFramework/FSM

# 有限状态机(Finite State Machine)

有限状态机是一种用来模拟系统行为的数学模型,通常被用于处理离散事件的控制问题。它由一组有限状态、转移函数和输入输出等组件构成,一个有限状态机在任一时刻只能处于一种状态。有限状态机能够对角色的行为进行抽象和编排,使得游戏角色能够根据当前状态自动切换到相应的行为,并且可以通过规定的条件进行状态的转换。这样一来,角色在游戏中的表现更加智能、灵活,同时也增强了游戏的可玩性。
可以将它理解为一个流程控制系统。

# 状态机设计

有限状态机的实现方式有很多种,这主要取决于具体的需求是什么,本文给出一个例子作为参考。

# 需求分析

需求:实现游戏中敌人(NPC)的基本功能,具体功能请看下面的配置表
img

# 设计

  • 根据配置表可以得到三个主要的类
    • 条件类(抽象类):逻辑的处理,判断条件是否达成,比如:生命值是否为 0、是否发现目标......
    • 状态类(抽象类):储存条件到状态的映射,处理进入状态、在状态中、退出状态的情况
    • 状态机(脚本):储存所有的状态,切换状态
  • 为了方便储存映射,条件类和状态类需要有 ID 做标识
  • 状态机有很多数据,可以将一部分数据封装起来
  • 根据配置表配置状态机

img
img

# 代码

# 数据类

条件的 ID

FSMTriggerID
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

FSMStateID
using UnityEngine;
namespace AI.FSM
{
    /// <summary>
    /// StateID
    /// </summary>
    public enum FSMStateID
    {
        [Tooltip("默认")]
        Default,
        [Tooltip("死亡")]
        Dead,
        [Tooltip("闲置")]
        Idle,
        [Tooltip("追逐")]
        Pursuit,
        [Tooltip("攻击")]
        Attack,
        [Tooltip("巡逻")]
        Patrol,
    }
}

状态机脚本的数据类

FSMData
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;
    }
}

自动寻路的模式

FSMPatrolModes
using System;
using UnityEngine;
namespace AI.FSM
{
    [Serializable]
    /// <summary>
    /// 寻路模式
    /// </summary>
    public enum FSMPatrolModes
    {
        [Tooltip("无(在巡逻状态下,触发 完成巡逻)")]
        None,
        [Tooltip("单次")]
        Once,
        [Tooltip("循环")]
        Loop,
        [Tooltip("往返")]
        RoundTrip,
    }
}

# 条件类

  • ID
  • 提供用于判断是否满足条件的方法

实现类必须给 ID 赋值

FSMTrigger
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),这个会在后面讲到

代码如下

FSMState
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)处理配置表,这两个会在后面讲到

代码如下

FSMBase
using ARPGDemo.Character;
using ARPGDemo.Skill;
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
    {
        [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 配置文件读取器

FSMConfigReader
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.GetConfigFile(path);
            ConfigReader.ReadConfigFile(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 配置文件读取工厂

FSMConfigReaderFactory
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;
            }
        }
    }
}

# 创建工厂

  • 根据指定的命名规则,使用反射创建对象
  • 条件类的对象只涉及各种情况的触发判定,所以可以循环利用

代码如下

FSMCreateFactory
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;
        }
    }
}

# 具体的条件类

  • 继承抽象类,处理对应的逻辑

完成巡逻

CompletePatrolTrigger
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;
        }
    }
}

发现目标

FoundTargetTrigger
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;
        }
    }
}

击杀目标

KilledTargetTrigger
using ARPGDemo.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;
        }
    }
}

丢失目标

LostTargetTrigger
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;
        }
    }
}

没有生命

NoHealthTrigger
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;
        }
    }
}

巡逻

PatrolTrigger
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;
        }
    }
}

到达目标

ReachTargetTrigger
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;
        }
    }
}

复活

ReviveTrigger
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;
        }
    }
}

# 具体的状态类

  • 继承抽象类,处理对应的逻辑

闲置状态

IdleState
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);
        }
    }
}

死亡状态

DeadState
namespace AI.FSM
{
    /// <summary>
    /// 死亡状态
    /// </summary>
    public class DeadState : FSMState
    {
        public override FSMStateID StateID => FSMStateID.Dead;
        public override bool IgnoreWalls => false;
    }
}

追逐状态

PursuitState
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);
        }
    }
}

巡逻状态

PatrolState
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);
        }
    }
}

攻击状态

AttackState
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);
            }
        }
    }
}

# 类图

最后给出类图:

img
img

# 测试

添加组件
img

设置参数
img

结果演示
img