对GF的源码解析,学习设计思路,学习代码规范 GF官网 GF-API
看了GF的源码,感觉自己之前写的代码都是一堆垃圾!!!
源码阅读建议:与StarForce项目一同阅读

Base层

数据处理器,序列化工具,Log,事件池,引用池,任务代理池,其他扩展:Action,Func,变量封装(用于自定义数据结构),自定义链表

DataProvider 数据处理器

主要细节只需要看IDataProvider,IDataProviderHelper的实现上
IDataProvider,IDataProviderHelper主要实现2个接口:ReadData,ParseData

  1. IDataProvider 从ResourceManager中读取资源,为数据提供者
  2. IDataProviderHelper负责对具体数据的解析,为数据提供者帮助接口,用户使用上只需要实现帮助类即可
    对于IDataProviderHelper可能会引起误区,这里的ReadData是在IDataProvider读取资源成功时调用,这时已经拿到了需要的资源,可以直接使用或者再主动调用ParseData解析数据再使用。
  3. 执行顺序如:
    ConfigManager.ReadData()->IDataProvider.ReadData()->读取成功后->IDataProviderHelper.ReadData(),
    这时主动调用ConfigManager.ParseData()->IDataProvider.ParseData()->IDataProviderHelper.ParseData()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
IDataProvider
/// <summary>
/// 数据提供者接口。
/// </summary>
/// <typeparam name="T">数据提供者的持有者的类型。</typeparam>
public interface IDataProvider<T>
{
/// <summary>
/// 读取数据成功事件。
/// </summary>
event EventHandler<ReadDataSuccessEventArgs> ReadDataSuccess;

/// <summary>
/// 读取数据失败事件。
/// </summary>
event EventHandler<ReadDataFailureEventArgs> ReadDataFailure;

/// <summary>
/// 读取数据更新事件。
/// </summary>
event EventHandler<ReadDataUpdateEventArgs> ReadDataUpdate;

/// <summary>
/// 读取数据时加载依赖资源事件。
/// </summary>
event EventHandler<ReadDataDependencyAssetEventArgs> ReadDataDependencyAsset;

/// <summary>
/// 读取数据。
/// </summary>
/// <param name="dataAssetName">内容资源名称。</param>
/// <param name="priority">加载数据资源的优先级。</param>
/// <param name="userData">用户自定义数据。</param>
void ReadData(string dataAssetName, int priority, object userData);

/// <summary>
/// 解析内容。
/// </summary>
/// <param name="dataBytes">要解析的内容二进制流。</param>
/// <param name="startIndex">内容二进制流的起始位置。</param>
/// <param name="length">内容二进制流的长度。</param>
/// <param name="userData">用户自定义数据。</param>
/// <returns>是否解析内容成功。</returns>
bool ParseData(byte[] dataBytes, int startIndex, int length, object userData);
}
/// <summary>
/// 数据提供者辅助器接口。
/// </summary>
public interface IDataProviderHelper<T>
{
/// <summary>
/// 读取数据。
/// </summary>
/// <param name="dataProviderOwner">数据提供者的持有者。</param>
/// <param name="dataAssetName">内容资源名称。</param>
/// <param name="dataAsset">内容资源。</param>
/// <param name="userData">用户自定义数据。</param>
/// <returns>是否读取数据成功。</returns>
bool ReadData(T dataProviderOwner, string dataAssetName, object dataAsset, object userData);

/// <summary>
/// 读取数据。
/// </summary>
/// <param name="dataProviderOwner">数据提供者的持有者。</param>
/// <param name="dataAssetName">内容资源名称。</param>
/// <param name="dataBytes">内容二进制流。</param>
/// <param name="startIndex">内容二进制流的起始位置。</param>
/// <param name="length">内容二进制流的长度。</param>
/// <param name="userData">用户自定义数据。</param>
/// <returns>是否读取数据成功。</returns>
bool ReadData(T dataProviderOwner, string dataAssetName, byte[] dataBytes, int startIndex, int length, object userData);

/// <summary>
/// 解析内容。
/// </summary>
/// <param name="dataProviderOwner">数据提供者的持有者。</param>
/// <param name="dataString">要解析的内容字符串。</param>
/// <param name="userData">用户自定义数据。</param>
/// <returns>是否解析内容成功。</returns>
bool ParseData(T dataProviderOwner, string dataString, object userData);

/// <summary>
/// 解析内容。
/// </summary>
/// <param name="dataProviderOwner">数据提供者的持有者。</param>
/// <param name="dataBytes">要解析的内容二进制流。</param>
/// <param name="startIndex">内容二进制流的起始位置。</param>
/// <param name="length">内容二进制流的长度。</param>
/// <param name="userData">用户自定义数据。</param>
/// <returns>是否解析内容成功。</returns>
bool ParseData(T dataProviderOwner, byte[] dataBytes, int startIndex, int length, object userData);

/// <summary>
/// 释放内容资源。
/// </summary>
/// <param name="dataProviderOwner">数据提供者的持有者。</param>
/// <param name="dataAsset">要释放的内容资源。</param>
void ReleaseDataAsset(T dataProviderOwner, object dataAsset);
}

EventPool 事件池

实现事件接口:订阅,取消订阅,抛出事件,立即抛出事件

  1. Fire 抛出事件:线程安全,将待执行事件放入队列,下一帧执行
  2. FireNow 立即抛出事件:线程不安全
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
//
// 摘要:
// 事件管理器接口。
public interface IEventManager
{
//
// 摘要:
// 抛出事件,这个操作是线程安全的,即使不在主线程中抛出,也可保证在主线程中回调事件处理函数,但事件会在抛出后的下一帧分发。
//
// 参数:
// sender:
// 事件源。
//
// e:
// 事件参数。
void Fire(object sender, GameEventArgs e);
//
// 摘要:
// 抛出事件立即模式,这个操作不是线程安全的,事件会立刻分发。
//
// 参数:
// sender:
// 事件源。
//
// e:
// 事件参数。
void FireNow(object sender, GameEventArgs e);
//
// 摘要:
// 设置默认事件处理函数。
//
// 参数:
// handler:
// 要设置的默认事件处理函数。
void SetDefaultHandler(EventHandler<GameEventArgs> handler);
//
// 摘要:
// 订阅事件处理函数。
//
// 参数:
// id:
// 事件类型编号。
//
// handler:
// 要订阅的事件处理函数。
void Subscribe(int id, EventHandler<GameEventArgs> handler);
//
// 摘要:
// 取消订阅事件处理函数。
//
// 参数:
// id:
// 事件类型编号。
//
// handler:
// 要取消订阅的事件处理函数。
void Unsubscribe(int id, EventHandler<GameEventArgs> handler);
}

Log

对Log进行封装,ILogHelper对Log的具体实现

ReferencePool 引用池

为了降低因大量产生类对象而导致的内存分配,设计了引用池的概念,来将用完的对象清理并缓存起来,供后续使用。
实现IReference接口,通过ReferencePool.Acquire()获取

TaskPool 任务池

这个任务池主要做资源异步加载,下载等异步操作的任务

  • TaskBase:只是任务数据
  • ITaskAgent:任务代理,处理任务的具体行为
    如DownloadAgent.cs处的使用,Agent只处理该Task中数据,并在执行中通知对应Helper执行具体下载逻辑
  • TaskInfo:用于Debug等展示的信息
  • TaskPool:任务池,管理ITaskAgent并执行

Variable 变量

变量封装

Version 版本号

方便版本号管理,版本号在资源更新时需要使用,判断旧资源与新资源的版本号。

封装的基础类型

类 注释
GaneFrameworkAction 封装多参数委托
GameFrameworkFunc 封装多参数委托
GameFrameworkEntry 游戏入口
GameFrameworkEventArgs 事件数据封装
GameFrameworkException 异常抛出封装
GameFrameworkLinkedList 带缓存的LinkedList(链表)
GameFrameworkLinkedListRange 有范围的链表,即理解为LinkedList中的一小段
GameFrameworkModule 模块的封装基类,统一管理各类模块Manager
GameFrameworkMultiDictionary 多值字典,Value为链表
GameFrameworkSerializer 序列化器,

Config

经过了对Base层的理解,Config层就很轻松弄明白了。
主要封装了数据读取,解析,获取。
执行顺序:
ConfigManager.ReadData()->IDataProvider.ReadData()->读取成功后->IDataProviderHelper.ReadData(),
ConfigManager.ParseData()->IDataProvider.ParseData()->IDataProviderHelper.ParseData()
使用上只需要:修改对应Helper即可

DataNode

树状数据节点,个人使用最多就是GetOrAddNode(string)

DataTable

  • 使用DataTable,扩展一下可以很方便与excel使用
  • 实现Helper类解析Excel产生的bytes数据即可
接口 注释
IDataTable 表(数据容器,管理多条数据)
IDataRow 数据项(一条数据)
IDataHelper 数据解析帮助类
IDataTableManager 管理所有表

Debugger

运行时的Debugger界面,使用上很方便,具体就是打印多种不同信息

Download

下载任务都是异步操作,因此需要等待,这时Base层定义好的TaskPool就有了作用
下载步骤:

  1. DownloadTask携带下载数据:下载路径,保存路径,下载状态,缓冲区大小等
  2. DownloadAgent处理任务数据,监听下载状态变化:下载数据更新,下载长度更新,下载完成,下载失败
  3. IDownloadAgentHelper实现实际下载逻辑,如UnityWebRequestDownloadAgentHelper为例,使用UnityWebRequest发送实际下载请求,DownloadHandler抛出下载数据更新事件
  4. DownloadCounter计算下载速度
  5. IDownloadManager即下载管理器,管理任务池以及开放对应下载接口

Entity

Entity即实体
EntityManager->EntityGroup->Entity
每个Entity有独一无二的id,Manager通过字典存储,方便管理Entity
每个EntityManager管理EntityGroup,EntityGroup只管理组内的Entity
每个Entity实际生成时通过EntityGroup中的对象池子管理
ShowEntity流程:ReourcesManager加载资源->IEntityHelper实例化->注册进EntityGroup的对象池中->调用Entity生命周期函数OnInit->OnShow

Event

对Base层的EventPool的一层封装

FileSystem

  • FileSystem对应一个物理文件,其中保存多个文件数据,每个文件数据可理解为一个数据类型的二进制数据,加载时解析成对应类型的数据
    其中有一个概念数据块,即每一个文件数据都是一个数据块,但是当同名数据更新时,文件数据会更换一个空闲块进行存储。
  • IFileSystem fileSystem = fileSystemComponent.CreateFileSystem(fullPath, FileSystemAccess.ReadWrite, maxFileCount, maxBlockCount);
    创建文件系统时,输入的maxFileCount,以及maxBlockCount对应最大文件个数以及最大数据块个数,目前FileSystem还不支持文件系统自动扩容,需一开始设定好,且maxFileCount <= maxBlockCount,在更新文件数据时会更换数据块,因此更新越频繁的数据maxBlockCount需要越大,以保证数据更新有足够的碎片空间进行修改。

FSM

有限状态机

Localization

本地化语言,实现上与Config类似,只是在不同Language下读取不同的文件下存储的keyValue

Network

接口 注释
INetworkManager 管理NetworkChannel
INetworkChannel 建立链接,处理消息接收发送
INetworkChannelHelper 消息序列化反序列化帮助类
IPacketHandler 处理协议,每个协议有其ID,通过ID区分
IPacketHeader 消息头部信息主要记录长度,可自行添加头部信息携带数据,实际消息存在长度,发包是存在数据的粘包以及分包,这时需要头部信息判断消息是否接收完毕
  • 使用上,客户端需要链接几处服务器就创建几个INetworkChannel分别链接,发消息也是同样每个链接处理各自的消息如:实际服务器一般存在:Gate服务器,Game服务器,Chat服务器,Friend服务器等等,使用NetworkManager可以很方便管理这种分布式服务器的链接
  • 使用网络通信的数据结构最好使用protobuf,是目前最适合用于网络通信的数据结构,可以参考StarForce中Network模块心跳包实现
  • 发送以及接收消息都是异步的,且客户端收到消息在非主线程,需要事件系统通过线程安全方式抛出

ObjectPool

对象池,其中有提供CreateSingleSpawnObjectPool,CreateMultiSpawnObjectPool
理解为:池子里的资源能够同时被使用一次或使用多次,缓存资源在Spawn后以及Release前都算是在使用下,MultiSpawnObjectPool能多次Spawn同一资源(目前还没使用过,感觉没什么作用,我目前理解为这个池子只管理了一个缓存资源,在任何情况下都能Spawn出来使用),SingleSpawnObjectPool是我们通常情况下的对象池。

Procedure

游戏进程管理,是FSM的实现,可以参考StarForce的游戏启动流程

Resource

  • 资源管理模块设计整个框架,几乎每个模块都使用到资源管理器,普通的资源管理只是提供资源打包,加载,卸载等操作,这个强大的资源管理器还提供了可视化操作界面,资源使用分析,以及资源更新处理。
  • 资源模式一共有三种:单机模式,预下载的可更新模式,使用时下载的可更新模式。 关于三种模式的使用方法参考StarForce的启动流程,非常详细。
  • 关于资源组的思想,资源组类似于Unity中的AssetBundle包即压缩包,一个包里包含多个资源文件,方便管理,资源还可以设置文件系统,多个资源组放进一个文件中。

编辑器中的使用方式

  1. 使用前需要先设置ResourceEditor.xml文件配置,是ResourceEditor的过滤配置,再导入GF框架后可以直接复制StarForce中的配置
  2. 关于ResourceEditor有些无法过滤的文件:如bytes文件,atlas文件(将图片打成一个包并不会自动生成图集,Load出来还是Texture2D格式)等,可以手动扩展ResourceEditor的过滤配置修改脚本ResourceEditorController.cs过滤文件设置
  3. 进行第一步后就可看到设置路径下的所有合法资源文件了
  4. Resource Builder:选择打包平台,以及路径,还可以选择打包事件处理器,如StarForce将打完的包复制了一份到StreamingAssets路径下,具体查看接口IBuildEventHandler.cs

实际运行中检查并更新资源流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 根据StarForce启动流程

// ---单机模式
// 注意:使用单机模式并初始化资源前,需要先构建 AssetBundle 并复制到 StreamingAssets 中,否则会产生 HTTP 404 错误
GameEntry.Resource.InitResources(OnInitResourcesComplete);

// ---更新模式
// 1.向服务器请求版本信息
GameEntry.WebRequest.AddWebRequest(Utility.Text.Format(GameEntry.BuiltinData.BuildInfo.CheckVersionUrl, GetPlatformPath()), this);

// 2.拿到服务器的VersionInfo,包含:最新版本信息,下载地址等等信息,设置资源更新下载地址并检查是否有更新
GameEntry.Resource.UpdatePrefixUri = Utility.Path.GetRegularPath(m_VersionInfo.UpdatePrefixUri);
GameEntry.Resource.CheckVersionList(m_VersionInfo.InternalResourceVersion);

// 3.进行版本更新
GameEntry.Resource.UpdateVersionList(m_VersionInfo.VersionListLength,m_VersionInfo.VersionListHashCode,m_VersionInfo.VersionListZipLength,m_VersionInfo.VersionListZipHashCode);

// 4.检查资源是否需要更新
GameEntry.Resource.CheckResources(OnCheckResourcesComplete);

// 5.更新资源
GameEntry.Resource.UpdateResources(OnUpdateResourcesComplete);

dll源码解析

Scene

场景加载,卸载管理,对ResourceManager的LoadScene,UnLoadScene封装了一层

Setting

游戏设置,与Config实现类似

Sound

非常好用的音效管理器,高效,简洁,功能强大

接口 注释
ISoundAgent 一个音效有一个代理,内部逻辑代理接口,主要负责处理声音开启暂停等
ISoundAgentHelper 声音播放实际逻辑
ISoundGroup 声音组
ISoundGroupHelper 声音组帮助器,StarForce提供默认用于处理混音的组
ISoundHelper 释放资源
ISoundManager 管理音效播放,暂停,恢复以及音效组

UI

设计思路与Sound以及Entity类似,UIGroup组管理UIForm,UIManager一起管理,需要注意的是同类型UIForm可以开启多个,只单个存在的界面需要自己判断是否再次开启,可以参考Demo中的写法

Utility

提供了各种通用函数,方便使用

WebRequest

Web请求是异步的,通过TaskPool来代理Web请求,使用上带来的好处是可以不需要使用协程,监听成功失败事件即可。