AWESOME ENGINE / 技术指南

引擎架构

了解主循环、EngineContext、对象所有权以及各个系统之间的关系。

本页内容

从输入到一帧画面

main() 构造一个 Engine,并在窗口保持打开时重复执行三个操作。以下片段来自参考代码:

while (engine.IsRunning()) {
  engine.ProcessEvent();
  engine.Update();

  if (!engine.HasFocus()) {
    sf::sleep(sf::milliseconds(10));
    continue;
  }

  engine.Render();
}

失去焦点时,循环仍然处理事件并更新时钟,但跳过渲染并休眠 10 ms。Engine::Update 在推进场景之前也会检查焦点。

Engine 的职责

Engine 拥有窗口、EngineContext、场景注册表、活动场景引用以及暂停覆盖层,负责协调这些元素。

Engine
├── sf::RenderWindow
├── EngineContext
│   ├── renderer · resources · particles
│   ├── input · cursor · gui
│   ├── audio · time · random
│   └── scenes · save · screenshot · clipboard
├── SceneFactory::Scenes
│   └── 名称 → unique_ptr<Scene>
├── currentScene → 活动 Scene
└── Overlay → TGUI 控件

上下文通过引用传给每个场景。游戏因此可以访问 ctx.renderer、ctx.input 或 ctx.resources,而不必重复构造服务。

事件处理

在 ProcessEvent 开始时,引擎先处理待执行的场景切换,然后从 sf::RenderWindow 取出事件并分发:

  1. EngineVisitor 处理关闭、焦点、尺寸、手柄和全局快捷键。
  2. GuiManager 将事件发送给 TGUI。
  3. 覆盖层隐藏时,场景接收 OnEvent。
  4. 引擎处理覆盖层中的选择,并同步暂停状态。

GUI 与场景参与同一个处理流程;本文描述的代码没有独立的事件总线。

更新与时间

TimeManager 计算上一帧至今的经过时间,并用 gConfig.maximumDeltaTime 限制它。之后更新光标;若覆盖层隐藏,则调用 Scene::Update。

系统没有实现固定步长累加器。每个游戏自行决定如何使用时间增量和自己的计时器。

在参考修订中,每个场景自行实现对 OnPause 的响应,包括音频和计时器。用于冷却计时的时钟正在进行本地重构,标为开发中,不属于此公开参考 API。

渲染与图层

场景绘制到 sf::RenderTexture。RenderManager 应用效果链并返回最终纹理。Engine 将它显示到窗口,再在其上绘制 GUI 和光标。

Scene::Render
     ↓
RenderManager / RenderTexture
     ↓
Bloom → Monitor
     ↓
窗口 → GUI → Cursor → display

只有 sf::Shader::isAvailable() 返回真时才会加入效果。GUI 和光标在后处理完成后绘制。有关 Bloom 的各个渲染阶段,请参阅渲染。

所有权与内存

  • 场景存储在值为 unique_ptr<Scene> 的 unordered_map 中。
  • 效果存储在 unique_ptr<Effect> 的向量中。
  • 活动场景使用不拥有对象的指针;对象仍归注册表所有。
  • 各管理器作为值成员属于 EngineContext。
  • ResourceManager 在映射中按值保存资源。

项目使用 RAII 和标准容器,没有自定义内存管理器或通用 ECS。所有注册场景都在 CreateScenes 中构造,切换场景不会在每次过渡时创建新实例。

代码组织

目录 内容
Engine/Include 核心、管理器、场景、效果和工具的接口约定与类型。
Engine/Source 实现及程序入口。
Games/Include 游戏类、类型及配置常量。
Games/Source 游戏逻辑与渲染。
Content 图形和音频资源、字体、着色器、主题及 JSON。

源码参考

基于已验证的源码。发现问题了吗? →