序
Anywhere
是一个基于 Unity 引擎的GamePlay
框架,脱离 Unity 本身的开发方式,致力于采用无感开发的方式(极少的父类需要继承,无关乎生命周期),内部并提供了一个ECS
的上层抽象来开发它,但事实上你并不一定需要使用这个ECS
,你也可以使用自定义的上层,比如自己实现像MVCC
,或者是MVC
的上层封装。
设计目标
-
无框架化,它之所有不提供是为了更好的设计出不同品类的游戏,而我在近10年的游戏开发生涯中,我始终觉得框架的约束即使最大的约束,因为业务的多样性和非明确性的特点,一般游戏后期的一些奇奇怪怪的需求总是会迫使你绕过框架的约束从而形成屎山code,所以我希望
Anywhere
框架本身可以尽可能的简单,让开发者可以自由的去选择框架的约束。你可使用Anywhere
的一部分,或者全部,甚至是都不需要。 -
无
MonoBehaviour
编程设计,解除引擎原始的约束,更自由的编程方式,像之前开发游戏,一个角色身上可能挂在各式各样的组件,一旦后期业务变动很容易出现引用丢失或者维护起来更为困难,而且一些特殊的时候可能还需要设置一下脚本的执行顺序,给维护带来巨大的不便(如我之前所呆的项目各种口口相传的细节规范,让开发痛不欲生) -
模块化,
Anywhere
的一大特色,以像C library
的方式来组织模块,让模块之间可以互相调用,并且可以互相替换,让开发者可以自由的去选择模块的约束。选择何种内置模块,或者是自定义模块由开发者决定,这也是使用Anywhere
唯一的约束,你的模块可以是框架,也可以是Module
。 -
简单化,
Anywhere
本身只提最必要的一些基础组件,你可以重新实现,而并非是必要的,就是这么随意,就像它的名字一样。 -
自由化,游戏开发是自由的,是创造性的,
Anywhere
不会约束你干什么,你只需要关注你的想法,怎么做取决于你的点子。 -
去屎山,一旦使用
Anywhere
的Module
约束,那么它一定是强类型约束,这么做的目的是让业务不容易形成屎山,避免屎上添粪的情况出现(我所在的一些项目就是这样,后期持续性优化,由于业务量巨大,实难以支撑) -
非文档约束性组件控制器绑定,面向对象的模式必然导致代码变得复杂,因使用内嵌代替
OOP
,但显然C#做不到,需要额外的封装,但过于麻烦不符合Anywhere
的设计哲学,故通过静态泛型约束实现。 -
无任何反射调度,提高代码的运行速度。
-
Hybrid
集成(todo) -
开源,
Anywhere
很快将会进入开源状态。
结果
我个人使用
Anywhere
开发过三款独立项目(当然线上不算,因为技术理解成本原因,大部分情况下只会有部分模块被适配到其它线上项目),一款Roguelike,一款塔防,一款农场经营,这三款项目都使用了Anywhere
框架,完成大部分核心内容只花费了一个月不到的时间,从中也调整过需要设计上的变动,是为了更好的适应游戏开发。原计划是这三款项目的将会上架Steam
,但受限于美术资源,可能开发周期会被拉长,但不妨碍Anywhere
框架的持续迭代。
架构概念
Anywhere的一些设计思想不算是纯粹的
OOP
,它有ECS的概念,也有Type embedding
的概念,而且设计概念大部分是参考面过过程的思想,所以理解起来会比较困难
GameModule
GameModule
可以简单的认为是一个单例的定义,它是GameComponent
的基类,约束了创建和销毁的方式,它不会受任何生命周期的调度,它由Anywhere
持有,外部直接通过CreateOrGetGameModule<T>()
获取即可,无需理会生命周期。它一定是全局唯一。如 RmsModule
DatatableModule
,ArchviedModule
。
public class CustomModule:GameModule{
protected override void OnCtor(object userdata) {}
protected override void OnDestroy() {}
}
GameComponent
GameComponent
可以认为是一个种类功能的具体实现,它用于执行游戏运行中的实际逻辑,它可以拥有数据,也可以拥有逻辑,它是一个完成特定逻辑的实现,如TimeComponent
,NetPollComponent
,EnityManager
等,它表示一系列相似功能的集合实现。 而且它的每一个类型实现都是全局唯一,除非手动卸载,它的生命周期等同于全局。它与GameModule
的区别它需要执行游戏更新逻辑。
...
// Define a component that satisfies the Anywhere specification
public class CustomComponent:GameComponent{
protected override void OnCtor(object userdata) {}
protected override void OnDestroy() {}
protected override void OnEnable() {}
protected override void OnDisable() {}
protected override void OnUpdate(float delta,float unscaleDelta) {}
...
GameController
GameController
需要注意的是它是基于内嵌编程思想的设计,用于定制GameComponent
的默认行为逻辑(如果要求的话), 它可以动态开关,动态修改,它被设计成定义一个方法集,尽量不要带有任何数据对象,当然也可以带,不带的好处是为了复用用一个Controller
的时候,可以避免数据对象被重复创建,而带来的性能消耗。它在Runtime
时根据不同的策略而产生不同的效果。 当然若本身连个GameComponent
直接是父子或者是有相似度的关系(如不同种类的实体组,但数据结构以及逻辑相似)亦可复用GameController
而且带有一些数据也是没问题的。
-
一个
GameController
可以绑定多个GameComponent
,通过静态泛型约束避免非相关Controller
调用Component
出现不可预期的错误 (可在编译时期确定)。反之亦然 -
无需关注初始化时机,它会在必要的时候被初始化,因
Controller
不能比Component
的初始化时机更早,若此时Component
没有被创建的化,将会由框架自动创建并绑定。 -
还有就是提供一个
GameControllerGetter
辅助调用,提供更简单的访问,绕过OOP
为了得到其内嵌类型方法或数据而需要的额外包装的问题
...
// CustomController It is bound to the component it belongs to
public class CustomController:GameController<CustomComponent>{
// some code...
}
{
// Gets the specified instance through a constraint
GameControllerGetter<CustomComponent,CustomController>.Owner; // call custom controller. Owner == CustomComponent
}
...
Settings
-
GlobalSetting
约束了内部的GameModule
的全局配置,但若需要自己实现Module
的话,则需要手动传入userdata
数据。 -
GameSettings
定义了GameComponent
的一些数据信息,GameController
对它是ReadWrite
,而GameComponent
仅仅是ReadOnly
,尽量不要在内定义方法。内置Controller
和Component
并不会调用它们,如果你自己在外部调用时不符合规范的,但并不会禁止这种行为。只要不会出现预期之外的情况。
GameSettings
和 GameComponet
是一对一绑定。
public class CustomGameSettings:GameSettings<CustomComponent,CustomGameSettings>{
public int a;
public int b;
public int c;
protected override void OnLoad(){
// some handle...
}
}
EnityManager
EntityManager
是所有可见的Visable
物体的创建入口,之前提到过,如果按照GameComponent
的方式去实现一个也是能做的,但是为了减少冗余以及提高易用性的角度来看,可以单独抽象成一个统一的模块,包括对象池,以及Entity
模板,只需要通过EnitySettings
配置创建,即可实现自定义的EntityManager
。 若有一些特殊的逻辑,也可以直接继承EntityManager
实现其子类。
Toolkit
Toolkit
是许多有用的工具类集合实现,包括像Radix
,RC
,ARC
,Zmalloc
,Pool
等可复用结构及算法实现。