对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请求,使用上带来的好处是可以不需要使用协程,监听成功失败事件即可。