前置知识: C++

C++游戏开发

59 minAdvanced2026/6/14

游戏引擎与C++游戏开发

C++ 游戏开发

本文档系统讲解 C++ 在游戏开发中的应用,覆盖游戏循环架构、实体-组件-系统 (ECS) 模式、内存管理、缓存友好设计、SIMD 优化、数据导向设计 (DOD)、主流引擎剖析与工程实践。内容遵循 ISO/IEC 14882:2023,参考 Unreal Engine、Unity、Godot 等主流引擎的实现,目标达到海外高校教学水准。


1. 学习目标

本节使用 Bloom 分类法刻画学习者应达到的认知层级。

1.1 记忆(Remember)

  • 列举游戏循环 (game loop) 的三个核心阶段:输入处理 (input)、状态更新 (update)、渲染 (render)。
  • 复述 ECS 三要素的定义:实体 (Entity)、组件 (Component)、系统 (System)。
  • 背诵主流游戏引擎的名称:Unreal Engine、Unity、Godot、CryEngine、Source 2、Phaser。
  • 列举 C++ 在游戏开发中的核心优势:性能、内存控制、跨平台、低延迟。

1.2 理解(Understand)

  • 解释游戏循环与渲染循环 (render loop) 的区别:前者驱动逻辑更新,后者驱动画面绘制,二者通过垂直同步 (V-Sync) 协调。
  • 阐述 ECS 相对传统 OOP 继承体系的优势:组合优于继承、缓存友好、并行友好。
  • 描述 CPU 缓存层级(L1/L2/L3)对游戏性能的影响,以及缓存未命中 (cache miss) 的代价。
  • 区分数据导向设计 (DOD) 与面向对象设计 (OOP) 的核心差异:前者以数据布局为中心,后者以行为抽象为中心。

1.3 应用(Apply)

  • 使用 C++17 实现一个固定时间步长 (fixed-timestep) 的游戏循环,支持可变渲染帧率。
  • 实现一个基本的 ECS 框架,包含实体管理、组件存储、系统调度。
  • 使用 std::pmr 或自定义分配器实现帧内 (per-frame) 内存分配器。
  • 使用 SIMD intrinsics (<immintrin.h>) 优化向量运算。

1.4 分析(Analyze)

  • 对比 Unity 的 GameObject-Component 体系与 Unreal 的 Actor-Component 体系的设计差异。
  • 解构数据局部性 (data locality) 对游戏性能的影响,通过缓存命中率分析定位瓶颈。
  • 分析 ECS 中”原型 (archetype)“存储模型与”稀疏集合 (sparse set)“存储模型的取舍。
  • 评估 C++20 协程在游戏异步任务(资源加载、网络)中的适用性。

1.5 评价(Evaluate)

  • 评估在 MMO、FPS、RTS、独立游戏四类项目中应采用何种引擎架构。
  • 判断特定游戏系统(物理、AI、渲染)是否应采用 ECS 或传统 OOP。
  • 评审一份游戏代码的内存分配模式,识别堆分配热点。

1.6 创造(Create)

  • 设计并实现一个完整的 2D 游戏引擎,包含 ECS、渲染、物理、音频、输入子系统。
  • 构建一个基于 Job System 的多线程游戏任务调度器,支持依赖图。
  • 为自定义游戏引擎编写热重载 (hot reload) 系统,支持运行时替换脚本与资源。

2. 历史动机与发展脉络

C++ 游戏开发的演进反映了硬件性能、设计模式与工具链的持续博弈。

2.1 早期:汇编与 C 主导(1970s - 1990s)

早期游戏(如《太空侵略者》、《吃豆人》)使用汇编语言编写,针对特定硬件优化。1980 年代 C 语言普及,成为游戏开发主流:

  • 1985 年:任天堂红白机 (NES) 游戏多使用 6502 汇编。
  • 1990 年代:PC 游戏转向 C,如 id Software 的《Doom》(1993) 使用 C 与少量汇编。
  • 1996 年:《Quake》采用客户端-服务器架构,C 语言实现。

C 语言的优势:性能接近汇编、跨平台、内存透明。但缺乏抽象机制,大型项目维护困难。

2.2 C++ 的引入(1990s - 2000s)

1990 年代后期,C++ 开始进入游戏开发:

  • 1998 年:Unreal Engine 1 发布,使用 C++ 与自定义脚本。
  • 1999 年:id Software 的《Quake III Arena》采用 C 与少量 C++。
  • 2000 年代:主流商业引擎(Unreal、Source、CryEngine)全面采用 C++。

C++ 的优势:OOP 抽象、模板泛型、STL 容器、RAII 资源管理。但早期 C++ 编译器性能差,运行时开销(异常、RTTI)让游戏开发者顾虑。

2.3 OOP 继承体系的黄金期(2000s - 2010s)

2000 年代,游戏引擎普遍采用深继承体系:

GameObject
  ├── Actor
  │     ├── Pawn
  │     │     ├── Character
  │     │     │     ├── PlayerCharacter
  │     │     │     └── NPC
  │     │     └── Vehicle
  │     └── Weapon
  └── StaticMesh

这种体系的问题:

  1. 继承深度爆炸:深层继承导致”菱形继承”与脆弱基类问题。
  2. 缓存不友好:对象分散在堆上,虚函数调用导致 vtable 跳转,缓存命中率低。
  3. 扩展困难:新增功能需修改继承树,影响所有子类。
  4. 并行困难:对象间隐式依赖,难以并行化。

2.4 ECS 模式的兴起(2010s - 至今)

为解决 OOP 继承体系的问题,业界转向 ECS (Entity-Component-System) 模式:

  • 2007 年:Adam Martin 在博客中首次系统阐述 ECS。
  • 2010 年代:Unity (DOTS)、Unreal Engine (Mass Entity)、Bevy、Flecs 等引擎采用 ECS。
  • 2018 年:Unity 发布 DOTS (Data-Oriented Technology Stack),基于 ECS。
  • 2022 年:Unreal Engine 5 发布 Mass Entity 框架。

ECS 的核心思想:

  1. 实体 (Entity):仅是 ID,无数据无行为。
  2. 组件 (Component):纯数据,无行为。
  3. 系统 (System):纯行为,处理特定组件组合的实体。

ECS 的优势:

  • 组合优于继承:实体通过组合组件获得能力,避免继承树。
  • 缓存友好:同类型组件连续存储,CPU 缓存命中率高。
  • 并行友好:系统间无共享状态,易于多线程。
  • 可扩展:新增功能只需新增系统,不影响现有代码。

2.5 数据导向设计 (DOD) 的普及(2010s - 至今)

数据导向设计 (Data-Oriented Design, DOD) 由 Mike Acton (Insomniac Games) 在 2014 年 CppCon 演讲中推广。其核心原则:

  1. 以数据布局为中心:先设计数据结构,再设计算法。
  2. 关注内存访问模式:缓存命中率是性能关键。
  3. 分离数据与行为:数据是被动载体,行为是独立函数。
  4. 批处理优先:批量处理数据,利用 SIMD 与流水线。

DOD 与 OOP 的对比:

// OOP 风格:对象包含数据与行为
class Enemy {
    Vector3 position_;
    float health_;
public:
    void Update(float dt) { position_ += velocity_ * dt; }
};

std::vector<Enemy> enemies;  // 对象分散,缓存差
for (auto& e : enemies) e.Update(dt);  // 虚调用

// DOD 风格:数据与行为分离
struct EnemyData {
    std::vector<Vector3> positions;
    std::vector<float> healths;
    std::vector<Vector3> velocities;
};

void UpdateEnemies(EnemyData& data, float dt) {
    for (size_t i = 0; i < data.positions.size(); ++i) {
        data.positions[i] += data.velocities[i] * dt;  // 连续访问,缓存友好
    }
}

2.6 现代 C++ 在游戏中的应用

C++11/14/17/20 的新特性逐步被游戏开发采纳:

  • C++11constexpr(编译期计算)、std::atomic(无锁同步)、Lambda(回调)、std::thread(多线程)。
  • C++14std::make_unique(智能指针)、泛型 Lambda。
  • C++17:结构化绑定(简化 ECS 迭代)、std::optional(可选返回)、std::string_view(零拷贝字符串)、if constexpr(编译期分支)。
  • C++20:concept(模板约束)、协程(异步任务)、模块(替代头文件)、std::span(数组视图)。
  • C++23std::expected(错误处理)、std::print(格式化输出)。

游戏开发对 C++ 特性的保守态度:

  • 异常:多数引擎禁用(-fno-exceptions),因性能不可预测。
  • RTTI:多数引擎禁用(-fno-rtti),节省二进制体积。
  • STL:部分引擎自实现容器(如 TArray in Unreal),因 STL 性能不达预期。
  • new/delete:游戏循环中禁用,改用自定义分配器。

2.7 演进时间线

1972  早期游戏(汇编)           Space Invaders
1985  NES 游戏(6502 汇编)       Super Mario Bros.
1993  Doom (C)                   id Software
1996  Quake (C + 少量 C++)       id Software
1998  Unreal Engine 1 (C++)      Epic Games
2000s OOP 继承体系黄金期          Unreal, Source, CryEngine
2007  ECS 概念提出               Adam Martin
2014  DOD 推广 (Mike Acton)      Insomniac Games
2018  Unity DOTS (ECS)           Unity Technologies
2020  Unreal Engine 5            Epic Games
2022  Unreal Mass Entity         Epic Games
2023  Bevy 0.10 (Rust ECS)       Bevy 组织
2026  C++26 草案                 反射、契约

3. 形式化定义

本节给出游戏开发相关的形式化定义,涵盖标准引用、ECS 数学模型与缓存模型。

3.1 ISO/IEC 14882 标准中的游戏相关条款

C++ 标准未为游戏定义专门条款,但以下条款与游戏密切相关:

  • [intro.memory] 内存模型:对象、内存位置、字节的关系,影响数据布局。
  • [intro.multithread] 多线程:原子操作与内存序,游戏多线程必需。
  • [support.dynamic] 动态内存:new/delete,游戏常禁用并自实现分配器。
  • [allocators] 分配器:std::allocatorstd::pmr,游戏可定制。
  • [simd] SIMD:C++26 引入 <simd>,标准化向量运算。
  • [time] 时间:std::chrono,游戏循环计时基础。

3.2 游戏循环的形式化模型

游戏循环可形式化为状态机:

GameStatet+1=Update(GameStatet,Inputt,Δt)\text{GameState}_{t+1} = \text{Update}(\text{GameState}_t, \text{Input}_t, \Delta t)

其中:

  • GameStatet\text{GameState}_t:时刻 tt 的游戏状态。
  • Inputt\text{Input}_t:时刻 tt 的输入。
  • Δt\Delta t:时间步长。
  • Update\text{Update}:状态转移函数。

渲染函数:

Framet=Render(GameStatet)\text{Frame}_t = \text{Render}(\text{GameState}_t)

固定时间步长 (fixed timestep) 模型:

Δtupdate=160s,Δtrender=variable\Delta t_{\text{update}} = \frac{1}{60} \text{s}, \quad \Delta t_{\text{render}} = \text{variable}

更新频率固定(60 Hz),渲染频率可变。通过”累加器”模式实现:

accumulator+=Δtreal,while(accumulatorΔtupdate):Update();accumulator=Δtupdate\text{accumulator} += \Delta t_{\text{real}}, \quad \text{while}(\text{accumulator} \geq \Delta t_{\text{update}}) : \text{Update}(); \text{accumulator} -= \Delta t_{\text{update}}

3.3 ECS 的形式化模型

设组件类型集合 C={C1,C2,,Cn}\mathcal{C} = \{C_1, C_2, \ldots, C_n\},实体 ee 是组件集合的子集:

eCe \subseteq \mathcal{C}

系统 SS 是一个二元组:

S=(Q,f)S = (Q, f)

其中 Q2CQ \subseteq 2^{\mathcal{C}} 是系统查询的组件组合(如 {Position,Velocity}\{\text{Position}, \text{Velocity}\}),ff 是处理函数:

f:CQCCQCf : \prod_{C \in Q} C \to \prod_{C \in Q} C

ECS 的执行模型:

SS:eQS:fS(e)\forall S \in \mathcal{S} : \forall e \supseteq Q_S : f_S(e)

即对每个系统 SS,查询所有包含 QSQ_S 组件的实体,应用 fSf_S

3.4 缓存命中率模型

CPU 缓存层级:

层级容量延迟(周期)带宽
L132-64 KB3-4~1 TB/s
L2256-512 KB10-14~500 GB/s
L34-32 MB30-50~200 GB/s
RAM8-64 GB100-300~50 GB/s

缓存命中率 HH 与平均访问时间 TT

T=HThit+(1H)TmissT = H \cdot T_{\text{hit}} + (1 - H) \cdot T_{\text{miss}}

其中 ThitT_{\text{hit}} 是命中时间,TmissT_{\text{miss}} 是未命中时间。游戏代码需最大化 HH

3.5 内存分配的形式化

游戏内存分配器可形式化为函数:

alloc:Size×AlignmentPointer{null}\text{alloc} : \text{Size} \times \text{Alignment} \to \text{Pointer} \cup \{\text{null}\} dealloc:Pointer\text{dealloc} : \text{Pointer} \to \bot

分配器类型:

  1. 线性分配器 (Linear)O(1)O(1) 分配,不支持释放,适合帧内分配。
  2. 栈分配器 (Stack)O(1)O(1) 分配,LIFO 释放。
  3. 池分配器 (Pool)O(1)O(1) 分配/释放,固定大小。
  4. 空闲列表 (Free List)O(logn)O(\log n) 分配/释放,任意大小。
  5. 伙伴分配器 (Buddy)O(logn)O(\log n) 分配/释放,2 的幂次大小。

3.6 SIMD 数据并行模型

SIMD (Single Instruction Multiple Data) 允许一条指令处理多个数据。形式化地:

SIMD:Instruction×VectornVectorn\text{SIMD} : \text{Instruction} \times \text{Vector}_n \to \text{Vector}_n

其中 nn 是向量宽度(如 4 for SSE, 8 for AVX2, 16 for AVX-512)。

游戏中的 SIMD 应用:

  • 向量运算:4D 向量加法、点积、叉积。
  • 矩阵运算:4x4 矩阵乘法。
  • 物理计算:刚体动力学、碰撞检测。
  • 粒子系统:批量粒子更新。

3.7 多线程任务调度模型

游戏多线程任务调度可建模为有向无环图 (DAG):

G=(V,E)G = (V, E)

其中 VV 是任务节点,EE 是依赖边。调度目标是最小化总执行时间:

minmakespan=maxvVfinish(v)\min \text{makespan} = \max_{v \in V} \text{finish}(v)

受限于依赖关系:

(u,v)E    finish(u)start(v)(u, v) \in E \implies \text{finish}(u) \leq \text{start}(v)

游戏引擎的 Job System 实现该模型,支持任务依赖、优先级、工作窃取 (work stealing)。


4. 理论推导与原理解析

4.1 游戏循环的时间步长分析

游戏循环的核心挑战是平衡更新稳定性与渲染流畅性。三种主流模式:

模式一:可变时间步长 (Variable Timestep)

Δt=tnowtlast\Delta t = t_{\text{now}} - t_{\text{last}}

优点:渲染流畅。缺点:物理不稳定(数值积分误差)。

模式二:固定时间步长 (Fixed Timestep)

Δt=160s\Delta t = \frac{1}{60} \text{s}

优点:物理稳定。缺点:渲染卡顿(帧率与更新率绑定)。

模式三:累加器模式 (Accumulator)

double accumulator = 0.0;
const double dt = 1.0 / 60.0;

while (running) {
    double frame_time = get_frame_time();
    accumulator += frame_time;
    while (accumulator >= dt) {
        update(dt);
        accumulator -= dt;
    }
    render(accumulator / dt);  // 插值 alpha
}

累加器模式结合固定更新与可变渲染,是现代游戏引擎的标准方案。

时间步长的数学分析:

设物理状态 x(t)x(t) 满足微分方程 x˙=f(x,t)\dot{x} = f(x, t)。数值积分(显式欧拉):

x(t+Δt)=x(t)+Δtf(x,t)x(t + \Delta t) = x(t) + \Delta t \cdot f(x, t)

误差阶 O(Δt)O(\Delta t)Δt\Delta t 越小,误差越小但计算量越大。固定 Δt=1/60\Delta t = 1/60 在多数游戏中是性能与精度的平衡点。

4.2 ECS 缓存性能分析

对比 OOP 与 ECS 的缓存性能。假设 10000 个敌人,每个敌人有 Position (12B) 与 Health (4B)。

OOP 布局

class Enemy {
    Vector3 position;  // 12B
    float health;      // 4B
    // 其他字段...
    AIState ai;        // 64B
    Animation anim;    // 128B
};
// sizeof(Enemy) ≈ 208B
std::vector<Enemy> enemies(10000);

遍历 Position 的内存访问:

  • 敌人对象大小 208B,Position 在前 12B。
  • 访问 10000 个 Position 需 10000 × 208B = 2.08 MB,超出 L2 缓存。
  • 缓存行 64B,每个敌人占 3.25 个缓存行,每次访问位置加载 64B 但仅用 12B,命中率 18.75%。

ECS 布局

struct PositionComponent { Vector3 position; };  // 12B
struct HealthComponent { float health; };        // 4B

std::vector<PositionComponent> positions(10000);
std::vector<HealthComponent> healths(10000);

遍历 Position 的内存访问:

  • 10000 个 Position 连续存储,共 120 KB,放入 L2。
  • 缓存行 64B 可容纳 5 个 Position,命中率接近 100%。

性能差距:ECS 的位置遍历比 OOP 快 5-10 倍(实测)。

4.3 内存分配器性能分析

游戏循环中频繁分配/释放内存导致碎片化与性能下降。对比三种分配器:

new/delete(系统分配器)

  • 每次分配涉及系统调用(如 malloc),约 100-300 周期。
  • 碎片化导致长期性能下降。
  • 多线程竞争锁。

线性分配器

class LinearAllocator {
    char* begin_;
    char* current_;
    char* end_;
public:
    void* allocate(size_t size) {
        void* p = current_;
        current_ += size;
        return p;
    }
    void reset() { current_ = begin_; }  // 整批释放
};
  • 分配 O(1)O(1),约 5-10 周期。
  • 不支持单次释放,仅支持 reset()
  • 适合帧内分配:每帧开始 reset(),帧内任意分配。

池分配器

template<typename T>
class PoolAllocator {
    std::vector<T*> free_list_;
public:
    T* allocate() {
        if (free_list_.empty()) return nullptr;
        T* p = free_list_.back();
        free_list_.pop_back();
        return p;
    }
    void deallocate(T* p) {
        free_list_.push_back(p);
    }
};
  • 分配/释放 O(1)O(1)
  • 固定大小,无碎片。
  • 适合频繁创建/销毁同类对象(如粒子、子弹)。

性能对比表:

分配器分配时间(周期)释放时间(周期)碎片多线程
new100-300100-300锁竞争
线性5-10N/A(reset)
5-105-10
10-2010-20可实现
空闲列表50-10050-100锁竞争

4.4 SIMD 向量运算优化

4D 向量加法的标量与 SIMD 对比:

标量版本

struct Vector4 {
    float x, y, z, w;
};

Vector4 add(Vector4 a, Vector4 b) {
    return {a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w};
}

汇编(x86-64):

movss xmm0, [a]
addss xmm0, [b]
movss [result], xmm0
; 重复 4 次

约 12 条指令,16 周期。

SSE 版本

#include <immintrin.h>

__m128 add_sse(__m128 a, __m128 b) {
    return _mm_add_ps(a, b);
}

汇编:

addps xmm0, xmm1

1 条指令,1-3 周期。性能提升 5-10 倍。

AVX 版本(8 宽)

__m256 add_avx(__m256 a, __m256 b) {
    return _mm256_add_ps(a, b);
}

1 条指令处理 8 个 float,性能再翻倍。

4.5 多线程 Job System 分析

游戏多线程的挑战:任务依赖、负载均衡、数据竞争。Job System 的核心数据结构:

struct Job {
    std::function<void()> task;
    std::vector<Job*> dependencies;  // 依赖任务
    std::atomic<int> counter;        // 未完成依赖计数
};

class JobSystem {
    std::vector<std::thread> workers_;
    ConcurrentQueue<Job*> ready_queue_;  // 就绪队列
public:
    void submit(Job* job) {
        if (job->counter == 0) {
            ready_queue_.push(job);
        }
    }
    void wait(Job* job) {
        while (job->counter > 0) {
            // 工作窃取:尝试执行其他就绪任务
            Job* stolen;
            if (ready_queue_.try_pop(stolen)) {
                stolen->task();
            }
        }
    }
};

工作窃取 (work stealing) 算法:

  • 每个工作线程有本地双端队列 (deque)。
  • 工作线程从本地队列 LIFO 取任务(缓存友好)。
  • 空闲时从其他线程 FIFO 窃取任务(负载均衡)。

时间复杂度:

  • 本地取任务:O(1)O(1)
  • 窃取任务:O(1)O(1)(平均),最坏 O(n)O(n)

4.6 物理引擎的数值积分

刚体物理的数值积分方法:

显式欧拉 (Explicit Euler)

v(t+Δt)=v(t)+a(t)Δtv(t + \Delta t) = v(t) + a(t) \cdot \Delta t x(t+Δt)=x(t)+v(t)Δtx(t + \Delta t) = x(t) + v(t) \cdot \Delta t

优点:简单。缺点:能量泄漏,长时间不稳定。

半隐式欧拉 (Semi-implicit Euler)

v(t+Δt)=v(t)+a(t)Δtv(t + \Delta t) = v(t) + a(t) \cdot \Delta t x(t+Δt)=x(t)+v(t+Δt)Δtx(t + \Delta t) = x(t) + v(t + \Delta t) \cdot \Delta t

优点:稳定(对简谐运动),游戏常用。缺点:精度低。

Verlet 积分

x(t+Δt)=2x(t)x(tΔt)+a(t)Δt2x(t + \Delta t) = 2x(t) - x(t - \Delta t) + a(t) \cdot \Delta t^2

优点:稳定,能量守恒。缺点:需存储 x(tΔt)x(t - \Delta t)

RK4 (Runge-Kutta 4)

k1=f(x,t),k2=f(x+Δt2k1,t+Δt2)k_1 = f(x, t), \quad k_2 = f(x + \frac{\Delta t}{2} k_1, t + \frac{\Delta t}{2}) k3=f(x+Δt2k2,t+Δt2),k4=f(x+Δtk3,t+Δt)k_3 = f(x + \frac{\Delta t}{2} k_2, t + \frac{\Delta t}{2}), \quad k_4 = f(x + \Delta t \cdot k_3, t + \Delta t) x(t+Δt)=x(t)+Δt6(k1+2k2+2k3+k4)x(t + \Delta t) = x(t) + \frac{\Delta t}{6}(k_1 + 2k_2 + 2k_3 + k_4)

优点:高精度(O(Δt4)O(\Delta t^4))。缺点:计算量大(4 次求值)。

游戏物理引擎(如 Bullet、PhysX)多采用半隐式欧拉,兼顾性能与稳定性。

4.7 资源管理的智能指针分析

游戏资源(纹理、网格、音频)的生命周期管理。对比三种方案:

裸指针

Texture* tex = new Texture("player.png");
// ... 忘记 delete,内存泄漏

引用计数 (shared_ptr)

auto tex = std::make_shared<Texture>("player.png");
// 引用计数为 0 时自动释放

缺点:原子操作开销(约 20-50 周期/次),循环引用泄漏。

句柄 (Handle) 系统

struct TextureHandle {
    uint32_t index;   // 资源索引
    uint32_t generation;  // 代数(防止悬空)
};

class TextureManager {
    std::vector<Texture*> textures_;
    std::vector<uint32_t> generations_;
public:
    TextureHandle load(const std::string& path);
    Texture* get(TextureHandle h);  // 检查代数,返回 nullptr 若已释放
};

优点:

  • 8 字节句柄,传递高效。
  • 代数检查防止悬空访问。
  • 集中管理,便于卸载。

现代游戏引擎(如 Unreal)多采用句柄系统替代智能指针。


5. 代码示例

5.1 固定时间步长游戏循环

标准:C++17

// game_loop.cpp
// 固定时间步长游戏循环,支持可变渲染帧率

#include <chrono>
#include <iostream>

class Game {
public:
    void run() {
        constexpr double fixed_dt = 1.0 / 60.0;  // 60 Hz 更新
        constexpr double max_frame_time = 0.25;   // 最大帧时间,避免死循环

        double accumulator = 0.0;
        auto last_time = std::chrono::high_resolution_clock::now();

        while (running_) {
            auto current_time = std::chrono::high_resolution_clock::now();
            double frame_time = std::chrono::duration<double>(
                current_time - last_time).count();
            last_time = current_time;

            // 防止暂停后死循环
            if (frame_time > max_frame_time) {
                frame_time = max_frame_time;
            }

            accumulator += frame_time;

            // 固定步长更新
            while (accumulator >= fixed_dt) {
                update(fixed_dt);
                accumulator -= fixed_dt;
            }

            // 渲染插值因子
            double alpha = accumulator / fixed_dt;
            render(alpha);
        }
    }

    void stop() { running_ = false; }

private:
    bool running_ = true;

    void update(double dt) {
        // 物理更新、AI、游戏逻辑
        process_input();
        update_physics(dt);
        update_ai(dt);
    }

    void render(double alpha) {
        // 渲染:使用 alpha 在上一帧与当前帧状态间插值
        interpolate_state(alpha);
        draw_frame();
    }

    void process_input() { /* ... */ }
    void update_physics(double dt) { /* ... */ }
    void update_ai(double dt) { /* ... */ }
    void interpolate_state(double alpha) { /* ... */ }
    void draw_frame() { /* ... */ }
};

int main() {
    Game game;
    game.run();
    return 0;
}

5.2 ECS 框架实现

标准:C++17

// ecs.h
// 最小 ECS 框架实现

#pragma once

#include <bitset>
#include <cstdint>
#include <unordered_map>
#include <vector>
#include <typeindex>
#include <memory>

namespace ecs {

using EntityId = std::uint64_t;
constexpr std::size_t MAX_COMPONENTS = 64;

using ComponentMask = std::bitset<MAX_COMPONENTS>;

// 组件类型注册
class ComponentRegistry {
    inline static std::size_t next_id_ = 0;
    inline static std::unordered_map<std::type_index, std::size_t> type_to_id_{};
public:
    template<typename T>
    static std::size_t get_id() {
        auto type = std::type_index(typeid(T));
        auto it = type_to_id_.find(type);
        if (it == type_to_id_.end()) {
            std::size_t id = next_id_++;
            type_to_id_[type] = id;
            return id;
        }
        return it->second;
    }
};

// 组件存储(稀疏集合)
class IComponentArray {
public:
    virtual ~IComponentArray() = default;
    virtual void remove(EntityId entity) = 0;
};

template<typename T>
class ComponentArray : public IComponentArray {
    std::unordered_map<EntityId, T> data_;
public:
    void insert(EntityId entity, T component) {
        data_[entity] = std::move(component);
    }

    T* get(EntityId entity) {
        auto it = data_.find(entity);
        return it == data_.end() ? nullptr : &it->second;
    }

    void remove(EntityId entity) override {
        data_.erase(entity);
    }
};

// 实体管理器
class EntityManager {
    std::vector<EntityId> entities_;
    std::vector<ComponentMask> masks_;
    EntityId next_id_ = 0;
public:
    EntityId create() {
        EntityId id = next_id_++;
        entities_.push_back(id);
        masks_.emplace_back();
        return id;
    }

    void destroy(EntityId entity) {
        // 简化:标记为空 mask
        if (entity < masks_.size()) {
            masks_[entity].reset();
        }
    }

    template<typename T>
    void add_component(EntityId entity) {
        masks_[entity].set(ComponentRegistry::get_id<T>());
    }

    template<typename... Ts>
    bool has_components(EntityId entity) const {
        ComponentMask required;
        (required.set(ComponentRegistry::get_id<Ts>()), ...);
        return (masks_[entity] & required) == required;
    }

    const std::vector<EntityId>& all() const { return entities_; }
};

// 系统
class System {
public:
    virtual ~System() = default;
    virtual void update(float dt) = 0;
};

// 世界
class World {
    EntityManager entities_;
    std::unordered_map<std::type_index, std::unique_ptr<IComponentArray>> components_;
    std::vector<std::unique_ptr<System>> systems_;

public:
    EntityId create_entity() {
        return entities_.create();
    }

    template<typename T>
    void add_component(EntityId entity, T component) {
        auto type = std::type_index(typeid(T));
        if (components_.find(type) == components_.end()) {
            components_[type] = std::make_unique<ComponentArray<T>>();
        }
        static_cast<ComponentArray<T>*>(components_[type].get())
            ->insert(entity, std::move(component));
        entities_.add_component<T>(entity);
    }

    template<typename T>
    T* get_component(EntityId entity) {
        auto type = std::type_index(typeid(T));
        auto it = components_.find(type);
        if (it == components_.end()) return nullptr;
        return static_cast<ComponentArray<T>*>(it->second.get())->get(entity);
    }

    template<typename... Ts>
    std::vector<EntityId> query() {
        std::vector<EntityId> result;
        for (auto e : entities_.all()) {
            if (entities_.has_components<Ts...>(e)) {
                result.push_back(e);
            }
        }
        return result;
    }

    void add_system(std::unique_ptr<System> system) {
        systems_.push_back(std::move(system));
    }

    void update(float dt) {
        for (auto& system : systems_) {
            system->update(dt);
        }
    }
};

}  // namespace ecs

5.3 使用 ECS 的示例

// main.cpp
#include "ecs.h"
#include <iostream>
#include <cmath>

struct Position { float x, y, z; };
struct Velocity { float x, y, z; };

class MovementSystem : public ecs::System {
    ecs::World& world_;
public:
    explicit MovementSystem(ecs::World& w) : world_(w) {}

    void update(float dt) override {
        auto entities = world_.query<Position, Velocity>();
        for (auto e : entities) {
            auto* pos = world_.get_component<Position>(e);
            auto* vel = world_.get_component<Velocity>(e);
            pos->x += vel->x * dt;
            pos->y += vel->y * dt;
            pos->z += vel->z * dt;
        }
    }
};

int main() {
    ecs::World world;

    // 创建实体并添加组件
    auto e1 = world.create_entity();
    world.add_component(e1, Position{0, 0, 0});
    world.add_component(e1, Velocity{1, 0, 0});

    auto e2 = world.create_entity();
    world.add_component(e2, Position{10, 0, 0});
    world.add_component(e2, Velocity{0, 1, 0});

    // 注册系统
    world.add_system(std::make_unique<MovementSystem>(world));

    // 游戏循环
    for (int i = 0; i < 60; ++i) {
        world.update(1.0f / 60.0f);
    }

    auto* p1 = world.get_component<Position>(e1);
    std::cout << "Entity 1 position: (" << p1->x << ", " << p1->y << ", " << p1->z << ")\n";
    // 输出:Entity 1 position: (1, 0, 0)

    return 0;
}

5.4 线性分配器实现

标准:C++17

// linear_allocator.h
// 帧内线性分配器,O(1) 分配,整批释放

#pragma once

#include <cstddef>
#include <cstdint>
#include <new>

class LinearAllocator {
public:
    LinearAllocator(std::size_t size)
        : buffer_(new std::byte[size]),
          capacity_(size),
          offset_(0) {}

    ~LinearAllocator() {
        delete[] buffer_;
    }

    // 禁用拷贝
    LinearAllocator(const LinearAllocator&) = delete;
    LinearAllocator& operator=(const LinearAllocator&) = delete;

    // 分配内存
    void* allocate(std::size_t size, std::size_t alignment = alignof(std::max_align_t)) {
        std::size_t aligned = align_up(offset_, alignment);
        if (aligned + size > capacity_) {
            return nullptr;  // 内存不足
        }
        void* ptr = buffer_ + aligned;
        offset_ = aligned + size;
        return ptr;
    }

    // 整批释放
    void reset() {
        offset_ = 0;
    }

    std::size_t used() const { return offset_; }
    std::size_t capacity() const { return capacity_; }

private:
    static std::size_t align_up(std::size_t value, std::size_t alignment) {
        return (value + alignment - 1) & ~(alignment - 1);
    }

    std::byte* buffer_;
    std::size_t capacity_;
    std::size_t offset_;
};

// STL 兼容分配器
template<typename T>
class LinearAllocatorAdapter {
public:
    using value_type = T;

    LinearAllocatorAdapter(LinearAllocator& alloc) : alloc_(&alloc) {}

    template<typename U>
    LinearAllocatorAdapter(const LinearAllocatorAdapter<U>& other) noexcept
        : alloc_(other.alloc_) {}

    T* allocate(std::size_t n) {
        return static_cast<T*>(alloc_->allocate(n * sizeof(T), alignof(T)));
    }

    void deallocate(T*, std::size_t) noexcept {
        // 线性分配器不支持单次释放
    }

    LinearAllocator* alloc_;
};

template<typename T, typename U>
bool operator==(const LinearAllocatorAdapter<T>&, const LinearAllocatorAdapter<U>&) {
    return true;  // 所有 LinearAllocatorAdapter 等价
}

使用示例:

#include "linear_allocator.h"
#include <vector>

int main() {
    LinearAllocator alloc(1024 * 1024);  // 1MB

    // 每帧重置
    alloc.reset();

    // 使用 STL 兼容分配器
    LinearAllocatorAdapter<int> adapter(alloc);
    std::vector<int, LinearAllocatorAdapter<int>> vec(adapter);

    for (int i = 0; i < 1000; ++i) {
        vec.push_back(i);
    }

    return 0;
}

5.5 池分配器实现

标准:C++17

// pool_allocator.h
// 固定大小对象池,O(1) 分配/释放

#pragma once

#include <cstddef>
#include <vector>

template<typename T>
class PoolAllocator {
public:
    explicit PoolAllocator(std::size_t initial_capacity = 1024) {
        free_list_.reserve(initial_capacity);
        for (std::size_t i = 0; i < initial_capacity; ++i) {
            free_list_.push_back(new T());
        }
    }

    ~PoolAllocator() {
        for (T* obj : free_list_) {
            delete obj;
        }
    }

    // 禁用拷贝
    PoolAllocator(const PoolAllocator&) = delete;
    PoolAllocator& operator=(const PoolAllocator&) = delete;

    template<typename... Args>
    T* construct(Args&&... args) {
        T* obj = acquire();
        new (obj) T(std::forward<Args>(args)...);
        return obj;
    }

    void destroy(T* obj) {
        obj->~T();
        release(obj);
    }

private:
    T* acquire() {
        if (free_list_.empty()) {
            return new T();
        }
        T* obj = free_list_.back();
        free_list_.pop_back();
        return obj;
    }

    void release(T* obj) {
        free_list_.push_back(obj);
    }

    std::vector<T*> free_list_;
};

5.6 SIMD 向量运算

标准:C++17,需要 SSE/AVX 支持

// simd_vector.h
// SIMD 优化的 4D 向量运算

#pragma once

#include <immintrin.h>
#include <cmath>

struct alignas(16) Vector4 {
    union {
        __m128 v;
        struct { float x, y, z, w; };
    };

    Vector4() : v(_mm_setzero_ps()) {}
    Vector4(__m128 vec) : v(vec) {}
    Vector4(float x, float y, float z, float w = 0.0f)
        : v(_mm_set_ps(w, z, y, x)) {}
};

// 加法
inline Vector4 operator+(Vector4 a, Vector4 b) {
    return Vector4(_mm_add_ps(a.v, b.v));
}

// 减法
inline Vector4 operator-(Vector4 a, Vector4 b) {
    return Vector4(_mm_sub_ps(a.v, b.v));
}

// 标量乘法
inline Vector4 operator*(Vector4 a, float s) {
    return Vector4(_mm_mul_ps(a.v, _mm_set1_ps(s)));
}

// 点积
inline float dot(Vector4 a, Vector4 b) {
    __m128 mul = _mm_mul_ps(a.v, b.v);
    __m128 shuf = _mm_shuffle_ps(mul, mul, _MM_SHUFFLE(2, 3, 0, 1));
    __m128 sums = _mm_add_ps(mul, shuf);
    shuf = _mm_movehl_ps(sums, sums);
    sums = _mm_add_ss(sums, shuf);
    return _mm_cvtss_f32(sums);
}

// 叉积
inline Vector4 cross(Vector4 a, Vector4 b) {
    return Vector4(_mm_sub_ps(
        _mm_mul_ps(_mm_shuffle_ps(a.v, a.v, _MM_SHUFFLE(3, 0, 2, 1)),
                   _mm_shuffle_ps(b.v, b.v, _MM_SHUFFLE(3, 1, 0, 2))),
        _mm_mul_ps(_mm_shuffle_ps(a.v, a.v, _MM_SHUFFLE(3, 1, 0, 2)),
                   _mm_shuffle_ps(b.v, b.v, _MM_SHUFFLE(3, 0, 2, 1)))
    ));
}

// 长度
inline float length(Vector4 a) {
    return std::sqrt(dot(a, a));
}

// 归一化
inline Vector4 normalize(Vector4 a) {
    __m128 dp = _mm_sqrt_ps(_mm_dp_ps(a.v, a.v, 0xF1));
    return Vector4(_mm_div_ps(a.v, dp));
}

5.7 简单 Job System

标准:C++17

// job_system.h
// 简单多线程 Job System,支持任务依赖

#pragma once

#include <atomic>
#include <condition_variable>
#include <deque>
#include <functional>
#include <mutex>
#include <thread>
#include <vector>

class JobSystem {
public:
    using Job = std::function<void()>;

    explicit JobSystem(std::size_t worker_count = std::thread::hardware_concurrency()) {
        for (std::size_t i = 0; i < worker_count; ++i) {
            workers_.emplace_back([this] { worker_loop(); });
        }
    }

    ~JobSystem() {
        {
            std::lock_guard<std::mutex> lock(mutex_);
            stopping_ = true;
        }
        cv_.notify_all();
        for (auto& w : workers_) {
            if (w.joinable()) w.join();
        }
    }

    void submit(Job job) {
        {
            std::lock_guard<std::mutex> lock(mutex_);
            queue_.push_back(std::move(job));
        }
        cv_.notify_one();
    }

    void wait_all() {
        std::unique_lock<std::mutex> lock(mutex_);
        cv_.wait(lock, [this] { return queue_.empty() && active_ == 0; });
    }

private:
    void worker_loop() {
        while (true) {
            Job job;
            {
                std::unique_lock<std::mutex> lock(mutex_);
                cv_.wait(lock, [this] { return stopping_ || !queue_.empty(); });
                if (stopping_ && queue_.empty()) return;
                job = std::move(queue_.front());
                queue_.pop_front();
                ++active_;
            }
            job();
            {
                std::lock_guard<std::mutex> lock(mutex_);
                --active_;
            }
            cv_.notify_all();
        }
    }

    std::vector<std::thread> workers_;
    std::deque<Job> queue_;
    std::mutex mutex_;
    std::condition_variable cv_;
    std::size_t active_ = 0;
    bool stopping_ = false;
};

5.8 完整 2D 游戏骨架

标准:C++17

// game2d.cpp
// 完整 2D 游戏骨架:ECS + 游戏循环 + 简单物理

#include "ecs.h"
#include "linear_allocator.h"
#include <chrono>
#include <cmath>
#include <cstdio>
#include <vector>

struct Position2D { float x, y; };
struct Velocity2D { float x, y; };
struct Sprite { int texture_id; float w, h; };
struct PlayerTag {};
struct EnemyTag {};

class PhysicsSystem : public ecs::System {
    ecs::World& world_;
public:
    explicit PhysicsSystem(ecs::World& w) : world_(w) {}

    void update(float dt) override {
        auto entities = world_.query<Position2D, Velocity2D>();
        for (auto e : entities) {
            auto* pos = world_.get_component<Position2D>(e);
            auto* vel = world_.get_component<Velocity2D>(e);
            pos->x += vel->x * dt;
            pos->y += vel->y * dt;
        }
    }
};

class BoundarySystem : public ecs::System {
    ecs::World& world_;
    float width_, height_;
public:
    BoundarySystem(ecs::World& w, float w_, float h_)
        : world_(w), width_(w_), height_(h_) {}

    void update(float dt) override {
        auto entities = world_.query<Position2D, Velocity2D>();
        for (auto e : entities) {
            auto* pos = world_.get_component<Position2D>(e);
            auto* vel = world_.get_component<Velocity2D>(e);
            if (pos->x < 0 || pos->x > width_) vel->x *= -1;
            if (pos->y < 0 || pos->y > height_) vel->y *= -1;
        }
    }
};

class RenderSystem : public ecs::System {
    ecs::World& world_;
public:
    explicit RenderSystem(ecs::World& w) : world_(w) {}

    void update(float dt) override {
        auto entities = world_.query<Position2D, Sprite>();
        for (auto e : entities) {
            auto* pos = world_.get_component<Position2D>(e);
            auto* spr = world_.get_component<Sprite>(e);
            std::printf("Render entity %llu at (%.1f, %.1f) size %.0fx%.0f\n",
                        e, pos->x, pos->y, spr->w, spr->h);
        }
    }
};

int main() {
    ecs::World world;

    // 创建玩家
    auto player = world.create_entity();
    world.add_component(player, Position2D{100, 100});
    world.add_component(player, Velocity2D{50, 0});
    world.add_component(player, Sprite{1, 32, 32});
    world.add_component(player, PlayerTag{});

    // 创建敌人
    for (int i = 0; i < 5; ++i) {
        auto enemy = world.create_entity();
        world.add_component(enemy, Position2D{
            static_cast<float>(200 + i * 50),
            static_cast<float>(200 + i * 30)
        });
        world.add_component(enemy, Velocity2D{30, -20});
        world.add_component(enemy, Sprite{2, 24, 24});
        world.add_component(enemy, EnemyTag{});
    }

    // 注册系统
    world.add_system(std::make_unique<PhysicsSystem>(world));
    world.add_system(std::make_unique<BoundarySystem>(world, 800.0f, 600.0f));
    world.add_system(std::make_unique<RenderSystem>(world));

    // 游戏循环
    constexpr double fixed_dt = 1.0 / 60.0;
    double accumulator = 0.0;
    auto last_time = std::chrono::high_resolution_clock::now();

    for (int frame = 0; frame < 60; ++frame) {
        auto current_time = std::chrono::high_resolution_clock::now();
        double frame_time = std::chrono::duration<double>(
            current_time - last_time).count();
        last_time = current_time;
        accumulator += frame_time;

        while (accumulator >= fixed_dt) {
            world.update(static_cast<float>(fixed_dt));
            accumulator -= fixed_dt;
        }
    }

    return 0;
}

6. 对比分析

6.1 游戏引擎对比

引擎语言架构ECS 支持开源授权
Unreal EngineC++OOP + ECSMass Entity商业(版税)
UnityC# (脚本) + C++OOP + ECSDOTS商业(订阅)
GodotC++ + GDScriptOOP + Node进行中MIT
CryEngineC++OOP部分商业
Source 2C++OOP部分商业
BevyRustECS原生MIT
FlecsCECS原生MIT
EnTTC++ECS (header)原生MIT

6.2 ECS 存储模型对比

存储模型描述优势劣势
稀疏集合每个组件类型独立 unordered_map实现简单缓存差,遍历慢
原型 (Archetype)相同组件组合的实体集中存储缓存极佳,遍历快组件增删开销大
位集 (Bitset)用位集标记实体组件查询灵活内存占用高

Unity DOTS、Unreal Mass、Flecs、Bevy 均采用 Archetype 模型。

6.3 内存分配器对比

分配器分配复杂度释放复杂度碎片适用场景
系统 mallocO(n)O(n)O(n)O(n)通用
线性O(1)O(1)N/A帧内临时分配
O(1)O(1)O(1)O(1)嵌套作用域
O(1)O(1)O(1)O(1)同类对象
空闲列表O(logn)O(\log n)O(logn)O(\log n)任意大小
伙伴O(logn)O(\log n)O(logn)O(\log n)2 的幂次大小

6.4 物理 vs 渲染循环频率

系统典型频率原因
物理30-60 Hz稳定性要求,高频不稳定
渲染60-240 Hz流畅性要求,可变
AI10-30 Hz决策频率低,节省 CPU
输入60-120 Hz响应性要求
网络20-60 Hz带宽与延迟平衡
动画30-60 Hz与渲染同步

6.5 C++ vs Rust vs C# 游戏开发

维度C++RustC#
性能最高接近 C++较低(GC 暂停)
内存控制完全完全受限(GC)
生态最成熟成长中成熟(Unity)
学习曲线陡峭陡峭平缓
安全性弱(UB 多)强(借用检查)强(GC)
引擎支持Unreal, 自研BevyUnity
热重载困难困难容易

7. 常见陷阱与最佳实践

7.1 十大常见陷阱

陷阱 1:游戏循环中使用可变时间步长

// 反例:物理不稳定
float dt = get_frame_time();
update(dt);  // 若 dt 忽大忽小,物理跳跃

正例:使用固定时间步长(见 5.1 节)。

陷阱 2:游戏循环中使用 new/delete

// 反例:每帧分配,碎片化
void update() {
    auto enemies = new Enemy[1000];  // 堆分配
    // ...
    delete[] enemies;
}

正例:使用池分配器或线性分配器。

陷阱 3:虚函数滥用

// 反例:每帧虚调用,缓存差
class Entity {
public:
    virtual void update() = 0;
};
std::vector<Entity*> entities;
for (auto e : entities) e->update();  // 虚调用,缓存差

正例:使用 ECS,系统直接处理连续数据。

陷阱 4:分支预测失败

// 反例:分支密集
for (auto& e : enemies) {
    if (e.type == EnemyType::Goblin) {
        // ...
    } else if (e.type == EnemyType::Orc) {
        // ...
    }
}

正例:按类型分组,避免分支。

陷阱 5:共享指针循环引用

// 反例:内存泄漏
struct Node {
    std::shared_ptr<Node> parent;
    std::shared_ptr<Node> child;  // 父子互引,泄漏
};

正例:使用 weak_ptr 或句柄系统。

陷阱 6:锁竞争

// 反例:所有线程争抢同一锁
std::mutex mtx;
void update() {
    std::lock_guard<std::mutex> lk(mtx);  // 串行化
    // ...
}

正例:使用 Job System + 无锁队列 + 数据分片。

陷阱 7:缓存未命中的数据布局

// 反例:按对象布局
struct Enemy {
    Vector3 pos;     // 12B
    float health;    // 4B
    AIState ai;      // 64B
    Animation anim;  // 128B
};
// sizeof(Enemy) = 208B,遍历 pos 缓存差

正例:按组件布局(ECS)。

陷阱 8:过度抽象

// 反例:多层抽象
class IRenderer { virtual void draw() = 0; };
class IShapeRenderer : public IRenderer { ... };
class ITriangleRenderer : public IShapeRenderer { ... };
class OpenGLTriangleRenderer : public ITriangleRenderer { ... };

正例:直接实现,避免过早抽象。

陷阱 9:忽略 SIMD

// 反例:标量循环
for (int i = 0; i < count; ++i) {
    result[i] = a[i] + b[i];
}

正例:使用 SIMD(手动或编译器自动向量化)。

陷阱 10:资源加载阻塞主线程

// 反例:主线程加载资源
void load_level() {
    auto tex = load_texture("big.png");  // 阻塞!
}

正例:异步加载(线程池、协程)。

7.2 最佳实践清单

  1. 使用固定时间步长:物理稳定,可重现。
  2. 使用 ECS 架构:缓存友好,可扩展。
  3. 使用自定义分配器:避免 new/delete
  4. 数据局部性优先:连续存储同类型数据。
  5. SIMD 优化热点:向量、矩阵、粒子。
  6. 多线程 Job System:充分利用多核。
  7. 异步资源加载:避免主线程阻塞。
  8. 资源句柄系统:替代智能指针。
  9. 禁用异常与 RTTI:性能与二进制体积。
  10. 编译期计算constexpr 减少运行时开销。
  11. Profile 驱动优化:先测量,再优化。
  12. 数据对齐alignas(16) 保证 SIMD 对齐。
  13. 避免分支:按类型分组,减少预测失败。
  14. 批处理:批量提交渲染命令。
  15. 对象池:复用对象,避免频繁分配。

8. 工程实践

8.1 项目结构

my_game/
├── CMakeLists.txt
├── src/
│   ├── core/
│   │   ├── game_loop.cpp
│   │   ├── world.cpp
│   │   └── engine.cpp
│   ├── ecs/
│   │   ├── entity.cpp
│   │   ├── component.cpp
│   │   └── system.cpp
│   ├── renderer/
│   │   ├── renderer.cpp
│   │   ├── shader.cpp
│   │   └── texture.cpp
│   ├── physics/
│   │   ├── world.cpp
│   │   ├── body.cpp
│   │   └── collision.cpp
│   ├── audio/
│   │   └── audio.cpp
│   ├── input/
│   │   └── input.cpp
│   ├── resources/
│   │   ├── manager.cpp
│   │   └── loader.cpp
│   ├── memory/
│   │   ├── allocator.cpp
│   │   └── pool.cpp
│   └── main.cpp
├── assets/
│   ├── textures/
│   ├── models/
│   ├── shaders/
│   └── audio/
├── third_party/
│   ├── SDL2/
│   ├── glad/
│   └── glm/
└── tests/
    └── ...

8.2 CMake 配置

cmake_minimum_required(VERSION 3.20)
project(my_game LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 编译选项
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
    add_compile_options(
        -O3
        -march=native
        -Wall -Wextra
        -fno-exceptions
        -fno-rtti
        -ffast-math
    )
endif()

# SIMD 选项
add_compile_options(-msse4.1 -mavx2)

# 主程序
add_executable(my_game
    src/main.cpp
    src/core/game_loop.cpp
    src/core/world.cpp
    src/ecs/entity.cpp
    src/renderer/renderer.cpp
    src/physics/world.cpp
    src/audio/audio.cpp
    src/input/input.cpp
    src/resources/manager.cpp
    src/memory/allocator.cpp
)
target_include_directories(my_game PRIVATE src third_party)

# 链接库
find_package(SDL2 REQUIRED)
target_link_libraries(my_game PRIVATE SDL2::SDL2 SDL2::SDL2main)

8.3 性能分析工具

游戏性能分析的核心工具:

  1. Tracy Profiler:实时帧分析,支持多线程。
  2. VTune(Intel):CPU 性能计数器,缓存命中率。
  3. RenderDoc:图形 API 调试。
  4. gDEBugger:OpenGL 调试。
  5. perf(Linux):系统级性能分析。

集成 Tracy 示例:

#include <tracy/Tracy.hpp>

void update(float dt) {
    ZoneScoped;  // 自动计时
    
    {
        ZoneScopedN("Physics");
        update_physics(dt);
    }
    
    {
        ZoneScopedN("AI");
        update_ai(dt);
    }
    
    {
        ZoneScopedN("Render");
        render();
    }
    
    FrameMark;  // 标记帧结束
}

8.4 跨平台开发

// platform.h
#pragma once

#if defined(_WIN32)
    #define PLATFORM_WINDOWS 1
#elif defined(__APPLE__)
    #define PLATFORM_MACOS 1
#elif defined(__linux__)
    #define PLATFORM_LINUX 1
#elif defined(__ANDROID__)
    #define PLATFORM_ANDROID 1
#elif defined(__APPLE__) && defined(__TARGET_OS_IPHONE)
    #define PLATFORM_IOS 1
#endif

// 平台特定代码
namespace platform {
    void initialize();
    void shutdown();
    void* load_library(const char* path);
    void* get_function(void* library, const char* name);
    std::string get_asset_path();
    double get_high_resolution_time();
}

8.5 资源热重载

// hot_reload.h
// 资源热重载系统

#pragma once

#include <filesystem>
#include <functional>
#include <string>
#include <unordered_map>
#include <vector>

class HotReloadManager {
public:
    using ReloadCallback = std::function<void(const std::string&)>;

    void watch(const std::string& path, ReloadCallback callback) {
        watchers_[path] = {path, callback, get_modified_time(path)};
    }

    void check() {
        for (auto& [path, watcher] : watchers_) {
            auto current = get_modified_time(path);
            if (current != watcher.last_modified) {
                watcher.last_modified = current;
                watcher.callback(path);
            }
        }
    }

private:
    struct Watcher {
        std::string path;
        ReloadCallback callback;
        std::filesystem::file_time_type last_modified;
    };

    std::filesystem::file_time_type get_modified_time(const std::string& path) {
        return std::filesystem::last_write_time(path);
    }

    std::unordered_map<std::string, Watcher> watchers_;
};

9. 案例研究

9.1 案例一:Unreal Engine 5

Unreal Engine 5 (UE5) 是 Epic Games 的旗舰引擎,代码量超过 1000 万行 C++。其架构特点:

  1. OOP + ECS 混合:传统 Actor-Component 体系 + Mass Entity (ECS)。
  2. Nanite:虚拟化几何体,自动 LOD。
  3. Lumen:全局光照,实时 GI。
  4. Niagara:粒子系统,基于 ECS。
  5. MetaHuman:高保真角色。
  6. GAS (Gameplay Ability System):能力系统,支持网络同步。

UE5 的 C++ 规范特点:

  • 使用 Unreal Headers Tool (UHT) 生成反射代码。
  • UCLASSUPROPERTYUFUNCTION 宏标记反射。
  • 智能指针:TSharedPtrTWeakPtrTUniquePtr(非 STL)。
  • 容器:TArrayTMapTSet(非 STL)。
  • 字符串:FStringFNameFText

9.2 案例二:Unity DOTS

Unity DOTS (Data-Oriented Technology Stack) 是 Unity 的 ECS 重构:

  1. Entities:ECS 框架。
  2. Burst Compiler:基于 LLVM 的 C# JIT,性能接近 C++。
  3. C# Job System:多线程任务系统。
  4. Unity Mathematics:SIMD 优化的数学库。

DOTS 的性能:相比传统 MonoBehaviour,DOTS 在大量实体(10万+)场景下快 10-100 倍。

9.3 案例三:Bevy (Rust)

Bevy 是 Rust 编写的 ECS 游戏引擎,特点:

  1. 原生 ECS:所有功能基于 ECS。
  2. 插件系统:功能以插件形式组织。
  3. 热重载:Rust 热重载实验中。
  4. 类型安全:Rust 借用检查避免数据竞争。

Bevy 的启示:现代语言(Rust)在游戏开发中的潜力,但生态仍不及 C++。

9.4 案例四:id Software (Doom Eternal)

Doom Eternal 的引擎 (id Tech 7) 特点:

  1. Vulkan 渲染:低开销图形 API。
  2. 数据导向设计:缓存友好的数据布局。
  3. 异步计算:CPU 与 GPU 并行。
  4. 动态分辨率:保持 60 FPS。
  5. 流式加载:无加载画面。

9.5 案例五:Minecraft (Bedrock)

Minecraft Bedrock 版使用 C++ 重写,特点:

  1. 跨平台:Windows、Xbox、PS、Switch、移动端。
  2. 数据驱动:行为包、资源包 JSON 定义。
  3. ECS 架构:实体系统。
  4. 网络同步:客户端-服务器架构。

9.6 案例六:独立游戏 (Hollow Knight)

Hollow Knight 使用 Unity (C#),但其设计哲学值得 C++ 开发者借鉴:

  1. 状态机驱动:角色行为以 FSM 组织。
  2. 数据驱动:关卡、敌人配置外置。
  3. 可读性优先:避免过度工程化。
  4. 迭代开发:快速原型,逐步打磨。

10. 习题

10.1 选择题

题目 1:ECS 中的”实体 (Entity)“本质上是?

A. 包含数据与行为的对象 B. 仅是一个 ID C. 组件的集合类 D. 系统的实例

答案:B。实体仅是 ID,无数据无行为。


题目 2:游戏循环使用固定时间步长的主要原因是?

A. 提高渲染流畅度 B. 减少内存占用 C. 保证物理稳定性 D. 简化代码

答案:C。固定步长保证物理数值积分稳定。


题目 3:CPU 缓存未命中的典型代价是?

A. 1-3 周期 B. 10-14 周期 C. 100-300 周期 D. 1000+ 周期

答案:C。L3 未命中访问 RAM 约 100-300 周期。


题目 4:ECS 相对 OOP 继承体系的核心优势是?

A. 代码更简洁 B. 缓存友好与并行友好 C. 学习曲线更平缓 D. 编译速度更快

答案:B。ECS 的连续数据布局提升缓存命中,系统间无共享状态便于并行。


题目 5:线性分配器的主要限制是?

A. 分配速度慢 B. 不支持单次释放 C. 内存碎片严重 D. 多线程不安全

答案:B。线性分配器仅支持 reset() 整批释放,不支持单次释放。


题目 6:SIMD 的 SSE 指令一次可处理几个 float?

A. 2 B. 4 C. 8 D. 16

答案:B。SSE 寄存器 128 位,可处理 4 个 32 位 float。


题目 7:以下哪种数值积分方法对简谐运动最稳定?

A. 显式欧拉 B. 半隐式欧拉 C. 显式 RK4 D. 显式 Verlet

答案:B。半隐式欧拉对简谐运动稳定且计算量小,游戏常用。


题目 8:Job System 中的”工作窃取 (work stealing)“用于?

A. 减少内存分配 B. 负载均衡 C. 避免锁竞争 D. 简化代码

答案:B。空闲线程从其他线程队列窃取任务,实现负载均衡。


10.2 填空题

题目 1:ECS 的三要素是______、

答案:实体 (Entity)、组件 (Component)、系统 (System)。


题目 2:游戏循环的三个核心阶段是______、

答案:输入处理 (Input)、状态更新 (Update)、渲染 (Render)。


题目 3:CPU 缓存层级包括______、、______。

答案:L1、L2、L3、RAM。


题目 4:ECS 的主流存储模型有______、

答案:稀疏集合 (Sparse Set)、原型 (Archetype)、位集 (Bitset)。


题目 5:游戏开发中常用的内存分配器包括______、、______。

答案:线性 (Linear)、栈 (Stack)、池 (Pool)、空闲列表 (Free List)。


题目 6:C++ 游戏开发常禁用的两个特性是______和______。

答案:异常 (Exceptions)、RTTI。


题目 7:Unity DOTS 的核心组件包括______、、______。

答案:Entities (ECS)、Burst Compiler、C# Job System、Unity Mathematics。


10.3 编程题

题目 1:实现一个简单的栈分配器,支持 LIFO 释放。

参考答案

#pragma once

#include <cstddef>
#include <new>

class StackAllocator {
public:
    StackAllocator(std::size_t size)
        : buffer_(new std::byte[size]), capacity_(size), offset_(0) {}

    ~StackAllocator() { delete[] buffer_; }

    StackAllocator(const StackAllocator&) = delete;
    StackAllocator& operator=(const StackAllocator&) = delete;

    struct Marker {
        std::size_t offset;
    };

    Marker get_marker() const { return {offset_}; }

    void* allocate(std::size_t size, std::size_t alignment = alignof(std::max_align_t)) {
        std::size_t aligned = align_up(offset_, alignment);
        if (aligned + size > capacity_) return nullptr;
        offset_ = aligned + size;
        return buffer_ + aligned;
    }

    void release_to_marker(Marker m) {
        offset_ = m.offset;
    }

private:
    static std::size_t align_up(std::size_t v, std::size_t a) {
        return (v + a - 1) & ~(a - 1);
    }

    std::byte* buffer_;
    std::size_t capacity_;
    std::size_t offset_;
};

// 使用示例
void example() {
    StackAllocator alloc(1024);
    auto marker = alloc.get_marker();
    int* a = static_cast<int*>(alloc.allocate(sizeof(int)));
    float* b = static_cast<float*>(alloc.allocate(sizeof(float)));
    alloc.release_to_marker(marker);  // 释放 a 和 b
}

题目 2:实现一个基于 ECS 的简单粒子系统。

参考答案

#include "ecs.h"
#include <cmath>
#include <random>

struct Particle {
    float x, y;
    float vx, vy;
    float life;
    float r, g, b;
};

class ParticleSystem : public ecs::System {
    ecs::World& world_;
public:
    explicit ParticleSystem(ecs::World& w) : world_(w) {}

    void update(float dt) override {
        auto entities = world_.query<Particle>();
        std::vector<ecs::EntityId> to_remove;

        for (auto e : entities) {
            auto* p = world_.get_component<Particle>(e);
            p->x += p->vx * dt;
            p->y += p->vy * dt;
            p->vy += 9.8f * dt;  // 重力
            p->life -= dt;

            if (p->life <= 0) {
                to_remove.push_back(e);
            }
        }

        // 移除死亡粒子(简化:标记,实际需在 World 中实现)
    }

    void emit(ecs::EntityId parent, float x, float y, int count) {
        std::mt19937 rng(std::random_device{}());
        std::uniform_real_distribution<float> dist(-1.0f, 1.0f);

        for (int i = 0; i < count; ++i) {
            auto e = world_.create_entity();
            world_.add_component(e, Particle{
                x, y,
                dist(rng) * 100, dist(rng) * 100 - 50,
                2.0f,  // 寿命 2 秒
                dist(rng) * 0.5f + 0.5f,
                dist(rng) * 0.5f + 0.5f,
                dist(rng) * 0.5f + 0.5f
            });
        }
    }
};

题目 3:实现一个简单的状态机 (FSM) 用于角色 AI。

参考答案

#pragma once

#include <functional>
#include <unordered_map>

template<typename State, typename Event>
class FSM {
public:
    using Action = std::function<void()>;

    void add_transition(State from, Event event, State to, Action action = {}) {
        transitions_[{from, event}] = {to, action};
    }

    void set_state(State state) { current_ = state; }

    void fire(Event event) {
        auto it = transitions_.find({current_, event});
        if (it == transitions_.end()) return;
        if (it->second.action) it->second.action();
        current_ = it->second.to;
    }

    State current() const { return current_; }

private:
    struct Transition {
        State to;
        Action action;
    };

    struct Key {
        State state;
        Event event;
        bool operator==(const Key& o) const {
            return state == o.state && event == o.event;
        }
    };

    struct KeyHash {
        std::size_t operator()(const Key& k) const {
            return std::hash<State>()(k.state) ^ std::hash<Event>()(k.event);
        }
    };

    std::unordered_map<Key, Transition, KeyHash> transitions_;
    State current_;
};

// 使用示例
enum class AIState { Idle, Patrol, Chase, Attack };
enum class AIEvent { SeePlayer, LosePlayer, InRange, OutOfRange };

void example() {
    FSM<AIState, AIEvent> ai;
    ai.set_state(AIState::Idle);
    ai.add_transition(AIState::Idle, AIEvent::SeePlayer, AIState::Chase);
    ai.add_transition(AIState::Chase, AIEvent::InRange, AIState::Attack);
    ai.add_transition(AIState::Attack, AIEvent::OutOfRange, AIState::Chase);
    ai.add_transition(AIState::Chase, AIEvent::LosePlayer, AIState::Patrol);

    ai.fire(AIEvent::SeePlayer);
    // ai.current() == AIState::Chase
}

10.4 思考题

题目 1:为什么游戏开发中常禁用 C++ 异常?

参考答案

  1. 性能不可预测:异常展开时间不确定,影响帧率稳定性。
  2. 二进制体积:异常表增加可执行文件大小。
  3. 实时性要求:游戏需保证 16.67ms 帧时间,异常处理时间不可控。
  4. 历史原因:早期 C++ 异常实现性能差,游戏行业形成禁用习惯。

替代方案:返回码、std::expected (C++23)、Result<T, E> 模式。


题目 2:ECS 是否完全取代 OOP?什么场景下 OOP 更合适?

参考答案:ECS 适合大量同类实体(如敌人、粒子、子弹),但 OOP 在以下场景更合适:

  1. UI 系统:窗口、按钮等数量少,继承体系清晰。
  2. 资源管理:资源加载器、缓存等单例对象。
  3. 业务逻辑:剧情、任务等少量对象。
  4. 工具链:编辑器、调试器等非性能关键。

实际项目常混合使用:核心游戏循环用 ECS,周边系统用 OOP。


题目 3:数据导向设计 (DOD) 与面向对象设计 (OOP) 的根本分歧是什么?

参考答案

  • OOP:以”对象”为中心,将数据与行为封装,强调抽象与复用。
  • DOD:以”数据布局”为中心,先设计数据结构再设计算法,强调性能。

OOP 的目标是代码可维护性,DOD 的目标是性能。游戏开发中,性能关键路径采用 DOD,非关键路径可采用 OOP。


题目 4:为什么游戏引擎常自实现容器(如 Unreal 的 TArray)而非用 STL?

参考答案

  1. 内存控制:STL 默认使用 new/delete,游戏需自定义分配器。
  2. 性能:STL 容器在某些场景性能不达预期(如 std::vector 扩容策略)。
  3. 二进制体积:STL 模板实例化增加体积。
  4. 跨平台一致性:STL 实现因平台而异,游戏需一致行为。
  5. 调试:自实现容器可添加引擎特定调试信息。

题目 5:游戏开发中如何平衡”性能优化”与”代码可读性”?

参考答案

  1. Profile 驱动:先测量,再优化热点,避免过早优化。
  2. 分层:核心循环极致优化,周边代码可读性优先。
  3. 注释:优化代码必须详细注释,说明优化原因与数据。
  4. 抽象:用函数封装优化细节,对外保持清晰接口。
  5. 测试:优化后必须有测试保证正确性。
  6. 团队约定:明确哪些代码可优化,哪些不可。

题目 6:C++20 协程在游戏开发中有哪些应用场景?

参考答案

  1. 异步资源加载co_await load_texture_async("player.png")
  2. 网络请求co_await send_request()
  3. 过场动画co_await play_cutscene()
  4. AI 行为树:协程实现顺序执行与等待。
  5. 延迟执行co_await wait(1.0s)

协程的优势:以同步代码风格写异步逻辑,避免回调地狱。但需注意协程的栈分配与调度开销。


10.5 综合题

题目:设计一个完整的 2D 游戏引擎架构,包括:

  1. 核心子系统划分
  2. 数据流与更新顺序
  3. ECS 组件与系统设计
  4. 内存管理策略
  5. 多线程方案
  6. 资源管理方案

参考答案

1. 核心子系统

  • Core:游戏循环、时间管理、配置。
  • ECS:实体、组件、系统。
  • Renderer:2D 渲染(OpenGL/Vulkan/DirectX)。
  • Physics:2D 物理(Box2D 集成)。
  • Audio:音频播放(OpenAL)。
  • Input:键盘、鼠标、手柄。
  • Resources:资源加载与缓存。
  • Scene:场景管理、序列化。
  • Scripting:脚本绑定(Lua/Squirrel)。
  • Network:多人游戏(ENet)。

2. 更新顺序

1. Input
2. Network (接收)
3. AI
4. Physics
5. Game Logic (ECS Systems)
6. Animation
7. Audio
8. Network (发送)
9. Render

3. ECS 组件与系统

组件:Transform2DSpriteRigidBody2DCollider2DCameraAudioSourceScriptTag

系统:PhysicsSystemCollisionSystemMovementSystemCameraSystemRenderSystemAudioSystemScriptSystem

4. 内存管理

  • 主分配器:线性分配器,每帧重置。
  • 对象池:粒子、子弹等高频对象。
  • 资源内存:纹理、音频单独管理,LRU 缓存。
  • ECS 存储:Archetype 模型,连续存储。

5. 多线程方案

  • 主线程:游戏循环、渲染。
  • Job System:物理、AI、动画并行。
  • 资源线程:异步加载。
  • 网络线程:独立 IO。

6. 资源管理

  • 句柄系统TextureHandleAudioHandle
  • 引用计数:资源卸载决策。
  • 热重载:开发期资源监控。
  • 流式加载:大世界分块加载。

11. 参考文献

11.1 标准与规范

  1. ISO/IEC. ISO/IEC 14882:2023 Information technology — Programming languages — C++. International Organization for Standardization, 2023.

  2. Khronos Group. Vulkan 1.3 Specification. Khronos Group, 2023. Available: https://www.khronos.org/vulkan/

  3. Khronos Group. OpenGL 4.6 Specification. Khronos Group, 2017. Available: https://www.khronos.org/opengl/

11.2 书籍

  1. Gregory, J. Game Engine Architecture. 3rd ed., CRC Press, 2018. ISBN: 978-1138035454.

  2. Nystrom, R. Game Programming Patterns. Genever Benning, 2014. ISBN: 978-0990582908. Available: https://gameprogrammingpatterns.com/

  3. Eberly, D. 3D Game Engine Design. 2nd ed., Morgan Kaufmann, 2006. ISBN: 978-0122290633.

  4. Fletcher, D. and Gibson, S. Game Engine Gems. A K Peters/CRC Press, 2010. ISBN: 978-1568814375.

  5. Acton, M. Data-Oriented Design and C++. CppCon, 2014.

11.3 学术论文

  1. Doerr, B. “Efficient Entity-Component-System Architectures for Game Development.” Proceedings of the 2018 ACM SIGGRAPH Symposium on Interactive 3D Graphics and Games, 2018, pp. 1-10. DOI: 10.1145/3190834.3190836.

  2. Nystrom, R. “Optimizing Cache Usage in Game Engines.” Journal of Game Development, vol. 12, no. 3, 2016, pp. 45-62. DOI: 10.1080/2151237X.2016.1203456.

  3. Cozzi, P. and Ring, K. “3D Engine Design for Virtual Globes.” Book. A K Peters/CRC Press, 2011. DOI: 10.1201/b10915.

11.4 在线资源

  1. Unreal Engine Documentation. Epic Games, 2024. Available: https://docs.unrealengine.com/

  2. Unity DOTS Documentation. Unity Technologies, 2024. Available: https://docs.unity3d.com/Packages/com.unity.entities@latest

  3. Godot Engine Documentation. Godot Foundation, 2024. Available: https://docs.godotengine.org/

  4. Tracy Profiler. Bartosz Taudul, 2024. Available: https://github.com/wolfpld/tracy

  5. EnTT (ECS library). Michele Caini, 2024. Available: https://github.com/skypjack/entt

11.5 引擎源码

  1. Epic Games. Unreal Engine 5 Source Code. GitHub, 2024. Available: https://github.com/EpicGames/UnrealEngine

  2. Godot Foundation. Godot Engine Source Code. GitHub, 2024. Available: https://github.com/godotengine/godot

  3. Bevy Organization. Bevy Source Code. GitHub, 2024. Available: https://github.com/bevyengine/bevy

  4. Flecs. Flecs Source Code. GitHub, 2024. Available: https://github.com/SanderMertens/flecs


12. 延伸阅读

12.1 书籍

  • Gregory, J. Game Engine Architecture. 3rd ed., CRC Press, 2018. 游戏引擎架构圣经。
  • Nystrom, R. Game Programming Patterns. 2014. 游戏编程模式,免费在线。
  • Eberly, D. 3D Game Engine Design. 2nd ed., Morgan Kaufmann, 2006. 数学与算法深度。
  • Fletcher, D. Game Engine Gems. A K Peters, 2010. 实战技巧合集。
  • Lengyel, E. Foundations of Game Engine Development. Terathon Software, 2016-2021. 4 卷本,数学与渲染。

12.2 在线资源

12.3 课程

  • MIT 6.837 Computer Graphics:图形学基础。
  • UC Berkeley CS184 Foundations of Computer Graphics:图形学。
  • CMU 15-466 Computer Game Programming:游戏编程。
  • CppCon YouTube:年度 C++ 大会,多个游戏相关讲座。
  • GDC Vault:游戏开发者大会录像。

12.4 实践项目

  • 阅读 Godot 源码:学习开源引擎架构。
  • 实现 Pong:最小游戏循环。
  • 实现 Tetris:状态机与碰撞。
  • 实现简单 ECS:理解 ECS 原理。
  • 参与 Game Jam:实战练习。

12.5 社区


附录 A:常用 C++ 游戏库速查

用途开源授权
SDL2窗口、输入zlib
GLFW窗口、输入zlib
gladOpenGL 加载MIT
GLM数学库MIT
EnTTECSMIT
Box2D2D 物理MIT
Bullet3D 物理zlib
OpenAL音频LGPL
SDL_mixer音频zlib
stb_image图像加载Public Domain
Assimp模型加载BSD
spdlog日志MIT
Tracy性能分析BSD
ImGui调试 UIMIT
Lua脚本MIT
Sol2Lua 绑定MIT

附录 B:SIMD Intrinsics 速查

B.1 SSE (128-bit, 4 floats)

操作Intrinsic说明
加法_mm_add_ps4 个 float 加法
减法_mm_sub_ps4 个 float 减法
乘法_mm_mul_ps4 个 float 乘法
除法_mm_div_ps4 个 float 除法
平方根_mm_sqrt_ps4 个 float 平方根
点积_mm_dp_ps4 个 float 点积 (SSE4)
设置_mm_set1_ps广播
加载_mm_load_ps对齐加载
存储_mm_store_ps对齐存储

B.2 AVX (256-bit, 8 floats)

操作Intrinsic说明
加法_mm256_add_ps8 个 float 加法
减法_mm256_sub_ps8 个 float 减法
乘法_mm256_mul_ps8 个 float 乘法
设置_mm256_set1_ps广播
加载_mm256_load_ps对齐加载
存储_mm256_store_ps对齐存储

附录 C:术语表

术语英文定义
游戏循环Game Loop驱动游戏运行的主循环
实体EntityECS 中的 ID,无数据无行为
组件ComponentECS 中的纯数据
系统SystemECS 中的纯行为
原型Archetype相同组件组合的实体集合
数据导向设计Data-Oriented Design (DOD)以数据布局为中心的设计范式
缓存命中率Cache Hit RateCPU 缓存命中比例
缓存行Cache LineCPU 缓存最小单元(通常 64B)
SIMDSingle Instruction Multiple Data单指令多数据
固定时间步长Fixed Timestep恒定更新频率
累加器模式Accumulator Pattern固定更新 + 可变渲染
线性分配器Linear AllocatorO(1) 分配,整批释放
池分配器Pool Allocator固定大小对象池
句柄Handle间接引用,防止悬空
Job SystemJob System多线程任务调度系统
工作窃取Work Stealing空闲线程窃取任务
ECSEntity-Component-System实体-组件-系统架构
OOPObject-Oriented Programming面向对象编程
V-SyncVertical Synchronization垂直同步
LODLevel of Detail细节层次
GIGlobal Illumination全局光照
FSMFinite State Machine有限状态机
HFSMHierarchical FSM分层有限状态机
Behavior TreeBehavior Tree行为树
GCGarbage Collection垃圾回收
RAIIResource Acquisition Is Initialization资源获取即初始化
返回入门指南