【GameFramework框架内置模块】9、有限状态机(FSM)

news2025/2/23 19:31:17

推荐阅读

  • 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:所有的状态机都在这里存储,KeyTypeNamePair类型,即类型+名称,这样就可以支持,相同类型的多个状态机了。
  • 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之UGUIUGUIUnity的UI系统UGUI全解析,从UGUI的基础控件开始讲起,然后将UGUI的原理,UGUI的使用全面教学。
Unity3D之读取数据文件读取使用Unity3D读取txt文档、json文档、xml文档、csv文档、Excel文档。
Unity3D之数据集合数据集合数组集合:数组、List、字典、堆栈、链表等数据集合知识分享。
Unity3D之VR/AR(虚拟仿真)开发虚拟仿真总结博主工作常见的虚拟仿真需求进行案例讲解。
Unity3D之插件插件主要分享在Unity开发中用到的一些插件使用方法,插件介绍等
Unity3D之日常开发日常记录主要是博主日常开发中用到的,用到的方法技巧,开发思路,代码分享等
Unity3D之日常BUG日常记录记录在使用Unity3D编辑器开发项目过程中,遇到的BUG和坑,让后来人可以有些参考。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1531006.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从零开始写 Docker(七)---实现 mydocker commit 打包容器成镜像

本文为从零开始写 Docker 系列第七篇&#xff0c;实现类似 docker commit 的功能&#xff0c;把运行状态的容器存储成镜像保存下来。 完整代码见&#xff1a;https://github.com/lixd/mydocker 欢迎 Star 推荐阅读以下文章对 docker 基本实现有一个大致认识&#xff1a; 核心原…

解决jenkins运行磁盘满的问题

参考&#xff1a;https://blog.csdn.net/ouyang_peng/article/details/79225993 分配磁盘空间相关操作&#xff1a; https://cloud.tencent.com/developer/article/2230624 登录jenkins相对应的服务或容器中查看磁盘情况&#xff1a; df -h在102挂载服务器上看到是这两个文件…

OSPF特殊区域(stub\nssa)

stub区域——只有1类、2类、3类&#xff1b;完全stub区域——只有1类、2类 NSSA区域&#xff1a;本区域将自己引入的外部路由发布给其他区域&#xff0c;但不需要接收其他区域的路由 在NSSA区域的路由器上&#xff0c;引入外部路由时&#xff0c;不会转换成5类LSA&#xff0c…

Ethsign银河活动开启,简单参与领6个NFT

简介&#xff1a;EthSign是一个基于区块链技术的去中心化电子签名平台&#xff0c;目的是解决传统中心化电子签名服务的各种问题。用户可以使用钱包或社交媒体帐户生成的私钥签署文件和协议&#xff0c;数字签名记录在链上&#xff0c;文件经过加密存储在去中心化存储网络中&am…

CSS学习(3)-浮动和定位

一、浮动 1. 元素浮动后的特点 脱离文档流。不管浮动前是什么元素&#xff0c;浮动后&#xff1a;默认宽与高都是被内容撑开&#xff08;尽可能小&#xff09;&#xff0c;而且可以设置宽 高。不会独占一行&#xff0c;可以与其他元素共用一行。不会 margin 合并&#xff0c;…

DETR算法简介

DETR方法是一种使用了Transformer的端到端的目标检测方法&#xff0c;也是经典目标检测算法之一&#xff0c;本文将用最少的话&#xff0c;介绍DETR算法的大致思想。之前的方法或多或少的都不要添加一下额外的步骤&#xff0c;进行人为干预&#xff0c;即使是号称端到端的YOLO系…

Linux卸载Zabbix6 Agent v1 v2 简易操作手册

一、Zabbix6 卸载Zabbix Agent v1 要在Linux系统上卸载Zabbix Agent v1(zabbix_agent)&#xff0c;您可以使用包管理器执行此操作。以下是针对不同Linux发行版的卸载命令&#xff1a; # 对于基于Debian的系统&#xff08;如Ubuntu&#xff09;: sudo apt-get remove zabbix-ag…

Java项目实战记录:雷达数据插值

Java项目实战记录&#xff1a;雷达数据插值 业务背景 之前已经实现了雷达数据的解析和雷达数据后端渲染功能&#xff0c;现在又有一个新的需求。之前是将雷达数据点使用GeoTools渲染成PNG的图片&#xff0c;但这个数据返给前端后不能无极缩放&#xff0c;因为它是个栅格图片&…

ArmSoM-Sige RK3588开发板使用手册

Sige7 使用手册&#xff0c;帮助用户了解Sige7的基本使用和需要的准备工作。 当您拿到产品的时候&#xff0c;您需要知道它的型号以及硬件版本&#xff0c;这些信息都可以在板子上的丝印找到。我们会尽可能详细地向您介绍产品的信息。 入门准备​ 在开始使用 ArmSoM-Sige7 之…

matlab空间曲线图形

说明&#xff1a;问题来自CSDN-问答板块&#xff0c;题主提问。 需求&#xff1a;如何用子图命令画出平面y2z&#xff0c;z2y与球面x^2y^2z^25相交的空间曲线图形。需要完整代码和结果的图片。 一、先看效果图 二、代码 % 创建figure figure% 创建二维网格&#xff0c;用于定…

数据结构的概念大合集03(栈)

概念大合集03 1、栈1.1 栈的定义和特点1.2 栈的基础操作1.3 栈的顺序存储1.3.1 顺序栈1.3.2 栈空&#xff0c;栈满&#xff0c;进栈&#xff0c;出栈的基本思想1.3.3 共享栈1.3.3.1 共享栈的4要素 1.4 栈的链式存储1.4.1 链栈的实现1.4.2 链栈的4个要素 1、栈 1.1 栈的定义和特…

酷开系统用电视为居家生活打开精彩窗口|酷开科技|酷开会员|

随着互联网的发展&#xff0c;电视也承载了更多的功能。相比于传统的电视&#xff0c;如今的智能电视屏幕更大、分辨率更高、色彩更加鲜艳&#xff0c;能够呈现出更加逼真的画面效果。当观众观看大屏电视时&#xff0c;仿佛置身于电影大幕的场景之中&#xff0c;感受到更为震撼…

边缘计算+WEB端应用融合:AI行为识别智能监控系统搭建指南 -- 整体介绍(一)

专栏目录 边缘计算WEB端应用融合&#xff1a;AI行为识别智能监控系统搭建指南 – 整体介绍&#xff08;一&#xff09; 边缘计算WEB端应用融合&#xff1a;AI行为识别智能监控系统搭建指南 – 边缘设备图像识别及部署&#xff08;二&#xff09; 边缘计算WEB端应用融合&#xf…

洛谷_P2404 自然数的拆分问题_python写法

P2404 自然数的拆分问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 思路记录&#xff1a; 这道题是一道简单的DFS。 n int(input()) data [0 for _ in range(110)] def dfs(sum, p, cnt):if sum>n:returnif sum n:print(data[0],end)for i in range(1,cnt):print(f{…

centos7安装jdk详细步骤(yum安装与手动安装)

centos7安装jdk详细步骤&#xff08;yum安装与手动安装&#xff09; 一、使用yum安装1. 准备工作2. 检查系统是否自带jdk3. 安装jdk 二、手动安装jdk1. 下载上传jdk2. 安装jdk3. 配置环境变量 一、使用yum安装 1. 准备工作 如果你的机器可以联网可以使用此方法 ping www.baidu…

YOLOv7 | 添加GSConv,VoVGSCSP等多种卷积,有效提升目标检测效果,代码改进(超详细)

⭐欢迎大家订阅我的专栏一起学习⭐ &#x1f680;&#x1f680;&#x1f680;订阅专栏&#xff0c;更新及时查看不迷路&#x1f680;&#x1f680;&#x1f680; YOLOv5涨点专栏&#xff1a;http://t.csdnimg.cn/QdCj6 YOLOv7专栏&#xff1a; http://t.csdnimg.cn/dy…

51-31 CVPR’24 | VastGaussian,3D高斯大型场景重建

2024 年 2 月&#xff0c;清华大学、华为和中科院联合发布的 VastGaussian 模型&#xff0c;实现了基于 3D Gaussian Splatting 进行大型场景高保真重建和实时渲染。 Abstract 现有基于NeRF大型场景重建方法&#xff0c;往往在视觉质量和渲染速度方面存在局限性。虽然最近 3D…

Git小乌龟安装及使用教程

一、Win7安装git 软件下载地址&#xff1a;git for windows 安装过程直接默认下一步&#xff0c;直到安装结束。 安装结束后重启一下。 安装完成后&#xff0c;在文件夹空白处右键出现以下几个标识&#xff0c;说明安装成功。 二、安装tortoise git&#xff08;乌龟git&…

flutter 局部view更新,dialog更新进度,dialog更新

局部更新有好几种方法&#xff0c;本次使用的是 StatefulBuilder 定义 customState去更新对话框内容 import package:flutter/cupertino.dart; import package:flutter/material.dart;class ProgressDialog {final BuildContext context;BuildContext? dialogContext;double _…

帮助读者掌握C语言编程基础知识的书籍

帮助读者掌握C语言编程的基础知识&#xff0c;了解如何将人工智能技术应用于自己的编程项目。 人工智能编程&#xff08;赋能C语言&#xff09; 作者&#xff1a; 黄箐、廖云燕、曾锦山、邢振昌 ISBN号&#xff1a; 9787302648796 出版日期&#xff1a; 2023-11-01 本书以C…