GameFramework源码分析(一) – 基础架构与核心

前言

本篇并不旨在教会你如何使用GameFramework框架。因为笔者个人更倾向在游戏开发上,根据实际项目需求运用经验思想,来有针对性的完成项目架构,而非习惯的简单依赖于通用框架。故自然我不会将这篇以教程的形式展开。

尽管我不习惯直接用这类框架,但并不妨碍我分析GameFramework的源码。这篇个人笔记在我电脑中吃灰了许久,其初衷是深入剖析GameFramework的源码,思考GameFramework的设计思想与目的,并汲取其框架设计的优点。

我认为好的框架应该注重精炼与高效,保持简洁和模块化设计,具备良好的可扩展性和灵活性,而不是盲目追求功能的丰富。它更应该像一把锋利的刀,能够精准地解决开发者面临的问题。而不是像瑞士军刀,虽功能多样,但显得笨重繁琐。

事实上,我大概看了目前国内Unity常用的几个框架。GameFramework在这几个框架中相比较为轻量好扩展,同时它也做到了一个框架该具备的规范性、健壮性 以及流程规范。

相比之下,国内某些框架过度集成了各种工具,导致早以偏离了框架的本质,更像是一个工具大杂烩,外表鲜艳但设计混乱。而GameFramework相对简洁,专注提供核心的游戏开发框架。

正文

GameFramework将Unity部分与C#语言层逻辑分别拆分到UGF与GF程序集。引用 Game Framework | 基于 Unity 引擎的游戏框架 的一张图:

GF框架抽象出Module模块类,希望你将游戏拆分为各个模块。由Entry按顺序轮询各个模块。通过拆分为独立的模块,你可以更好清楚代码职责以及模块逻辑间的交互关系。

 

GF

GameFrameworkEntry

通过链表管理并维护着Module,如注册、获取、创建Module。

其中Module在轮询中有优先级顺序。通过module.Priority控制Module的轮询优先级,Entry在创建Module时候通过module.Priority的比较来改变其在链表的位置,做到轮询的顺序可控。

轮询

在GF层中,也会进行轮询。

轮询由Unity层中发起。

Module

GF层的C#类,每个Module继承GameFrameworkModule。

GF-ReferencePool

GF引用池。区别于GF的对象池,GF引用池用来储存C#普通的引用类型的类实例对象,即类实例。

GF之所以这么划分,我认为是因为很多情况下对象池存储的不只是类实例,可能会是一个实体,比如游戏物体等等。这种实体的实列化的操作不同于单纯的类实例化。

IReference接口

GF认为可能需要用到对象池存储的引用类型,都应该实现IReference接口。

在ObjectPool架构下,Object<T>类同样实现IReference接口。但是仅仅是为了复用Object<T>对象,复用这层壳,其中有关实体的具体的信息会在ObjectPool创建新的实体时被更新。

 

对于普通的引用类型类实例,要想找到一个唯一标识来标记池是很简单的,直接可以以类名或类的类型作为标识。GF就是使用Type来标识池。

池顾名思义是一群长得一样的对象的集合,在GF中,具体的池使用ReferenceCollection类。

内部使用Queue<IReference>队列维护这群对象。

  • Acquire:获取对象
  • Release:把对象归还给池。
  • Add:添加指定数量对象进入池,参数:传递要追加的数量
  • Remove: 从池中移除指定数量,参数:传递要追加的数量

GF-ObjectPool

Object<T> where T : ObjectBase Object封装一些关于对象的信息和操作并维护ObjectBase,如使用状态等等。

  • spawn 对象池获取对象生命周期的相关操作
  • UnSpawn 对象池回收对象生命周期的相关操作
  • Release 释放对象
  • Clear 清理对象,即重置。

ObjectBase 对象继承ObjectBase 同样有对象池对对象操作的一些生命周期事件

如果对象池里面没有可用对象,GF的对象池在Sapwn的时候不会发起主动创建。

我认为之所以作者这样设计的原因,是因为每种类型的对象 创建方式是不一样的,即要想做成更加的通用化,要避免在框架中实例化对象。将实例的操作交由用户来操作,因此开放了CanSapwn接口。判断池中没有可用对象的时候,用户在框架外部实例化,再将对象注册进池中。

  • 创建新的对象:Register
  • 从对象池中获取对象:Spawn
  • AllowMultiSpawn:允许对象被多次获取,即被取出不从池中移除,允许下次继续获取。(作法很多,只要让处于处于被使用状态的对象,仍然可以被获取到)

GF的ObjectPool的一个对象池可以同时存放外表不同,但类型一样的实体。

IObjectPoolManager

方法CreateMultiSpawnObjectPool表示 允许对象池加载外观不同的实体(name不同)。

比如气球,可以有各种各样的颜色。如果每一个颜色各自做成不同的预制件,那么这些气球预制件就属于外观不同但类型一样的实体。

EntityManager创建实体也用到了ObjectPool。

EntityManager默认是单外观对象池的,如下:

如果需要像上面那样多外观对象池,需要替换为CreateMultiSpawnObjectPool方法。

自动释放对象

AutoReleaseInterval:自动执行释放可释放对象方法的间隔秒数。只是执行释放方法,具体对象能否释放取决于对象的使用时间与对象ExpireTime过期时间。

在释放方法中筛选出那些过期的对象,释放这些过期的、可释放的对象。

让某个对象永远不被自动释放:objectpool.SetLocked

GF-EntityManager

AddEntityGroup:增加实体组

ShowEntity:显示实体(往实体组增加实体),从组中获取实体。获取不到,就加载资源。entity初始化并触发OnShow显示。

 

UGF

游戏入口

GameEntry作为UGF组件的管理器,UGF组件会通过GameEntry.RegisterComponent把Component注册到GameEntry中。

以方便在其他地方通过GameEntry来访问任意Component。

Helper

有四个固定类型的Helper,可以继承DefaultXXXHelper实现具体的Helper。

但感觉可以再动态,开放更多可操作空间给用户,比如固定的有这四个,除此之外用户也可以实现其他的Helper,并注册到help管理器里。

至于用途,由用户决定。

比如输出日志的Helper例子:

Application.logMessageReceived 收到日志消息时触发的事件。

FSM

与大部分FSM设计类似。唯一值得说的是,FSM组件的Inspector面板可以实时看到所有正在运行的状态机,以及这些状态机当前处于的状态、运行时间。

流程

通过流程,将不同的游戏状态进行解耦将是一个非常好的习惯。

实际用有限状态机把游戏整体状态管理了起来。

有限状态机的状态具有的接口,流程都有。

起到了很好的逻辑划分作用,也很方便后期调整各流程的顺序,甚至可以构建一颗流程树,根据不同环境走不同的流程分支。

如果想增加流程,只要派生自 ProcedureBase 类并实现自己的流程类即可使用。

流程的颗粒度不应该过细,避免把流程当作状态。

启动流程

通过IProcedureManager.StartProcedure<Type>来启动流程,其中StartProcedure实际是对FSM.Start的一层封装,即开始启动状态机。

在Procedure脚本上可以配置流程的开,其中Entrance Procedure表示流程的入口,即第一个流程。

UI框架

架构

按照范围(非类结构):

  • UIGroup 界面组
    • UIForm 界面对象(封装界面相关信息,不直接处理逻辑,只UIFormLogic发起界面生命周期的调用)
      • UIFormLogic 界面逻辑
      • UIFormInfo 界面状态信息 m_Paused、m_Covered(只用于UIManager)

关闭正在加载资源的界面

如果加载方式是异步加载的,可能会出现还没加载完,又需要取消或不需要打开这个界面。

GF在Close界面时,会将正在加载的界面,载入到m_UIFormsToReleaseOnLoad集合。

这样,当资源加载成功后,自动销毁这个界面。

刷新界面组

public void Refresh()
            {
                //获取链表头,即位于最上面的界面
                LinkedListNode<UIFormInfo> current = m_UIFormInfos.First;
                bool pause = m_Pause;
                bool cover = false;
                int depth = UIFormCount;
                //迭代更新所有界面
                while (current != null && current.Value != null)
                {
                    LinkedListNode<UIFormInfo> next = current.Next;
                  //Depth:界面组的深度
                    current.Value.UIForm.OnDepthChanged(Depth, depth--);
                    if (current.Value == null)
                    {
                        return;
                    }
                    //pause:临时变量pause如果为true,那么之后一定都为true
                  //但如果为false,之后可能会为true。
                  //cover:当界面大于1个时且cover一开始为false时
                  //之后cover一定为true。
                    if (pause)
                    {
                      ////相对当前界面来说是暂停状态
                        if (!current.Value.Covered)
                        {
                          //当前界面没有被覆盖。
                          //但是界面组是暂停的,说明一定被其他界面组覆盖,因此
                          //因此该界面组下面所有界面都应该更新为覆盖状态
                            current.Value.Covered = true;
                            current.Value.UIForm.OnCover();
                            if (current.Value == null)
                            {
                                return;
                            }
                        }
                        
                        if (!current.Value.Paused)
                        {
                          //当前界面没有被暂停
                          //但是界面组是暂停的,
                          //那么该界面组下面所有界面都应该更新为暂停状态
                            current.Value.Paused = true;
                            current.Value.UIForm.OnPause();
                            if (current.Value == null)
                            {
                                return;
                            }
                        }
                    }
                    else
                    {
                      //pause:相对当前界面来说不是暂停状态的
                      
                        if (current.Value.Paused)
                        {
                          //当前界面是暂停状态
                          //但pause此时不是暂停状态,因此界面肯定不是。
                            current.Value.Paused = false;
                            current.Value.UIForm.OnResume();
                            if (current.Value == null)
                            {
                                return;
                            }
                        }

                        if (current.Value.UIForm.PauseCoveredUIForm)
                        {
                          //是否暂停被覆盖的界面
                          //那么后面迭代的界面一定是被覆盖的,因此一定是暂停的
                          //故pause设为true
                            pause = true;
                        }

                        if (cover)
                        {
                            if (!current.Value.Covered)
                            {
                              //界面没覆盖,但界面组是覆盖的。
                              //因此更新界面 为覆盖状态
                                current.Value.Covered = true;
                                current.Value.UIForm.OnCover();
                                if (current.Value == null)
                                {
                                    return;
                                }
                            }
                        }
                        else
                        {
                          //相对当前界面来说,不是覆盖的
                            if (current.Value.Covered)
                            {
                              //当前界面是覆盖状态
                              //更新为不覆盖
                                current.Value.Covered = false;
                                current.Value.UIForm.OnReveal();
                                if (current.Value == null)
                                {
                                    return;
                                }
                            }
                            //下一个界面一定是被当前界面覆盖的
                            cover = true;
                        }
                    }

                    current = next;
                }
            }

 

文章推荐

Game Framework | 基于 Unity 引擎的游戏框架

作者:Miracle
来源:麦瑞克博客
链接:https://www.playcreator.cn/archives/unity/4291/
本博客所有文章除特别声明外,均采用CC BY-NC-SA 4.0许可协议,转载请注明!
THE END
分享
打赏
海报
GameFramework源码分析(一) – 基础架构与核心
前言 本篇并不旨在教会你如何使用GameFramework框架。因为笔者个人更倾向在游戏开发上,根据实际项目需求运用经验思想,来有针对性的完成项目架构,而非习惯的……
<<上一篇
下一篇>>
文章目录
关闭
目 录