我们如何从头开始做我们的小团结



我们公司拥有自己的游戏引擎,该引擎可用于所有开发的游戏。 它提供了所有重要的基本功能:

  • 渲染
  • 使用SDK;
  • 使用操作系统;
  • 与网络和资源。

但是,它缺少Unity这么重视的东西-用于组织场景和游戏对象以及它们的编辑器的便捷系统。

在这里,我想告诉我们我们如何介绍了所有这些便利设施以及我们来到了哪里。

现在是什么


现在,我们在Unity中拥有类似于所有重要子系统和编辑器的组件系统。 但是,由于我们是从特定项目的需求出发的,所以存在很大的差异。

我们有存储在场景中的视觉对象。 这些对象由按层次结构组织的节点组成,每个节点可以具有许多实体,例如:

  • 变换-节点的变换;
  • 组件-用于渲染,只能有一个或根本没有。 组件是精灵,网格,粒子和其他可以显示的实体。 与Unity最接近的等效项是Renderer。
  • 行为-负责行为,可能有几种。 这是Unity中MonoBehaviour的直接类似物,任何逻辑都写在其中;
  • 排序是一个实体,负责场景中节点的显示顺序。 由于我们的系统应该易于集成到已经运行的游戏中,并具有用于显示对象的现有多样逻辑,因此必须能够将新实体集成到旧实体中。 因此,排序使您可以将显示顺序的控制权转移到外部代码。

与Unity一样,程序员创建其组件,行为或排序。 为此,只需编写一个类,重新定义必要的事件(Update,OnStart等),并以特殊方式标记必要的字段。 在UnrealEngine中,这是通过宏完成的,我们决定在注释中使用标签。

/// @category(VSO.Basic) class SpriteComponent : public MaterialComponent { VISUAL_CLASS(MaterialComponent) public: /// @getter const std::string& GetId() const; /// @setter void SetId(const std::string& id); protected: void OnInit() override; void Draw() override; protected: /// @property Color _color = Color::WHITE; /// @property Sprite _sprite; }; 

在课堂上,考虑到标签,将生成所有代码,这对于保存和加载数据,编辑器的工作,支持克隆和其他小功能都是必需的。

存储在可视对象中的实体不仅支持自动序列化和生成编辑器,而且还支持任何类。 为此,从特殊的Serializable类继承它并用标签标记必要的属性就足够了。 而且,如果您希望类的实例是全部资产(来自Unity的ScriptableObject的类似物),则应从Asset类继承该类。

结果,该库提供了快速开发新功能的机会。 现在,开发游戏的部分工作(例如,创建效果,布局UI,游戏场景设计)可以移交给能够比程序员更好地处理游戏的专家。

主要块




代码生成


对于许多工作的系统,您需要编写大量的例程代码,由于C ++中缺少反射( 反射 -访问程序代码中有关类型的信息的能力),因此这是必需的。 因此,我们生成了大多数此技术代码。

生成器是一组python脚本,用于解析头文件并在其基础上生成必要的代码。 对于灵活的生成设置,注释中使用了特殊标记。

我们可以为以下子系统生成代码:

  • 序列化-用于从磁盘或通过网络传输时保存/加载数据。 稍后将更详细地考虑。
  • 反射库的绑定-用于自动向数据显示编辑器。 将在有关编辑器的章节中进行讨论。
  • 克隆实体的代码-用于在编辑器和游戏中克隆实体。
  • 用于我们轻量级运行时反射的代码。

→可以在此处找到针对一个类生成的代码的示例

解析C ++


解决头文件解析问题的几乎所有选项都导致使用clang解析代码。 但是经过实验,很明显,这种解决方案的速度根本不适合我们。 而且,我们不需要clang提供的功能。

因此,找到了另一个解决方案: CppHeaderParser 。 这是一个可以读取头文件的python单一文件库。 它非常原始,不遵循#include,跳过宏,不解析字符并且运行非常迅速。

直到今天我们仍然使用它,但是,我们必须进行大量更改以修复错误并扩展我们的功能,尤其是添加了对C ++ 17中的创新的支持。

我们希望避免与代码生成状态不确定性有关的误解。 因此,决定应该完全自动地进行生成。 我们使用CMake,其中的生成在每次编译时都开始(我们无法将生成配置为仅在依赖项发生更改时才开始)。 为了节省时间,避免烦人,我们存储了一个缓存,其中包含所有文件和目录内容的解析结果。 结果,空闲的代码生成开始仅需几秒钟。

代码生成器


有了一代,一切都变得更加简单。 有很多库可用于从模板生成任何内容。 我们选择Templite + ,因为它很小,具有必要的功能并且可以正常工作。

有两种生成方法。 第一个版本包含许多条件,检查和其他代码,因此模板本身是最小的,并且大多数逻辑和产生的文本都在python代码中。 之所以方便,是因为在python代码中比在模板中编写更方便,并且易于拧紧任意棘手的逻辑。 但是,这也很糟糕,因为python代码与大量的C ++代码行混合在一起,不便于读取或编写。 使用过的python生成器简化了这种情况,但并没有从整体上消除问题。

因此,当前版本基于模板,而python代码只是准备必要的数据,现在看起来好多了。

序列化


对于序列化,考虑了各种库:protobuf,FlexBuffers,谷物等。

带有代码生成的库(Protobuf,FlatBuffers等)不适合使用,因为我们具有手写结构,无法将生成的结构集成到用户代码中。 将仅用于序列化的类数量增加一倍太浪费了。

谷类库似乎是最佳的选择-良好的语法,清晰的实现,方便地生成序列化代码。 但是,其二进制格式与大多数其他库的格式都不适合我们。 重要的格式要求是与硬件的独立性(无论字节顺序和位深如何,都应读取数据),二进制格式应便于从python写入。

用python编写二进制文件非常重要,因为我们希望拥有一个独立于平台和独立于项目的通用脚本,该脚本会将数据从文本视图转换为二进制视图。 因此,我们编写了一个脚本,事实证明这是一个非常方便的序列化工具。

主要思想来自谷物,它基于用于读写数据的基本档案。 从中创建不同的继承人,这些继承人以不同的格式实现记录:xml,json,二进制。 序列化代码由类生成,并使用这些归档来写入数据。



编辑


我们使用ImGui库进行编辑,并在其中编写了所有主要的编辑器窗口:场景内容,文件和资产查看器,资产检查器,动画编辑器等。

主编辑器代码是手工编写的,但是要查看和编辑特定类的属性,我们使用rttr库,为其生成的分箱以及可以与rttr一起使用的通用检查器代码。

反射库-rttr


为了组织C ++中的反射,选择了rttr库。 它不需要类本身的干预,具有方便易懂的API,支持类型(例如智能指针)的集合和包装器,并具有注册包装器的功能,并允许您执行必要的操作(创建类型,对类成员进行迭代,更改属性,调用方法等)。

它还使您可以像处理常规字段一样使用指针,并使用空对象模式,这大大简化了使用它的过程。

该库的缺点是它体积大且速度不快,因此我们仅将其用于编辑器。 在用于处理对象参数(例如动画系统)的游戏代码中,我们使用自己制作的最简单的反射库。

rttr库要求编写一个包含类的所有方法和属性的声明的绑定。 此绑定是从python代码为所有需要编辑支持的类生成的。 由于rttr可以为任何实体添加元数据,因此代码生成器可以为类成员设置不同的设置:工具提示,数字字段的可接受值边界的参数,字段的特殊检查器等。这些元数据在检查器中用于显示编辑界面。

→可以在此处找到用于在rttr中声明类的示例代码

检验员


编辑器本身的代码很少直接与rttr一起使用。 最常用的层是对象能够为其绘制一个ImGui检查器。 这是手写代码,可处理rttr中的数据并为其绘制ImGui控件。

为了自定义数据编辑界面的显示,使用了在rttr中注册期间指定的元数据。 我们支持所有原始类型,集合,可以创建按值和按指针存储的对象。 如果类成员是指向基类的指针,则可以在创建过程中选择特定的继承人。

同样,检查器代码支持取消操作-更改值时,将创建一条命令来更改数据,然后可以回滚该数据。

到目前为止,我们还没有一个能够查看和保存原子变化的系统来确定原子变化。 这意味着我们不支持将对象的更改属性保存到场景并在加载预制件后应用这些更改。 更改对象的属性时,也不会自动创建动画轨道。

Windows和编辑器


目前,在我们的编辑器,代码生成和资产创建系统的基础上,创建了许多不同的子系统和编辑器:

  • 游戏界面系统提供了灵活方便的布局,并包括所有必要的界面元素。 她为她制作了一个可视化的窗口行为脚本系统。
  • 切换动画状态的系统类似于Unity中动画中的状态编辑器,但是其操作原理有所不同,并且具有广泛的应用。
  • 任务和事件的设计者几乎无需程序员参与,即可灵活地自定义游戏事件,任务和教程。

在开发所有这些子系统和编辑器时,我们仔细研究了UnityUnreal Engine,并尝试从中获得最大收益。 其中一些子系统是在游戏项目方面制作的。

总结一下


最后,我想描述一下开发是如何进行的。 在短短两个月内,几个人制作了第一个工作版本并将其集成到一些游戏项目中。 它尚未生成代码,而现在却有大量的编辑器。 同时,它是一个工作版本,从此开始前进。 这并不是说当时与引擎发展的主要动力相对应,一切都取决于几个人的热情,并且清楚地了解了我们所做工作的必要性和正确性。

随后的所有开发都非常积极且逐步地进行,但始终考虑游戏项目的利益。 目前,有十多个人正在致力于“我们的小团结”的开发,而新版本的开发不再像开始时那样快。

尽管如此,我们在短短几年内就取得了巨大的成就,并且不会停止。 祝您前进,认为对自己和整个公司来说都是正确和重要的。

Source: https://habr.com/ru/post/zh-CN467827/


All Articles