推荐阅读
- CSDN主页
- GitHub开源地址
- Unity3D插件分享
- 简书地址
大家好,我是佛系工程师☆恬静的小魔龙☆,不定时更新Unity开发技巧,觉得有用记得一键三连哦。
一、前言
【GameFramework框架】系列教程目录:
https://blog.csdn.net/q764424567/article/details/135831551
这篇文章分享FSM
有限状态机,了解过StarForce
源码的同学相比都比较了解,GameFramework框架给我们抽象了一个Procedure
流程的概念。
这个Procedure
流程可以方便的处理我们游戏从启动到游戏的各个流程阶段的处理逻辑,实现的核心就是FSM
状态机,内部的Procedure
流程就是不同的FsmState
状态。
接下来,就是FSM
有限状态机的教程了。
二、正文
2-1、介绍
有限状态机(Finite-state machine,FSM)
,又叫做有限状态自动机,简称状态机,是在游戏编程中非常常用的编程技术,可以方便的管理各种状态的转换。
举个例子:怪物的行为逻辑,怪物有三种状态:巡逻Patrol
、寻路Pathfind
、攻击Attack
描述这三种状态的简单状态机模型:
初始状态:巡逻
从当前点寻路到下一个点位:巡逻→寻路
到下一个点位继续巡逻:巡逻→寻路
发现敌人退出巡逻状态:寻路→追击
进入攻击范围:攻击
敌人死亡或者离开巡逻半径:巡逻→寻路
在GameFramework中,FSM也被广泛使用,并且该框架提供了方便易用的FSM API,方便我们在项目中快速实现FSM逻辑。
2-2、使用说明
开始流程:
using GameFramework;
using GameFramework.Fsm;
using GameFramework.Procedure;
using System;
using System.Collections;
using UnityEngine;
namespace UnityGameFramework.Runtime
{
/// <summary>
/// 流程组件。
/// </summary>
[DisallowMultipleComponent]
[AddComponentMenu("Game Framework/Procedure")]
public sealed class ProcedureComponent : GameFrameworkComponent
{
private IProcedureManager m_ProcedureManager = null;
private IEnumerator Start()
{
// 初始化 流程状态机
m_ProcedureManager.Initialize(GameFrameworkEntry.GetModule<IFsmManager>(), procedures);
// 默认第一个进入的流程状态
m_ProcedureManager.StartProcedure(m_EntranceProcedure.GetType());
}
}
}
切换流程状态:
using GameFramework.Localization;
using System;
using UnityGameFramework.Runtime;
using ProcedureOwner = GameFramework.Fsm.IFsm<GameFramework.Procedure.IProcedureManager>;
namespace StarForce
{
public class ProcedureLaunch : ProcedureBase
{
protected override void OnEnter(ProcedureOwner procedureOwner)
{
base.OnEnter(procedureOwner);
// 进入流程
}
protected override void OnUpdate(ProcedureOwner procedureOwner, float elapseSeconds, float realElapseSeconds)
{
base.OnUpdate(procedureOwner, elapseSeconds, realElapseSeconds);
// 切换流程
ChangeState<ProcedureSplash>(procedureOwner);
}
}
}
这样我们的流程状态机就跑起来了,具体的流程切换就可以在各个不同的流程中,根据需求进行切换了。
2-3、实现及代码分析
FsmManager.cs
状态机管理器,创建一个FSM
状态机实例后,FSM
状态机的创建、获取和销毁都交给FsmManager
管理。
//------------------------------------------------------------
// Game Framework
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
// Homepage: https://gameframework.cn/
// Feedback: mailto:ellan@gameframework.cn
//------------------------------------------------------------
using System;
using System.Collections.Generic;
namespace GameFramework.Fsm
{
/// <summary>
/// 有限状态机管理器。
/// </summary>
internal sealed class FsmManager : GameFrameworkModule, IFsmManager
{
private readonly Dictionary<TypeNamePair, FsmBase> m_Fsms;
private readonly List<FsmBase> m_TempFsms;
/// <summary>
/// 初始化有限状态机管理器的新实例。
/// </summary>
public FsmManager()
{
m_Fsms = new Dictionary<TypeNamePair, FsmBase>();
m_TempFsms = new List<FsmBase>();
}
/// <summary>
/// 获取游戏框架模块优先级。
/// </summary>
/// <remarks>优先级较高的模块会优先轮询,并且关闭操作会后进行。</remarks>
internal override int Priority
{
get
{
return 1;
}
}
/// <summary>
/// 获取有限状态机数量。
/// </summary>
public int Count
{
get
{
return m_Fsms.Count;
}
}
/// <summary>
/// 有限状态机管理器轮询。
/// </summary>
/// <param name="elapseSeconds">逻辑流逝时间,以秒为单位。</param>
/// <param name="realElapseSeconds">真实流逝时间,以秒为单位。</param>
internal override void Update(float elapseSeconds, float realElapseSeconds)
{
m_TempFsms.Clear();
if (m_Fsms.Count <= 0)
{
return;
}
foreach (KeyValuePair<TypeNamePair, FsmBase> fsm in m_Fsms)
{
m_TempFsms.Add(fsm.Value);
}
foreach (FsmBase fsm in m_TempFsms)
{
if (fsm.IsDestroyed)
{
continue;
}
fsm.Update(elapseSeconds, realElapseSeconds);
}
}
/// <summary>
/// 关闭并清理有限状态机管理器。
/// </summary>
internal override void Shutdown()
{
foreach (KeyValuePair<TypeNamePair, FsmBase> fsm in m_Fsms)
{
fsm.Value.Shutdown();
}
m_Fsms.Clear();
m_TempFsms.Clear();
}
/// <summary>
/// 检查是否存在有限状态机。
/// </summary>
/// <typeparam name="T">有限状态机持有者类型。</typeparam>
/// <returns>是否存在有限状态机。</returns>
public bool HasFsm<T>() where T : class
{
return InternalHasFsm(new TypeNamePair(typeof(T)));
}
/// <summary>
/// 获取有限状态机。
/// </summary>
/// <typeparam name="T">有限状态机持有者类型。</typeparam>
/// <returns>要获取的有限状态机。</returns>
public IFsm<T> GetFsm<T>() where T : class
{
return (IFsm<T>)InternalGetFsm(new TypeNamePair(typeof(T)));
}
/// <summary>
/// 创建有限状态机。
/// </summary>
/// <typeparam name="T">有限状态机持有者类型。</typeparam>
/// <param name="owner">有限状态机持有者。</param>
/// <param name="states">有限状态机状态集合。</param>
/// <returns>要创建的有限状态机。</returns>
public IFsm<T> CreateFsm<T>(T owner, params FsmState<T>[] states) where T : class
{
return CreateFsm(string.Empty, owner, states);
}
/// <summary>
/// 创建有限状态机。
/// </summary>
/// <typeparam name="T">有限状态机持有者类型。</typeparam>
/// <param name="name">有限状态机名称。</param>
/// <param name="owner">有限状态机持有者。</param>
/// <param name="states">有限状态机状态集合。</param>
/// <returns>要创建的有限状态机。</returns>
public IFsm<T> CreateFsm<T>(string name, T owner, params FsmState<T>[] states) where T : class
{
TypeNamePair typeNamePair = new TypeNamePair(typeof(T), name);
if (HasFsm<T>(name))
{
throw new GameFrameworkException(Utility.Text.Format("Already exist FSM '{0}'.", typeNamePair));
}
Fsm<T> fsm = Fsm<T>.Create(name, owner, states);
m_Fsms.Add(typeNamePair, fsm);
return fsm;
}
/// <summary>
/// 销毁有限状态机。
/// </summary>
/// <typeparam name="T">有限状态机持有者类型。</typeparam>
/// <returns>是否销毁有限状态机成功。</returns>
public bool DestroyFsm<T>() where T : class
{
return InternalDestroyFsm(new TypeNamePair(typeof(T)));
}
private bool InternalDestroyFsm(TypeNamePair typeNamePair)
{
FsmBase fsm = null;
if (m_Fsms.TryGetValue(typeNamePair, out fsm))
{
fsm.Shutdown();
return m_Fsms.Remove(typeNamePair);
}
return false;
}
}
}
Dictionary<TypeNamePair,FsmBase> m_Fsms
:所有的状态机都在这里存储,Key
是TypeNamePair
类型,即类型+名称,这样就可以支持,相同类型的多个状态机了。List m_TempFsms
:这个Temp是一个临时变量机制,因为m_Fsms
是字典类型,字典类型如果在遍历时被修改会报错,所以为了防止m_Fsms
在遍历时被修改,作者加了一个Temp
机制,即把m_Fsms
的值转存到Temp
中,然后遍历Temp
,防止m_Fsms
被修改。
FsmState.cs
有限状态机,状态基类。
//------------------------------------------------------------
using System;
namespace GameFramework.Fsm
{
/// <summary>
/// 有限状态机状态基类。
/// </summary>
/// <typeparam name="T">有限状态机持有者类型。</typeparam>
public abstract class FsmState<T> where T : class
{
/// <summary>
/// 初始化有限状态机状态基类的新实例。
/// </summary>
public FsmState()
{
}
/// <summary>
/// 有限状态机状态初始化时调用。
/// </summary>
/// <param name="fsm">有限状态机引用。</param>
protected internal virtual void OnInit(IFsm<T> fsm){}
/// <summary>
/// 有限状态机状态进入时调用。
/// </summary>
/// <param name="fsm">有限状态机引用。</param>
protected internal virtual void OnEnter(IFsm<T> fsm){}
/// <summary>
/// 有限状态机状态轮询时调用。
/// </summary>
/// <param name="fsm">有限状态机引用。</param>
/// <param name="elapseSeconds">逻辑流逝时间,以秒为单位。</param>
/// <param name="realElapseSeconds">真实流逝时间,以秒为单位。</param>
protected internal virtual void OnUpdate(IFsm<T> fsm, float elapseSeconds, float realElapseSeconds){}
/// <summary>
/// 有限状态机状态离开时调用。
/// </summary>
/// <param name="fsm">有限状态机引用。</param>
/// <param name="isShutdown">是否是关闭有限状态机时触发。</param>
protected internal virtual void OnLeave(IFsm<T> fsm, bool isShutdown){}
/// <summary>
/// 有限状态机状态销毁时调用。
/// </summary>
/// <param name="fsm">有限状态机引用。</param>
protected internal virtual void OnDestroy(IFsm<T> fsm){}
/// <summary>
/// 切换当前有限状态机状态。
/// </summary>
/// <param name="fsm">有限状态机引用。</param>
/// <param name="stateType">要切换到的有限状态机状态类型。</param>
protected void ChangeState(IFsm<T> fsm, Type stateType){}
}
}
OnInit
:状态创建时调用一次,整个生命周期只会调用一次。OnDestroy
:状态销毁时调用一次,整个生命周期只会调用一次。OnEnter
:在每次状态进入时,都会调用一次OnLeave
:在每次状态进退出时,都会调用一次OnUpdate
:进入当前状态后,OnUpdte每帧轮询一次。
FSM
有限状态机持有者,比如怪物AI。
using System;
using System.Collections.Generic;
namespace GameFramework.Fsm
{
/// <summary>
/// 有限状态机。
/// </summary>
/// <typeparam name="T">有限状态机持有者类型。</typeparam>
internal sealed class Fsm<T> : FsmBase, IReference, IFsm<T> where T : class
{
private T m_Owner;
private readonly Dictionary<Type, FsmState<T>> m_States;
private Dictionary<string, Variable> m_Datas;
private FsmState<T> m_CurrentState;
private float m_CurrentStateTime;
private bool m_IsDestroyed;
/// <summary>
/// 初始化有限状态机的新实例。
/// </summary>
public Fsm(){}
/// <summary>
/// 获取有限状态机持有者。
/// </summary>
public T Owner{}
/// <summary>
/// 获取有限状态机持有者类型。
/// </summary>
public override Type OwnerType{}
/// <summary>
/// 获取当前有限状态机状态。
/// </summary>
public FsmState<T> CurrentState{}
/// <summary>
/// 创建有限状态机。
/// </summary>
/// <param name="name">有限状态机名称。</param>
/// <param name="owner">有限状态机持有者。</param>
/// <param name="states">有限状态机状态集合。</param>
/// <returns>创建的有限状态机。</returns>
public static Fsm<T> Create(string name, T owner, params FsmState<T>[] states){}
}
}
Dictionary<Type, FsmState> m_States
:当前状态机的内部状态集合,使用字典来存储。Dictionary<string, Variable> m_Datas
:状态机内部的数据存储,用于状态机内部逻辑参数互传。
状态机回收:由于Fsm是与状态无关的,且游戏中大量使用,所以框架把Fsm继承了IReference,让我们不用的Fsm可以及时回收。
下面是示例图(懒得画,借一张):
三、后记
如果觉得本篇文章有用别忘了点个关注,关注不迷路,持续分享更多Unity干货文章。
你的点赞就是对博主的支持,有问题记得留言:
博主主页有联系方式。
博主还有跟多宝藏文章等待你的发掘哦:
专栏 | 方向 | 简介 |
---|---|---|
Unity3D开发小游戏 | 小游戏开发教程 | 分享一些使用Unity3D引擎开发的小游戏,分享一些制作小游戏的教程。 |
Unity3D从入门到进阶 | 入门 | 从自学Unity中获取灵感,总结从零开始学习Unity的路线,有C#和Unity的知识。 |
Unity3D之UGUI | UGUI | Unity的UI系统UGUI全解析,从UGUI的基础控件开始讲起,然后将UGUI的原理,UGUI的使用全面教学。 |
Unity3D之读取数据 | 文件读取 | 使用Unity3D读取txt文档、json文档、xml文档、csv文档、Excel文档。 |
Unity3D之数据集合 | 数据集合 | 数组集合:数组、List、字典、堆栈、链表等数据集合知识分享。 |
Unity3D之VR/AR(虚拟仿真)开发 | 虚拟仿真 | 总结博主工作常见的虚拟仿真需求进行案例讲解。 |
Unity3D之插件 | 插件 | 主要分享在Unity开发中用到的一些插件使用方法,插件介绍等 |
Unity3D之日常开发 | 日常记录 | 主要是博主日常开发中用到的,用到的方法技巧,开发思路,代码分享等 |
Unity3D之日常BUG | 日常记录 | 记录在使用Unity3D编辑器开发项目过程中,遇到的BUG和坑,让后来人可以有些参考。 |