Files
XCEngine/docs/plan/end/编辑器与运行时分层架构设计.md

18 KiB
Raw Blame History

编辑器与运行时分层架构设计

1. 背景与目的

1.1 参考案例Unity 的 Editor/Player 分离

Unity 是目前最成熟的游戏引擎之一其架构设计经过多年迭代验证。Unity 的核心设计理念是将游戏开发工具(编辑器)与游戏运行载体(播放器)清晰分离,同时共享同一套引擎核心。

Unity 的整体架构如下:

┌─────────────────────────────────────────────────────────────┐
│                        Unity Hub                              │
│                    (项目浏览器、启动器)                       │
└─────────────────────────┬───────────────────────────────────┘
                          │
┌─────────────────────────┴───────────────────────────────────┐
│                    Unity Editor                               │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────────────┐  │
│  │Hierarchy │ │Inspector │ │  Scene   │ │    Project      │  │
│  │ Panel    │ │ Panel    │ │   View   │ │    Browser      │  │
│  └──────────┘ └──────────┘ └──────────┘ └─────────────────┘  │
│                                                              │
│  ┌────────────────────────────────────────────────────────┐  │
│  │           Unity Engine Core (C++)                       │  │
│  │  Scene System, ECS, Renderer, Physics, Audio, etc.     │  │
│  └────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
                          │ (Build)
                          ↓
┌─────────────────────────────────────────────────────────────┐
│                    Unity Player                              │
│  (独立可执行文件,不含编辑器组件)                           │
│  - IL2CPP 虚拟机运行 C# 脚本                               │
│  - 裁剪过的引擎子集                                         │
└─────────────────────────────────────────────────────────────┘

这一架构的核心思想是:

  • Editor 是开发工具,面向游戏开发者,提供场景编辑、参数调整、资源管理等功能
  • Player 是运行载体,面向最终用户,运行游戏逻辑,不包含任何编辑器 UI
  • Engine Core 是共享层Editor 和 Player 都依赖同一套核心逻辑

1.2 分层架构的目标与收益

采用分层架构设计编辑器与运行时,带来以下收益:

目标 说明
职责分离 编辑器和运行时各司其职,代码边界清晰
核心复用 场景系统、渲染器、物理等核心逻辑只需维护一份
按需发布 发行游戏时只需包含运行时,体积更小
独立迭代 编辑器改进不影响运行时稳定性
团队协作 程序员专注核心,设计师专注编辑器操作

2. 整体架构设计

2.1 分层原则

编辑器与运行时的分层应遵循以下原则:

  1. 核心不可依赖编辑器Engine Core 不应包含任何 UI 相关的代码,确保可以在无界面环境下运行
  2. 运行时独立最小化:运行时只包含游戏运行必需的组件,不包含编辑器特有功能
  3. 接口抽象分离:通过接口隔离变化,便于未来替换实现
  4. 数据驱动:场景、配置等数据与代码逻辑分离

2.2 模块划分

整体架构分为五个主要模块:

模块 英文名 说明
引擎核心 Engine Core 游戏运行时逻辑的核心库
编辑器应用 Editor Application 游戏开发工具
运行时应用 Runtime Application 游戏运行载体
启动器 Launcher 项目浏览器与启动器
脚本系统 Script System 游戏逻辑脚本

2.3 整体架构图

                         ┌─────────────────────────────┐
                         │      <Launcher>        │
                         │        (启动器)            │
                         │   扫描项目,启动应用         │
                         └─────────────┬───────────────┘
                                       │
                    ┌──────────────────┴──────────────────┐
                    │                                     │
          ┌─────────▼─────────┐               ┌──────────▼──────────┐
          │  <Editor>.exe  │               │   <Runtime>.exe      │
          │   (编辑器)       │               │    (运行时)        │
          │                   │               │                     │
          │  ┌─────────────┐  │               │  ┌───────────────┐  │
          │  │ <Editor>Layer  │  │               │  │ <Runtime>Layer │  │
          │  │  (UI面板)   │  │               │  │  (游戏逻辑)   │  │
          │  └──────┬──────┘  │               │  └───────┬───────┘  │
          └─────────┼─────────┘               └──────────┼──────────┘
                    │                                     │
         ┌──────────┴───────────────────────────────────┴──────────┐
         │                                                             │
         │                    <Core>                    │
         │                     (引擎核心静态库)                      │
         │  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌────────────┐  │
         │  │  Scene   │  │ Renderer │  │ Physics  │  │   Script   │  │
         │  │  (ECS)   │  │          │  │  2D/3D   │  │  (C# CLR)  │  │
         │  └──────────┘  └──────────┘  └──────────┘  └────────────┘  │
         │  ┌──────────┐  ┌──────────┐  ┌──────────────────────────┐  │
         │  │  Asset   │  │  Event   │  │     Project/Settings     │  │
         │  │ Manager  │  │  System  │  │                          │  │
         │  └──────────┘  └──────────┘  └──────────────────────────┘  │
         └─────────────────────────────────────────────────────────────┘

2.4 模块间依赖关系

<Launcher> ──启动──> <Editor> / <Runtime>

<Editor> ──链接──> Engine Core
<Runtime>  ──链接──> Engine Core

Engine Core不依赖任何其他模块
    │
    ├── Scene System
    ├── Renderer
    ├── Physics
    ├── Script Engine
    ├── Asset Manager
    └── Event System

<Script>C#项目)──编译输出──> .dll 供 <Core> 加载

依赖规则:

  • Engine Core 是底层,不被任何其他模块依赖(它依赖谁?)
  • Editor 和 Runtime 都依赖 Engine Core,不直接依赖彼此
  • Launcher 只依赖文件系统,不依赖 Engine Core

3. 核心模块设计

3.1 Engine Core引擎核心

职责

引擎核心是整个架构的基石,负责游戏运行时的一切逻辑:

  • 场景管理Entity-Component-System 架构,场景的创建、销毁、序列化
  • 渲染系统:渲染管线、材质、光照、后处理
  • 物理系统2D/3D 物理模拟
  • 脚本系统:脚本引擎、脚本绑定
  • 资源管理:资源加载、缓存、卸载
  • 事件系统:事件分发与响应
  • 输入系统:输入事件捕获与分发
  • 音频系统:音频播放、音效处理

边界

Engine Core 不包含

  • 任何 UI 逻辑ImGui 代码、面板布局等)
  • 编辑器特有功能场景大纲、Gizmo 选择等)
  • 启动器相关逻辑

设计要点

Engine Core 编译为静态库.lib),被 Editor 和 Runtime 两个可执行文件链接。这种方式确保:

  • 代码真正共享,而非运行时 DLL 加载
  • 两个应用使用完全相同的核心逻辑版本

3.2 Editor Application编辑器应用

职责

编辑器是游戏开发者的主要工具,提供:

  • 场景编辑:场景视图、层级面板、检视面板
  • 资源管理:资源导入、浏览、配置
  • 游戏预览Play/Simulate 模式切换
  • 项目配置:项目设置、构建选项

与 Engine Core 的关系

编辑器链接 Engine Core并在此基础上添加编辑器的 UI 层:

Editor Application
    │
    ├── Editor UI LayerImGui 面板)
    │       │
    │       ├── ViewportPanel
    │       ├── SceneHierarchyPanel
    │       ├── InspectorPanel
    │       ├── ContentBrowserPanel
    │       └── MenuBarPanel
    │
    └── Engine Core场景管理、渲染、物理等

核心机制Scene 复制

编辑器在 Play/Simulate 时,会将当前编辑的场景完整复制一份交给运行时:

m_activeScene = Scene::copy(m_editorScene);
m_activeScene->onRuntimeStart();

这样设计的好处是:

  • 编辑器场景保持不变,方便运行时修改调试
  • 运行时可以自由修改场景对象,不影响编辑器状态
  • Stop 时只需切回编辑器场景即可

3.3 Runtime Application运行时应用

职责

运行时是游戏的运行载体,面向最终用户:

  • 加载游戏项目:读取项目配置、启动场景
  • 运行游戏循环:持续执行 Scene 的更新逻辑
  • 执行脚本:运行 C# 脚本游戏逻辑
  • 渲染画面:调用渲染器输出图像

与 Engine Core 的关系

运行时同样链接 Engine Core但不包含任何编辑器 UI

Runtime Application
    │
    ├── Runtime Layer游戏入口、更新循环
    │
    └── Engine Core与编辑器相同

特点

  • 无 UI:运行时没有任何编辑器界面
  • 最小化:只包含游戏运行必需的组件
  • 独立发行:可打包为独立的游戏可执行文件

3.4 Launcher启动器

职责

启动器是用户进入游戏的第一个界面:

  • 项目浏览:扫描并显示本地的游戏项目列表
  • 项目管理:创建、打开、删除项目
  • 启动选择:启动编辑器或直接运行游戏

设计要点

启动器不链接 Engine Core而是作为一个独立的小型程序

Launcher Application
    │
    ├── 项目浏览器 UI
    ├── 项目文件系统扫描
    └── 进程启动(调用 <Editor>.exe 或 <Runtime>.exe

这种设计使得:

  • 启动器可以独立编译、独立发布
  • 即使引擎编译失败,启动器也可能正常工作
  • 未来可以单独改进启动器的 UI/UX

3.5 Script System脚本系统

定位

脚本系统是连接游戏逻辑与引擎的桥梁。游戏开发者编写脚本(当前采用 C#),脚本调用引擎提供的 API。

隔离方式

脚本系统采用独立编译 + 运行时加载的模式:

<Script>C# 项目)
    │
    ├── 游戏脚本源码(.cs
    │
    └── 编译输出 ──> GameScripts.dll
                              │
                              ↓
                      <Core>(运行时加载)
                              │
                              └── Mono Runtime 执行

设计要点

  • 脚本独立编译:不与引擎一起编译,便于快速迭代
  • 运行时加载Engine Core 通过 Mono Runtime 加载编译好的脚本
  • 引擎 API 绑定:引擎提供 C++ 函数,脚本通过 InternalCall 调用

4. 编辑器与运行时交互机制

4.1 Scene 复制机制

编辑器维护两种 Scene

Scene 用途 修改权限
m_editorScene 编辑器中显示的场景 编辑器随时可改
m_runtimeScene Play/Simulate 时复制的场景 运行时逻辑可改

复制过程:

std::shared_ptr<Scene> Scene::copy(std::shared_ptr<Scene> other) {
    std::shared_ptr<Scene> newScene = std::make_shared<Scene>();
    
    // 1. 复制所有 Entity保留 UUID
    auto idView = srcSceneRegistry.view<IDComponent>();
    for (auto e : idView) {
        UUID uuid = srcSceneRegistry.get<IDComponent>(e).ID;
        Entity newEntity = newScene->createEntityWithUUID(uuid, name);
        enttMap[uuid] = (entt::entity)newEntity;
    }
    
    // 2. 复制所有 Component
    copyComponent(AllComponents{}, dstSceneRegistry, srcSceneRegistry, enttMap);
    
    return newScene;
}

复制确保:

  • Entity UUID 不变,便于调试和序列化
  • 所有 Component 数据完整复制
  • 编辑器场景不受影响

4.2 状态机Edit / Play / Simulate

编辑器有三种状态:

状态 Scene 物理 脚本 相机 层级可编辑
Edit m_editorScene EditorCamera
Play m_runtimeScene MainCamera
Simulate m_runtimeScene EditorCamera

状态转换:

         [Edit Mode]
              │
    ┌─────────┼─────────┐
    │         │         │
[Play]   [Simulate]   [New/Open Scene]
    │         │         │
    └────┬────┴────┬────┘
         │         │
       [Stop] ←────┘
         │
         ↓
   [Edit Mode](切回编辑器场景)

Edit → Play

  1. m_editorScene 深复制为 m_runtimeScene
  2. m_runtimeScene->onRuntimeStart() 初始化运行时
  3. 层级面板禁用编辑

Edit → Simulate

  1. 与 Play 相同,但使用 EditorCamera
  2. 物理运行,方便调试物理效果

Stop

  1. 运行时场景 onRuntimeStop() 清理
  2. 切回 m_editorScene
  3. 层级面板恢复编辑

4.3 资源管理差异

编辑器与运行时对资源的访问权限不同:

操作 EditorAssetManager RuntimeAssetManager
加载资源
导入资源
创建资源
删除资源

这种设计确保:

  • 运行时安全:玩家无法导入新资源,防止作弊
  • 编辑器便捷:开发者可以直接在编辑器内导入、管理资源
  • 职责清晰:资源导入是开发流程的一部分,不是运行时必需的

实现上,两个 AssetManager 继承自同一基类:

class AssetManagerBase { ... };

class EditorAssetManager : public AssetManagerBase {
    void importAsset(const std::filesystem::path&) { /* 实现 */ }
};

class RuntimeAssetManager : public AssetManagerBase {
    void importAsset(const std::filesystem::path&) = delete; // 禁止
};

5. 总结

5.1 分层架构的核心价值

价值 说明
职责清晰 编辑器管开发,运行时管执行,各司其职
代码复用 Engine Core 被两方共享,维护一份代码
灵活发布 按需构建,只发行运行时
易于测试 核心逻辑可脱离编辑器独立测试

5.2 关键设计决策

决策 选择 理由
核心库形式 静态库 编译时链接,确保版本一致
脚本隔离 独立 C# 项目 快速编译迭代,与引擎解耦
场景复制 深复制 确保编辑/运行时隔离
状态机 Edit/Play/Simulate 三态 支持物理调试

5.3 架构优势

这种分层架构让引擎具备:

  • 类似 Unity 的开发体验
  • 可独立迭代的模块
  • 可裁剪的运行时体积
  • 清晰的代码边界

文档版本1.0
参考Unity Editor/Player Architecture