目录
[TOC]
一、写在前面
本文着重实现一个高度可配置的有限状态机(FiniteStateMachine);然后借鉴 Unity官方教程 将其运用于 Unity 中。
有限状态机的相关概念就不做介绍了,了解即可。
二、FiniteStateMachine
1.FSM 框架结构
如下图所示:
StateController
状态管理器:
- AI 的核心
- 将 AI 系统的 初始状态 注入
- 设置好 AI 的 相关数据(此后会将
StateController
注入到各个模块以提供 AI 相关数据) - 状态管理器的帧函数
UpdateAI
由外部(继承自 Monobehavior)的类调用
StateBase
状态基类:处理各个状态的公共逻辑:
- 存储 状态 的 行动集合(每个状态可能有多个行动组合而成)
- 存储 状态 的 转换条件集合(每个状态可能有多种转换条件)
- (必选)在子类中添加 行动集合
AddActions
- 在外部添加 转换条件集合
AddTransition
- 在内部处理状态帧函数
UpdateState
:执行动作DoActions
+ 检验每个转换条件CheckTransitions
- (可选)实现状态的生命周期函数:进入状态的
DoOnBefore
+ 退出状态的DoOnExit
ActionBase
动作基类:
- 将每个状态下的行为分解为各个动作的集合,有利于逻辑复用,更加模块化的处理也有利于 AI 行为的扩展。
Transition
转换条件:
- 每个转换条件由:一个 转换决策
Decision
、一个 转换成功后指向的状态TargetState
组成
DecisionBase
转换决策基类:
- 转换条件
Transition
的核心组成部分 - 用于处理状态之间转换的核心逻辑
2.框架代码
StateController
/****************************************************
文件:StateController.cs
作者:HuskyT
邮箱:1005240602@qq.com
日期:2020/3/20 16:19:33
功能:状态管理器(AI Brain)
*****************************************************/
using System;
using UnityEngine;
namespace FiniteStateMachine
{
/*
* 状态管理器
* AI Brain
* 作用:AI 的状态控制器(核心类)
*/
public class StateController
{
private StateBase mCurrentState;
/// <summary>
/// AI 是否为激活状态
/// </summary>
private bool mAIActivate;
/// <summary>
/// 当前状态过去的时间(当前状态的计时器)
/// </summary>
private float mElapsedTime;
public StateController(StateBase originalState, params object[] args)
{
mCurrentState = originalState;
mAIActivate = true;
mElapsedTime = 0.0f;
//设置 AI 初始数据
Debug.Log("AI 初始化完毕");
}
public void SetupAI(bool aiActivate, params object[] args)
{
mAIActivate = aiActivate;
//设置 AI 数据
}
public void UpdateAI()
{
if (mAIActivate == false) return;
mCurrentState.UpdateState(this);
}
public void TransitionToState(StateBase targetState)
{
mCurrentState.DoOnExit(this);
mElapsedTime = 0.0f;//计时器清零
mCurrentState = targetState;
mCurrentState.DoOnBefore(this);
}
/// <summary>
/// 检测当前状态的计时器是否到达指定时间
/// </summary>
/// <param 指定时间="time"></param>
/// <returns></returns>
public bool CheckElapsedTime(float time)
{
return mElapsedTime >= time;
}
}
}
StateBase
/****************************************************
文件:StateBase.cs
作者:HuskyT
邮箱:1005240602@qq.com
日期:2020/3/20 15:55:28
功能:状态基类
*****************************************************/
using System.Collections.Generic;
using UnityEngine;
namespace FiniteStateMachine
{
/*
* 状态基类
* 可创建不同 AI 对象特有的状态
* 作用:划分 AI 状态(内部处理每个状态独有的逻辑)
* 使用:在子类中设置 状态 包含的 动作集Actions ;转换条件集(Transitions)在外部设置
* 可选:在子类中实现 DoOnBefore 和 DoOnExit (状态 的 生命周期函数)
*/
public abstract class StateBase
{
protected List<ActionBase> mActions;
protected List<Transition> mTransitions;
public StateBase()
{
mActions = new List<ActionBase>();
mTransitions = new List<Transition>();
AddActions();
}
public virtual void DoOnBefore(StateController controller) { }
public virtual void DoOnExit(StateController controller) { }
/// <summary>
/// 在子类中配置 状态 State 包含的 动作集(Actions)
/// </summary>
protected abstract void AddActions();
/// <summary>
/// 在子类中配置 状态 State 包含的 转换条件集(Transitions)
/// </summary>
public void AddTransition(Transition transition)
{
for (int i = 0; i < mTransitions.Count; i++)
{
if (mTransitions[i] == transition)
{
Debug.LogErrorFormat("{0}状态中已包含转换条件:{1}", this, transition.Decision.ToString());
return;
}
}
mTransitions.Add(transition);
Debug.LogFormat("在{0}状态中添加转换条件:{1}", this, transition.Decision.ToString());
}
public void UpdateState(StateController controller)
{
DoActions(controller);
CheckTransitions(controller);
}
private void DoActions(StateController controller)
{
for (int i = 0; i < mActions.Count; i++)
{
mActions[i].Act(controller);
}
}
private void CheckTransitions(StateController controller)
{
bool succeed = false;
//每次帧循环都会检测当前状态所有的转换条件
//若存在多个可以成功转换的 转换条件 时,选取第一个为成功转换的状态(此处可以修改选取规则)
for (int i = 0; i < mTransitions.Count; i++)
{
succeed = mTransitions[i].Decision.Decide(controller);
if (succeed)
{
controller.TransitionToState(mTransitions[i].TargetState);
break;
}
}
}
}
}
ActionBase
/****************************************************
文件:ActionBase.cs
作者:HuskyT
邮箱:1005240602@qq.com
日期:2020/3/20 16:23:4
功能:AI 的最小行为单元(抽象基类)
*****************************************************/
namespace FiniteStateMachine
{
/*
* AI 的最小行为单元(抽象基类)
* 作用:将 AI 行为尽可能拆分、模块化,便于拓展、复用。
*/
public abstract class ActionBase
{
public abstract void Act(StateController controller);
}
}
Transition
/****************************************************
文件:Transition.cs
作者:HuskyT
邮箱:1005240602@qq.com
日期:2020/3/20 16:44:21
功能:转换条件
*****************************************************/
namespace FiniteStateMachine
{
/*
* 转换条件
* 每个转换条件由:一个转换决策、一个转换成功后指向的状态 组成
*/
public class Transition
{
private DecisionBase mDecision;
private StateBase mTargetState;
public DecisionBase Decision { get => mDecision; }
public StateBase TargetState { get => mTargetState; }
public Transition(DecisionBase decision, StateBase targetState)
{
mDecision = decision;
mTargetState = targetState;
}
}
}
Decision
/****************************************************
文件:DecisionBase.cs
作者:HuskyT
邮箱:1005240602@qq.com
日期:2020/3/20 16:23:26
功能:转换决策(抽象基类)
*****************************************************/
namespace FiniteStateMachine
{
/*
* 转换决策(抽象基类)
* 作用:为转换条件 Transition 的核心组成部分,用于处理状态之间转换的核心逻辑
*/
public abstract class DecisionBase
{
public abstract bool Decide(StateController controller);
}
}
3.案例
实现一个追踪者(Hunter)的 AI :
- 包含两个状态:巡逻状态 + 追逐状态
- 巡逻状态 由一个 Action 构成:巡逻行为
- 追逐状态 由两个 Action 构成:追逐行为 + 攻击行为
- 默认的初始状态为:巡逻状态
- 当目标被发现时,Hunter 由 巡逻状态 切换为 追逐状态,追逐状态 下当满足 攻击条件 时可进行 攻击行为 。
- 直到目标被消灭,Hunter 才会由 追逐状态 切换回 巡逻状态
如下图所示:
代码:(Action 和 Decision 的实现部分略)
HunterPatrolState:
using System;
using UnityEngine;
namespace FiniteStateMachine.Example1
{
public class HunterPatrolState : StateBase
{
protected override void AddActions()
{
mActions.Add(new PatrolAction());
Debug.LogFormat("{0}的Action添加完毕", this);
}
}
}
HunterChaseState:
using System;
using UnityEngine;
namespace FiniteStateMachine.Example1
{
public class HunterChaseState : StateBase
{
protected override void AddActions()
{
mActions.Add(new ChaseAction());
mActions.Add(new AttackAction());
Debug.LogFormat("{0}的Action添加完毕", this);
}
}
}
HunterController:
using System;
using UnityEngine;
namespace FiniteStateMachine.Example1
{
public class HunterController : MonoBehaviour
{
private StateController mAI;
private void Start()
{
HunterPatrolState hunterPatrolState = new HunterPatrolState();
HunterChaseState hunterChaseState = new HunterChaseState();
hunterPatrolState.AddTransition(new Transition(new LookDecision(), hunterChaseState));
hunterChaseState.AddTransition(new Transition(new InactiveStateDecision(), hunterPatrolState));
mAI = new StateController(hunterPatrolState, null);
}
private void Update()
{
mAI.UpdateAI();
}
}
}
三、SerializableFiniteStateMachine
1.说明
可序列化的有限状态机。可以在 Unity 编辑模式下进行 AI 的快速配置,有利于原型开发。
状态机构建原理和之前的基本一致(Transition 部分略有区别,本质相同)
2.框架代码
StateController
/****************************************************
文件:StateController.cs
作者:HuskyT
邮箱:1005240602@qq.com
日期:2020/3/18 22:6:21
功能:状态管理器(AI Brain)
*****************************************************/
using System;
using UnityEngine;
namespace SerializableFiniteStateMachine
{
/*
* 状态管理器
* AI Brain,直接挂载在 NPC 上
* 作用:AI 的状态控制器(核心类)
*/
public class StateController : MonoBehaviour
{
[Tooltip("当前状态")]
public State CurrentState;
[Tooltip("预设的虚拟状态")]
public State RemainState;
/// <summary>
/// AI 是否为激活状态
/// </summary>
private bool mAIActivate = false;
/// <summary>
/// 当前状态过去的时间(当前状态的计时器)
/// </summary>
private float mElapsedTime = 0.0f;
public void SetupAI(bool aiActivate, params object[] args)
{
mAIActivate = aiActivate;
//设置 AI 数据
}
private void Update()
{
if (mAIActivate == false) return;
CurrentState.UpdateState(this);
}
public void TransitionToState(State targetState)
{
if (targetState == RemainState) return;
CurrentState = targetState;
OnExitState();
}
/// <summary>
/// 检测当前状态的计时器是否到达指定时间
/// </summary>
/// <param 指定时间="time"></param>
/// <returns></returns>
public bool CheckElapsedTime(float time)
{
mElapsedTime += Time.deltaTime;
return mElapsedTime >= time;
}
private void OnExitState()
{
mElapsedTime = 0.0f;//计时器清零
}
}
}
State
/****************************************************
文件:State.cs
作者:HuskyT
邮箱:1005240602@qq.com
日期:2020/3/18 22:8:17
功能:状态
*****************************************************/
using UnityEngine;
namespace SerializableFiniteStateMachine
{
/*
* 状态
* 可创建不同 AI 对象特有的状态,可序列化为 Asset 资产对象,便于在 Unity 编辑模式下配置 AI
* 作用:划分 AI 状态(内部处理每个状态独有的逻辑)
*/
[CreateAssetMenu(fileName = "DefaultState", menuName = "SerializableFiniteStateMachine/States")]
public class State : ScriptableObject
{
[Tooltip("该状态下的行动集合")]
public ActionBase[] Actions;
[Tooltip("该状态下的转换条件集合")]
public Transition[] Transitions;
public void UpdateState(StateController controller)
{
DoActions(controller);
CheckTransitions(controller);
}
private void DoActions(StateController controller)
{
for (int i = 0; i < Actions.Length; i++)
{
Actions[i].Act(controller);
}
}
private void CheckTransitions(StateController controller)
{
bool succeed = false;
//每次帧循环都会检测当前状态所有的转换条件
//若存在多个转换条件,且同时为 TrueState 时,选取最后一个 TrueState 为成功转换的状态(此处可以修改选取规则)
for (int i = 0; i < Transitions.Length; i++)
{
succeed = Transitions[i].Decision.Decide(controller);
if (succeed)
{
controller.TransitionToState(Transitions[i].TrueState);
}
else
{
controller.TransitionToState(Transitions[i].FalseState);
}
}
}
}
}
ActionBase
/****************************************************
文件:ActionBase.cs
作者:HuskyT
邮箱:1005240602@qq.com
日期:2020/3/18 22:7:18
功能:AI 的最小行为单元(抽象基类)
*****************************************************/
using UnityEngine;
namespace SerializableFiniteStateMachine
{
/*
* AI 的最小行为单元
* 抽象基类,子类可序列化为 Asset 资产对象
* 作用:将 AI 行为尽可能拆分、模块化,便于拓展、复用。
*/
public abstract class ActionBase : ScriptableObject
{
public abstract void Act(StateController controller);
}
}
Transition
/****************************************************
文件:Transition.cs
作者:HuskyT
邮箱:1005240602@qq.com
日期:2020/3/18 22:8:44
功能:转换条件
*****************************************************/
using UnityEngine;
namespace SerializableFiniteStateMachine
{
/*
* 转换条件
* 每个转换条件由:一个转换决策、一个转换成功后指向的状态、一个转换失败后指向的状态 组成
* 说明:加上可序列化标签是便于在 Unity 编辑模式下进行配置
*/
[System.Serializable]
public class Transition
{
[Tooltip("转换决策")]
public DecisionBase Decision;
[Tooltip("转换成功后指向的状态")]
public State TrueState;
[Tooltip("转换失败后指向的状态")]
public State FalseState;
}
}
DecisionBase
/****************************************************
文件:DecisionBase.cs
作者:HuskyT
邮箱:1005240602@qq.com
日期:2020/3/18 22:8:33
功能:转换决策(抽象基类)
*****************************************************/
using UnityEngine;
namespace SerializableFiniteStateMachine
{
/*
* 转换决策
* 抽象基类,子类可序列化为 Asset 资产对象
* 作用:为转换条件 Transition 的核心组成部分,用于处理状态之间转换的核心逻辑
*/
public abstract class DecisionBase : ScriptableObject
{
public abstract bool Decide(StateController controller);
}
}
3.案例
(1)说明
新增一个扫描者(Scanner)的 AI :
- 包含三个状态:巡逻状态 + 追逐状态 + 警戒状态
- 巡逻状态 由一个 Action 构成:巡逻行为
- 追逐状态 由两个 Action 构成:追逐行为 + 攻击行为
- 警戒状态 没有行为
- 默认的初始状态为:巡逻状态
- 当目标被发现时,Scanner 由 巡逻状态 切换为 追逐状态,追逐状态 下当满足 攻击条件 时可进行 攻击行为 。
- 当失去目标踪迹时,Scanner 由 追逐状态 切换为 警戒状态
- 警戒状态下,Scanner 会进行 扫描搜索 目标:如果扫描结束依旧没有发现目标则由 警戒状态 切换回 巡逻状态 ;如果扫描过程中发现了目标,则由 警戒状态 切换回 追逐状态
如下图所示:
(2)在 Unity 编辑模式下配置 AI
Hierarchy:
各个状态:
ScannerPatrolState:
ScannerChaseState:
ScannerAlertState:
(3)代码
PatrolAction
/****************************************************
文件:PatrolAction.cs
作者:HuskyT
邮箱:1005240602@qq.com
日期:2020/3/18 23:44:27
功能:巡逻 Action
*****************************************************/
using System;
using UnityEngine;
namespace SerializableFiniteStateMachine.Example1
{
[CreateAssetMenu(fileName = "PatrolAction", menuName = "SerializableFiniteStateMachine/Actions/PatrolAction")]
public class PatrolAction : ActionBase
{
public override void Act(StateController controller)
{
Patrol(controller);
}
private void Patrol(StateController controller)
{
//巡逻逻辑
}
}
}
ScanDecision
/****************************************************
文件:ScanDecision.cs
作者:HuskyT
邮箱:1005240602@qq.com
日期:2020/3/19 0:22:2
功能:扫描 转换决策
*****************************************************/
using System;
using UnityEngine;
namespace SerializableFiniteStateMachine.Example1
{
[CreateAssetMenu(fileName = "ScanDecision", menuName = "SerializableFiniteStateMachine/Decisions/ScanDecision")]
public class ScanDecision : DecisionBase
{
public override bool Decide(StateController controller)
{
return Scan(controller);
}
private bool Scan(StateController controller)
{
//扫描逻辑
return false;
}
}
}
四、写在最后
如果 AI 的状态不复杂,使用 FSM 来构建 AI 是一件很高效的事情。利用 Unity 的可序列化功能能使 AI 的配置更为方便直观。
在 FSM 的基础上也可以很容易得扩展出 分层有限状态机。
Reference