文章目录
- 1 有限状态机
- 2 状态机实现框架
- 2.1 StateMachine
- 2.2 BaseState
- 2.3 ...State
- 2.4 IAIObject
- 3 框架类图
本文章参考 B 站唐老狮 2023 年直播内容。点击前往唐老狮 B 站主页。
1 有限状态机
有限状态机(Finite - State Machine,FSM),又称有限状态自动机,简称状态机。是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。
-
有限:有限度,不是无限的。
-
状态:指所拥有的所有状态。
举例说明:
人会做很多个动作,即有很多种状态,包括站立、走路、跑步、攻击、防守、睡觉等等。我们每天都会在这些状态中切换,而且这些状态虽然多但是是有限的。当达到某种条件时,就会在这些状态中进行切换,且这种切换时随时可能发生。
游戏中的怪物 AI 表现一般为巡逻、朝玩家移动、攻击、逃跑、格挡等等状态。即使怪物 AI 表现看起来比较复杂,但是深入观察和分析,它们的状态一般是有限的,只是在不同条件下切换不同状态,由于状态的多样性和美术表现的多样性,让我们觉得它是比较智能的。
因此,“有限”和“行为状态”这两个特点,使得游戏 AI 适用于使用有限状态机实现。
2 状态机实现框架
-
StateMachine(状态机类,用于统一管理各个状态)。
-
BaseState(状态基类,抽象出状态的共有内容)。
-
…State(各状态类,有多少个状态就有几个状态类,均继承状态基类)。
-
IAIObject(怪物 AI 接口,用于规范所有 AI 的共同行为)。
2.1 StateMachine
有限状态机类,用于管理各个状态之间的切换。同时需要记录所有状态,因此需要如下成员:
- StateDictionay:记录所有状态。
- NowState:当前处于的状态。
- AIObject:附属在哪个 AI 对象上。
- AddState():向外部提供添加状态的方法(执行一帧)。
- ChangeState():向外部提供切换状态的方法(执行一帧)。
- UpdateState():向外部提供更新状态的方法(每帧调用)。
基本结构如下:
using System;
using System.Collections.Generic;
/// <summary>
/// 有限状态机类,用于管理各个状态之间的切换
/// </summary>
public class StateMachine
{
/// <summary>
/// 管理所有状态的字典容器
/// </summary>
private Dictionary<EAIState, BaseState> _stateDic = new Dictionary<EAIState, BaseState>();
private BaseState _nowState; // 当前状态
public IAIObject AIObject; // 管理的 AI 对象
/// <summary>
/// 初始化方法
/// </summary>
/// <param name="aiObject">待管理的 AI 对象</param>
public void Init(IAIObject aiObject) {
AIObject = aiObject;
}
/// <summary>
/// 添加 AI 状态
/// </summary>
public void AddState(EAIState state) { ... }
/// <summary>
/// 切换状态
/// </summary>
/// <param name="state"></param>
public void ChangeState(EAIState state) { ... }
/// <summary>
/// 更新当前状态
/// </summary>
public void UpdateState() { ... }
}
2.2 BaseState
状态基类负责提取所有状态共有的特性,并声明为抽象类(Abstract),规范每个状态的共同行为。其共有特性基本包括如下:
- AIState:状态类型(所有状态需要告诉外面自己是哪种类型)。
- StateMachine:附属的状态机。
- OnStateEnter():进入状态时执行的逻辑(执行一帧)。
- OnStateUpdate():处于该状态时不断更新的逻辑(每帧更新)。
- OnStateExist():离开状态时执行的逻辑(执行一帧)。
基本结构如下:
using UnityEngine;
/// <summary>
/// 状态基类
/// </summary>
public abstract class BaseState
{
public virtual EAIState AIState { get; } // 状态类型
protected StateMachine _stateMachine; // 附属的状态机
public BaseState(StateMachine stateMachine) {
_stateMachine = stateMachine;
}
/// <summary>
/// 进入状态
/// </summary>
public abstract void OnStateEnter();
/// <summary>
/// 保持状态
/// </summary>
public abstract void OnStateUpdate();
/// <summary>
/// 离开状态
/// </summary>
public abstract void OnStateExit();
}
2.3 …State
在各个具体状态中,可以有独立的数据或逻辑,但每个状态都需要实现由基类规范的共同逻辑。
以 PatrolState(巡逻状态)为例,其基本结构如下:
using System;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
using Random = UnityEngine.Random;
/// <summary>
/// 巡逻状态类,处理巡逻逻辑
/// </summary>
public class PatrolState : BaseState
{
// 巡逻数据
...
public override EAIState AIState { get => EAIState.Patrol; } // 表示自己是巡逻状态
public PatrolState(StateMachine stateMachine) : base(stateMachine) {
// 初始化
...
}
public override void OnStateEnter() { ... } // 进入巡逻状态的逻辑实现
public override void OnStateUpdate() { ... } // 处于巡逻状态的逻辑实现
public override void OnStateExit() { ... } // 退出巡逻状态的逻辑实现
// 其他逻辑、方法封装等
...
}
2.4 IAIObject
AI 对象接口,规定 AI 对象必须继承该接口,用于规范 AI 对象的行为。
在 StateMachine 中,不会存储具体的 AI 对象,而是存储 IAIObject,指向对应的 AI 对象。不同的 AI 对象均需要实现 IAIObject 中规范的逻辑,从而使 StateMachine 能够适用于具有不同逻辑的不同 AI 对象。
基本实现如下:
using UnityEngine;
/// <summary>
/// AI 对象接口,用于规范 AI 对象的行为
/// </summary>
public interface IAIObject
{
public Transform Transform { get; } // AI 对象的 Transform
public Transform TargetTransform { get; } // 目标监测对象的 Transform
public float AttackRange { get; } // 攻击范围
public Vector3 BornPos { get; } // 出生位置
public void Move(Vector3 targetPos); // 移动
public void StopMove(); // 停止移动
public void Attack(); // 攻击
public void ChangeAction(EAction action); // 切换指定动作
// 等等
...
}