Treasure / anywhere 客户端框架概述

Created Sat, 01 Apr 2023 00:00:00 +0000 Modified Thu, 16 May 2024 08:42:33 +0000
2788 Words

Anywhere 是一个基于 Unity 引擎的GamePlay框架,脱离 Unity 本身的开发方式,致力于采用无感开发的方式(极少的父类需要继承,无关乎生命周期),内部并提供了一个ECS的上层抽象来开发它,但事实上你并不一定需要使用这个ECS,你也可以使用自定义的上层,比如自己实现像MVCC,或者是MVC的上层封装。

设计目标

  1. 无框架化,它之所有不提供是为了更好的设计出不同品类的游戏,而我在近10年的游戏开发生涯中,我始终觉得框架的约束即使最大的约束,因为业务的多样性和非明确性的特点,一般游戏后期的一些奇奇怪怪的需求总是会迫使你绕过框架的约束从而形成屎山code,所以我希望Anywhere框架本身可以尽可能的简单,让开发者可以自由的去选择框架的约束。你可使用Anywhere的一部分,或者全部,甚至是都不需要。

  2. MonoBehaviour编程设计,解除引擎原始的约束,更自由的编程方式,像之前开发游戏,一个角色身上可能挂在各式各样的组件,一旦后期业务变动很容易出现引用丢失或者维护起来更为困难,而且一些特殊的时候可能还需要设置一下脚本的执行顺序,给维护带来巨大的不便(如我之前所呆的项目各种口口相传的细节规范,让开发痛不欲生)

  3. 模块化,Anywhere的一大特色,以像C library的方式来组织模块,让模块之间可以互相调用,并且可以互相替换,让开发者可以自由的去选择模块的约束。选择何种内置模块,或者是自定义模块由开发者决定,这也是使用 Anywhere 唯一的约束,你的模块可以是框架,也可以是Module

  4. 简单化,Anywhere 本身只提最必要的一些基础组件,你可以重新实现,而并非是必要的,就是这么随意,就像它的名字一样。

  5. 自由化,游戏开发是自由的,是创造性的,Anywhere 不会约束你干什么,你只需要关注你的想法,怎么做取决于你的点子。

  6. 去屎山,一旦使用AnywhereModule约束,那么它一定是强类型约束,这么做的目的是让业务不容易形成屎山,避免屎上添粪的情况出现(我所在的一些项目就是这样,后期持续性优化,由于业务量巨大,实难以支撑)

  7. 非文档约束性组件控制器绑定,面向对象的模式必然导致代码变得复杂,因使用内嵌代替OOP,但显然C#做不到,需要额外的封装,但过于麻烦不符合Anywhere的设计哲学,故通过静态泛型约束实现。

  8. 无任何反射调度,提高代码的运行速度。

  9. Hybrid集成(todo)

  10. 开源,Anywhere很快将会进入开源状态。

结果

我个人使用Anywhere开发过三款独立项目(当然线上不算,因为技术理解成本原因,大部分情况下只会有部分模块被适配到其它线上项目),一款Roguelike,一款塔防,一款农场经营,这三款项目都使用了Anywhere框架,完成大部分核心内容只花费了一个月不到的时间,从中也调整过需要设计上的变动,是为了更好的适应游戏开发。原计划是这三款项目的将会上架Steam,但受限于美术资源,可能开发周期会被拉长,但不妨碍Anywhere框架的持续迭代。

架构概念

Anywhere的一些设计思想不算是纯粹的OOP,它有ECS的概念,也有Type embedding的概念,而且设计概念大部分是参考面过过程的思想,所以理解起来会比较困难

GameModule

GameModule 可以简单的认为是一个单例的定义,它是GameComponent的基类,约束了创建和销毁的方式,它不会受任何生命周期的调度,它由Anywhere持有,外部直接通过CreateOrGetGameModule<T>()获取即可,无需理会生命周期。它一定是全局唯一。如 RmsModule DatatableModuleArchviedModule

public class CustomModule:GameModule{

    protected override void OnCtor(object userdata) {}

    protected override void OnDestroy() {}
}

GameComponent

GameComponent 可以认为是一个种类功能的具体实现,它用于执行游戏运行中的实际逻辑,它可以拥有数据,也可以拥有逻辑,它是一个完成特定逻辑的实现,如TimeComponentNetPollComponent,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而且带有一些数据也是没问题的。

  1. 一个GameController可以绑定多个GameComponent,通过静态泛型约束避免非相关Controller调用Component出现不可预期的错误 (可在编译时期确定)。反之亦然

  2. 无需关注初始化时机,它会在必要的时候被初始化,因Controller不能比Component的初始化时机更早,若此时Component没有被创建的化,将会由框架自动创建并绑定。

  3. 还有就是提供一个 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

  1. GlobalSetting 约束了内部的GameModule 的全局配置,但若需要自己实现Module的话,则需要手动传入userdata数据。

  2. GameSettings定义了 GameComponent 的一些数据信息,GameController 对它是ReadWrite,而 GameComponent仅仅是 ReadOnly,尽量不要在内定义方法。内置ControllerComponent并不会调用它们,如果你自己在外部调用时不符合规范的,但并不会禁止这种行为。只要不会出现预期之外的情况。

GameSettingsGameComponet是一对一绑定。

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 是许多有用的工具类集合实现,包括像RadixRC,ARC,Zmalloc,Pool等可复用结构及算法实现。

目录

ECS

RMS

Datatable

NetPollComponent

EntityManager

ReGoap