DatatableModule
是一个基于 kpb 编码的配置文件管理系统,它定义了一个配置文件的数据结构,并提供了相应的API来操作和访问配置文件。在Zen
中它是一个GameComponent
。它包含了一个代码生成器,和数据解析器和编码器。
数据表的加载只有4(2)个接口 LoadDatatable<T>(bool lazy)
GetRow<T>(int row)
以及一套同作用的异步接口,前者获取一整张表,后者获取某表的某一行数据,即对应的数据结构体。
Datatable
静默行为是 Lazy load
对于同一张数据表,它只会根据需要读取指定行然后才缓存,而不是一次性读取所有表格。
lazy load
,它不会加载表格的所有数据,而是按照需要动态一部分一部分的加载,直到全部加载完毕。
DatatableModule
加载接口提供同步和异步两种模式,也可以加载远程资源,依赖于 Resource
DatatableModule
提供代码和数据生成的编辑器,无需关注实现逻辑。
多种类型数据支持 bool,int,float,string,binary,int*,float*,string*
,满足绝大部分场景
(2024/05/23)增加了定长数据的支持,减少内存消耗。
基于kproto
编码协议,极小的二进制文件,以及极快的编解码速度。
栈内存映射,大部分情况下不需要开辟堆空间,节省一部分堆内存的分配,减少Mono Reserved
的分配。
EntityManager
是一个很重要的模块,像游戏开发本身就是视觉感知,而EntityManager
是对所有场景物体的统一抽象封装, 并提供一系列通用操作,EntityManager
本身是一个抽象类,提供了一定程度的通用性操作,但针对一些特殊情况我们还是需要针对特定问题特定实现(千万不想要想着做平,来自某家公司的教训:))
EntityManager
附带一个默认的 EntityController
,提供一些常规的实体控制,像 Zen
的UI框架就是基于 EntityManager
的一个具体实现。
抽象描述一个物体的实体,它的生命周期函数定义类似于GameComponent
,但是它的调度不由GameEntry
而是 GameComponent
。
简单来说,要创建一个物体首先我们需要定义它的逻辑模板(骨架)以及它的数据(描述)
举个例子
public struct EnemyData:IEntityData{
// 对于实体数据接口,资源名必不可少
public string Assets {get;set;}
public void OnCtor(){
// 有些时候,实体的数据更关卡等级或这地图这类外部数据挂钩,可能需要在这里动态设置一次
}
}
// 定义Entity的逻辑模板
public struct Enemy:Entity {
protected EnemyData userdata;
protected void OnCtor(){
// init setup...
}
protected void OnUpdate(float delta,float unscaleDelta){
// loop logic.
}
}
void dosomething(){
// 直接在默认的EntityManager中创建一个实体
GameEntry.GetComponent<EntityManager>().Load<Enemy>(new EnemyData());
}
对于实体的销毁,像敌人死亡,特效消失之类的,仅仅只需要设置一个 Alive
属性,即可完成,生命周期由EntityManger
自行决断。
需要注意的是 继承IEntityData
的数据模板是会一直复用的,它与 Entity
的复用规则不同,后者复用的是Entity
所持有的实体,因为它本身只是一系列函数集合且很少会带有数据并不会占用太多的内存,而前者大部分情况下是都是通过读表获取,复杂的实体数据可能会导致内存异常大,所以保留实体数据是EntityManager
的默认行为。
EntityManager
可能需要在某些大量对象时使用对象池(Entity
),或者内存池(EntityData
),但在大多数情况下,框架并不清楚是否需要对象池,或者是需要一个定长周期的物体(子弹或者特效)。
这个时候需要设置就非常有必要了,可以决断出是否需要且生命周期(keepalive)或者是需要多大的对象池,以及自动孵化的频率了。
Resource
是一个全自动化,且简单易用的资源管理系统,继承于GameComponet
实现 ,它内部使用RC
的方式来管理资源的引用,且不需要手动显示释放,而是通过拦截finalizer的方式,来达到释放的目的,比手动安全性更高,也更易用。
它仅提供了两组接口资源加载接口LoadAsset
LoadAssetAsync
,。并提供了虚拟化的方式加载,在编辑器模式下不会真正的构建AssetBundle
包,从而提供开发效率。
Resource
也包括流场景构建,可以将整个场景都作为热更新资源。
自动化检测文件变动
通过Radix
算法,监控资源变动,来自动构建manifest
。上层只需要关注资源本身,无需关注AssetBundle
包的构建。做到对上层完全无感。
自动化引用计数
通过 RC
+ finalizer,监控资源的引用,来自动释放资源。且无需轮询检查资源的引用计数,提供更好的性能,做到对上层完全无感。也没有手动释放接口,提高安全性和易用性
自动化冗余剔除 在构建的时候自动收集冗余资源,如像A->(C,D) B->(C) 此时(A,D)将会作为一个的bundle,而C则会单独作为一个bundle
Profile资源使用分析,用以在运行时统计各个资源的引用和加载数
自定义流处理,支持加密等自定义操作
多种模式支持
Builtin: 内建资源,默认将资源插入母包中。
Remote: 远程资源模式,仅需要配置远程资源地址即可,所有资源不会在本地存盘,只会在内存中使用,使用完后立即释放,防止被反编译资源
无感知资源更新
一般出现资源更新的时候,都是先下载到本地,然后再通过加载本地资源的方式实现, 现由FS
映射的远程目录,通过分流的方式,可以实现一边从远程直接读取,并copy另外一部分流到本地磁盘,减少重新读盘的情况。
对于下载过程对于玩家而言完全无感,其本身下载速度是可以被控制,当前并没有暴露此接口(因为带宽值是动态变化的,无法根据不同的条件找到一个合理值),只是需要设定一个百分率阈值,内部自动计算下载速度尽可能保证达到预期值。
淘汰策略 当某个Assetbundle的引用计数为0时候,底层不会立即释放它,而是根据它过往的加载次数判断下一次淘汰次数,越大将会越晚淘汰,当然也提供了强制卸载函数。
现阶段 已实现Resource
是依赖于Assetbundle
后续会抽象出一层 FS
以提供自定义流提供更多可操作性和安全性。
Resource
现在可以说是完全不能脱离Unity
独立运行,这不是一个好的方式,我期望提供更高的抽象满足多个引擎的需求。当完成这一步时只需要在Unity
中需要手动实现一个Adaptor
即可。
Zen
是一个基于 Unity 引擎的GamePlay
框架,脱离 Monobehaviour
开发,致力简化开发流程。内部提供了一个类ECS
的架构满足开发,你也可以使用自定义的上层,比如自己实现像MVCC
,或者是MVC
的上层封装。让开发聚焦在游戏玩法而非一些底层架构上。
Zen的一些设计思想不算是纯粹的
OOP
,它有ECS的概念,也有type embedding
的概念,而且设计概念大部分是参考面过过程和内嵌的设计思想,所以理解曲线会比较困难
无框架化,它之所有不提供是为了更好的设计出不同品类的游戏,而我在近10年的游戏开发生涯中,我始终觉得框架的约束即使最大的约束,因为业务的多样性和非明确性的特点,一般游戏后期的一些奇奇怪怪的需求总是会迫使你绕过框架的约束从而形成屎山code,所以我希望Zen
框架本身可以尽可能的简单,让开发者可以自由的去选择框架的约束。你可使用Zen
的一部分,或者全部,甚至是都不需要。
无MonoBehaviour
编程设计,解除引擎原始的约束,更自由的编程方式,像之前开发游戏,一个角色身上可能挂在各式各样的组件,一旦后期业务变动很容易出现引用丢失或者维护起来更为困难,而且一些特殊的时候可能还需要设置一下脚本的执行顺序,给维护带来巨大的不便(如我之前所呆的项目各种口口相传的细节规范,让开发痛不欲生)
模块化,Zen
的一大特色,以像C library
的方式来组织模块,让模块之间可以互相调用,并且可以互相替换,让开发者可以自由的去选择模块的约束。选择何种内置模块,或者是自定义模块由开发者决定,这也是使用 Zen
唯一的约束,你的模块可以是框架,也可以是Module
。
简单化,Zen
本身只提最必要的一些基础组件,你可以重新实现,而并非是必要的
自由化,游戏开发是自由的,是创造性的,Zen
不会约束你干什么,你只需要关注你的想法,怎么做取决于你的点子。
非文档约束性组件控制器绑定,面向对象的模式必然导致代码变得复杂,因使用内嵌代替OOP
,但显然C#做不到,需要额外的封装,但过于麻烦不符合Zen
的设计哲学,故通过静态泛型约束实现。
无任何反射调度,提高代码的运行速度。
高度继承的构建管线,非常完善且易使用基建设计(配置,资源,本地化,网络等)
ZenRpc
现在可用了,无关乎网络,无缝与 skynet-go
Zen
不鼓励继承,其内部设计也是符合此规范,所以整个拓扑架构更平整