中EN
概念页

运行时对象模型架构

Runtime Object Model Architectures

玩法基础系统 (gameplay foundation system) 是位于"引擎与游戏分界线"附近、供构建游戏规则和动态元素的一组运行时组件,其中最复杂的是运行时对象模型:它在运行时实现世界编辑器展示给策划的抽象对象模型。GEA 把架构分为两大类:以对象为中心(每个工具侧对象对应一个或一小组类实例)和以属性为中心(对象只是一个 id,属性分散在按属性类型划分、以 id 为键的表里)。前者从单体类层次走向组合与组件,后者更省内存、更缓存友好、更易数据驱动,但难以表达属性间关系、也更难调试(GEA4 卷 II 17.1–17.2)。

玩法基础系统的组成

业界没有统一名称,GEA 称之为玩法基础系统。理论上可以与游戏无关,实际上几乎总带着类型或游戏特有的细节;"引擎与游戏的分界线"在这里是一片渐变的模糊地带,各引擎差异也在这里最大(卷 II 17.1)。主要子系统:

子系统职责本库页面
运行时对象模型实现工具侧抽象对象模型本页
关卡管理与流送加载卸载世界内容,流送制造无缝大世界的假象游戏世界的加载与流送
实时对象模型更新周期性更新每个对象,各系统在这里汇合成整体游戏对象的实时更新
消息与事件处理对象间通信;消息常表示世界状态变化即"事件"事件与消息传递
脚本提高生产力、快速迭代、把能力交给非程序员;文本(Python、Lua)或图形(UE 蓝图)游戏脚本系统
目标与游戏流程管理管理目标序列/树/图、章节、区域门控,策划称之为游戏的"脊柱"游戏世界与游戏对象

运行时对象模型通常提供(卷 II 17.1):

  • 动态生成与销毁(血包被捡走、爆炸出现又消散、增援从拐角冒出);有的引擎干脆禁止动态创建;
  • 与底层引擎系统的连接:网格、粒子、声音、动画、碰撞、物理,确保每个对象能访问它依赖的服务;
  • 对象行为的实时模拟:引擎本质上是基于代理模型的实时动态模拟,更新顺序受对象间依赖、对子系统的依赖和子系统之间的依赖约束;
  • 定义新对象类型:理想是完全数据驱动,但很多引擎仍需程序员;
  • 唯一对象 id:可读字符串方便但运行时有开销,整数高效但难用;GEA 认为哈希字符串 id 最好,见 字符串与唯一标识符;
  • 对象查询与对象引用(指针、句柄、引用计数智能指针),见 游戏对象引用与查询;
  • 有限状态机支持;
  • 网络复制:对象状态通常归一台机器所有并复制到其他机器;
  • 存档与持久化:需要运行时类型识别 (RTTI)、反射和抽象构造(不硬编码类名就能创建实例,反序列化时非常有用);语言不原生支持就手工添加。

两种基本架构

(卷 II 17.2)

  • 以对象为中心 (object-centric):每个工具侧对象在运行时是一个类实例或一小组相互连接的实例,属性和行为封装在类里,世界就是对象的集合。
  • 以属性为中心 (property-centric):每个工具侧对象只是一个唯一 id(整数、哈希字符串 id 或字符串);属性分布在许多数据表中,每种属性一张表、以对象 id 为键;属性本身通常是硬编码类的实例;对象的行为由它拥有的属性集合隐式决定,例如有 Health 属性就能受伤和死亡,有 MeshInstance 属性就能被渲染。

Hydro Thunder:用 C 实现的简单对象模型

街机游戏 Hydro Thunder(Midway 圣地亚哥)完全用 C 编写,对象类型很少:船、蓝色红色加速图标、环境动画对象(赛道边的动物等)、水面、坡道、瀑布、粒子、赛道扇区(相连的二维多边形区域)、静态几何、二维 HUD。World_t 结构管理一条赛道的内容,持有各类对象数组;静态几何是一个网格实例,水面、瀑布、粒子各有自定义结构;船和图标等动态对象都是通用结构 WorldOb_t 的实例(卷 II 17.2.1.1)。

WorldOb_t 含位置朝向、渲染网格、一组碰撞球、简单动画状态(只支持刚性层次动画)、速度质量浮力等通用数据,再加三个指针:void* 用户数据、自定义 update 函数指针、自定义 draw 函数指针。用户数据指针实现了"继承"(如 Banshee 和 Rad Hazard 两种船的加速器状态不同),两个函数指针相当于虚函数,实现了多态的行为和外观。

struct WorldOb_s {
    Orient_t m_transform;   /* 位置/旋转 */
    Mesh3d*  m_pMesh;       /* 3D 网格 */
    /* ... */
    void*    m_pUserData;   /* 自定义状态 */
    void   (*m_pUpdate)();  /* 多态 update */
    void   (*m_pDraw)();    /* 多态 draw */
};

单体类层次及其问题

按分类学组织对象类型很自然,所以多数商业引擎采用类层次:根类 GameObject 提供 RTTI、序列化等,往下是 MovableObject(有位置朝向)、RenderableObject(能渲染),叶子是具体类型(GEA 以吃豆人为例)。层次开始时小而直观,但会同时变深变宽,几乎所有类都继承自同一个基类,GEA 称为单体类层次 (monolithic class hierarchy),并以 Unreal 引擎的对象类层次为典型(卷 II 17.2.1.2)。问题(卷 II 17.2.1.3):

  • 难以理解、维护和修改:要理解一个类必须理解它所有的父类;改派生类里一个看似无害的虚函数可能破坏某个基类的假设,产生隐蔽 bug。
  • 无法描述多维分类:层次每一级只能按一个"轴"分类(生物分类学按基因分 8 级,却无法同时按颜色分类)。

    注:GEA 说生物分类学"也叫 alpha taxonomy"。alpha 分类学 (alpha taxonomy) 指发现、描述和命名物种(尤其是种一级)的工作,不是整个分级体系的别名(Wikipedia "Taxonomy (biology)" 中 "Alpha and beta taxonomy" 一节)。真实游戏的类层次常把多种分类标准揉进一棵树,或为未预料的新类型做让步。例:载具层次分陆地(汽车、卡车、摩托)和水上(游艇、气垫船、快艇),策划要一辆两栖车时就放不进去。

  • 多重继承与致命菱形:用 C++ 多重继承解决两栖车,会导致对象含有基类成员的多份副本("致命菱形")。

    注:多份副本只出现在普通(非虚)继承下。C++ 用虚继承 (class LandVehicle : virtual public Vehicle) 可以让菱形顶端的基类只有一份子对象,代价是额外的间接访问和更复杂的构造规则(cppreference "Derived classes" 中 "Virtual base classes" 一节)。GEA 这里没有提到虚继承。构建可用、易懂、可维护的多重继承层次通常得不偿失,所以多数工作室禁止或严格限制多重继承。

  • 混入类 (mix-in):有的团队允许受限的多重继承:一个类只能从主层次继承一个父类,但可以继承任意多个没有基类的独立混入类,例如给任何类加上生命值和死亡能力的 MHealth、让角色能携带的 MCarryable。但 GEA 认为通常组合或聚合这些类比继承它们更好。
  • 冒泡效应 (bubble-up effect):多个无关类想共享代码时,功能会"冒泡"到层次上方。例如最初只有木箱会漂浮,策划想让角色、纸片、载具也漂浮;不许多重继承,程序员就把浮力代码移到共同基类,再加个 m_bCanFloat 区分,最终浮力(以及几乎所有其他功能)都到了根类。GEA 把 Unreal 的 Actor 类作为冒泡效应的典型:它包含渲染、动画、物理、世界交互、音效、网络复制、对象创建销毁、Actor 迭代和消息广播的数据与代码。

与 UE5 文档的对照:本库依据 UE 5.8 文档的 Gameplay 框架 把 Actor 描述为"组件的容器",移动、渲染、位置、视角等由 Actor 组件负责,Actor 自身负责变换、生成销毁和网络复制。GEA 的描述强调 Actor 承担了大量子系统职责;UE5 官方的说法则把多数子系统功能放在组件上。两种说法并列记录,GEA 没有说明它描述的是哪个版本的 Actor。

用组合简化层次

单体层次最常见的成因是滥用"是一个 (is-a)"关系:GUI 里让 Window 继承 Rectangle,但窗口不是矩形,而是有一个 (has-a) 定义边界的矩形,应该把 Rectangle 嵌入或用指针引用(卷 II 17.2.1.4)。

  • 组合 (composition):A 直接包含 B 或持有 B 的指针/引用,并且拥有 B:创建 A 时自动创建 B,销毁 A 时销毁 B。
  • 聚合 (aggregation):用指针或引用连接,但不管理对方的生命周期。

GEA 的例子:纯继承层次 GameObject → MovableObject → RenderableObject → CollidableObject → AnimatingObject → PhysicalObject 限制了设计选择(要物理就必须继承骨骼动画;要碰撞就必须可渲染),也难以扩展(加入 SkeletalObject 和 MorphTargetObject 后,要让两者都有物理就得复制 PhysicalObject 或使用多重继承)。解决办法是把功能拆成独立的、各提供单一明确服务的类,称为组件 (component) 或服务对象:每种对象只选需要的功能;各功能可独立维护、扩展、重构;组件解耦后更易理解和测试;很多组件直接对应一个引擎子系统,让子系统保持封装。

下图对应 GEA 图 17.7 到图 17.8 的重构:左边的继承链被右边以 GameObject 为"枢纽"、持有可选组件指针的结构取代。

flowchart LR
  subgraph "重构前 纯继承"
    G0[GameObject] --> MO[MovableObject] --> RO[RenderableObject] --> CO[CollidableObject] --> AO[AnimatingObject] --> PO[PhysicalObject]
  end
  subgraph "重构后 组合"
    GO["GameObject 枢纽"] --> T[Transform]
    GO --> MI[MeshInstance]
    GO --> AC[AnimationController]
    GO --> RB["RigidBody 碰撞与物理"]
  end

组件的创建与所有权:枢纽类通常拥有组件。最简单的做法是根类为所有可能的组件各持一个指针,构造时置空,派生类构造函数创建自己需要的组件,基类析构统一删除;这样从 GameObject 派生的类层次仍是主要分类,组件是可选附加功能。GEA 的示例代码(节选):

class GameObject {
protected:
    Transform            m_transform;
    MeshInstance*        m_pMeshInst = nullptr;
    AnimationController* m_pAnimController = nullptr;
    RigidBody*           m_pRigidBody = nullptr;
public:
    ~GameObject() { delete m_pMeshInst; delete m_pAnimController; delete m_pRigidBody; }
};

class Vehicle : public GameObject {
protected:
    Chassis* m_pChassis; Engine* m_pEngine;
public:
    Vehicle() {
        m_pMeshInst       = new MeshInstance;
        m_pRigidBody      = new RigidBody;
        m_pAnimController = new AnimationController(*m_pMeshInst); // 需要网格以提供矩阵调色板
        m_pChassis = new Chassis(*this, *m_pAnimController);
        m_pEngine  = new Engine(*this);
    }
    ~Vehicle() { delete m_pChassis; delete m_pEngine; }
};

原书问题:示例里 ~GameObject() 不是虚函数,而 Vehicle 定义了自己的析构函数来释放 m_pChassis、m_pEngine。游戏对象通常经 GameObject* 管理和删除,delete pGameObject 时若基类析构函数非虚,行为未定义,实际上 ~Vehicle() 不会被调用,车辆专属组件会泄漏。基类析构函数应声明为 virtual ~GameObject()(C++ 标准 [expr.delete]/3;cppreference "Destructors" 中 "Virtual destructors" 一节)。

通用组件:根类持有一个组件链表,组件派生自共同基类,可以遍历做多态操作(询问类型、逐个传递事件)。根类不必知道有哪些组件类型,新增组件通常不用改 GameObject,一个对象还能有同类组件的任意多个实例(硬编码指针只能有固定数量)。代价是游戏对象代码必须完全通用,组件也不能假设同一对象里有哪些其他组件。两种做法没有明显优劣,团队各有选择(卷 II 17.2.1.5)。GPP 的组件模式见 组件模式。

纯组件模型:把所有功能都移出根类,GameObject 只剩唯一 id 和一堆组件指针,那就干脆去掉它:每个组件保存对象 id,按 id 逻辑分组,只要能按 id 快速找到组件即可(卷 II 17.2.1.6)。问题:

  • 仍要定义具体对象类型并在实例化时创建正确的组件:可以用工厂模式(每种类型一个工厂类,虚构造函数创建相应组件),或用数据驱动(文本文件定义类型);
  • 组件间通信:没有了枢纽,可以按 id 查找其他组件,但最好用更高效的方式,例如预先把同一对象的组件连成环形链表;
  • 对象间消息:无法再向 GameObject 发消息,要么事先知道发给哪个组件,要么多播给对象的所有组件,都不理想。

GEA 的建议:纯组件模型在实际项目中可行,但也不是明显更好;除非在做研发,否则选自己最熟悉、最有信心、最适合当前游戏的架构。

以属性为中心的架构

面向对象程序员习惯"对象 → 属性"的视图;以属性为中心则反过来"属性 → 对象":为每种属性建一张表,存所有拥有该属性的对象的值,以 id 为键(卷 II 17.2.2):

以对象为中心以属性为中心
Object1:Position、OrientationPosition:Object1、Object2
Object2:Position、HealthOrientation:Object1、Object3
Object3:OrientationHealth:Object2

它更像关系数据库:每种属性像一张表,对象 id 是主键。《杀出重围 2》和《神偷》系列成功使用过这种模型。行为通常放在两个地方之一或两者:

  • 属性类:每种属性实现为一个类,可以简单到一个布尔值或浮点数,也可以复杂到可渲染网格或 AI "大脑";对象的行为是其所有属性行为的聚合。属性之间可以通信合作:Health 检测到攻击时通知 AnimatedSkeleton 播放受击动画,快死时通知 RigidBodyDynamics 激活爆炸或布娃娃(卷 II 17.2.2.1)。
  • 脚本:属性值作为原始数据存在类似数据库的表里,用脚本实现行为。例如特殊属性 ScriptId 指定管理该对象行为的脚本函数或脚本对象,脚本也可以响应世界中的事件。有些引擎由程序员提供一组核心硬编码属性类,同时允许完全用脚本定义新属性类型,《地牢围攻》就这样做(卷 II 17.2.2.2)。

属性与组件:GEA 引用的不少作者把它说的"属性对象"叫"组件"。两者都是一个逻辑对象由多个子对象组成;区别在于属性表示对象自身的某种特性(生命、外观、背包、某种魔法),而以对象为中心的组件往往表示到某个底层子系统的连接(渲染、动画、碰撞与动力学)。这个区别细微到很多时候无关紧要,叫纯组件模型还是以属性为中心都可以,结果本质相同(卷 II 17.2.2.3)。

优点(卷 II 17.2.2.4):

  • 更省内存:只存实际用到的属性,不存在未使用的数据成员;
  • 更易数据驱动:策划不重新编译就能定义新属性,因为没有游戏对象类定义要改;只有需要全新属性类型(且无法用脚本添加)时才需要程序员;
  • 可能更缓存友好:同类型数据在内存中连续存放,访问一个元素时相邻元素也进了同一缓存行。GEA 指出在 PS3 上一次缓存未命中相当于执行数千条指令。这种布局叫数组结构 (struct of arrays, SoA),与传统的结构数组 (array of structs, AoS) 相对(见 数据局部性、C++ 内存布局与对齐):
static const U32 MAX_GAME_OBJECTS = 20480;
// 传统 AoS
struct GameObject { U32 m_uniqueId; Vector m_pos; Quaternion m_rot; float m_health; };
GameObject g_aAllGameObjects[MAX_GAME_OBJECTS];
// 缓存更友好的 SoA
struct AllGameObjects {
    U32        m_aUniqueId[MAX_GAME_OBJECTS];
    Vector     m_aPos[MAX_GAME_OBJECTS];
    Quaternion m_aRot[MAX_GAME_OBJECTS];
    float      m_aHealth[MAX_GAME_OBJECTS];
};

(GEA 说明实际不会这样实现对象模型,代码只示意以属性为中心会产生许多同类型连续数组。)

缺点:对象只是一袋属性时,难以强制属性之间的关系;只靠拼凑细粒度属性行为难以实现大尺度行为;难以调试,程序员不能把一个对象拖进调试器监视窗口一次看到它的全部属性。延伸阅读包括 Fermier、Bilas、Duran、Chatelaine(GDC 2002/2003)和 Doug Church 的演讲(卷 II 17.2.2.5)。

与 UE5 的对应

  • Actor + Actor 组件是"以对象为中心 + 组件"的形态,见 Gameplay 框架;
  • MassEntity 数据导向框架 中实体只是 ID + 片段、相同组成的实体按原型连续存放,属于 GEA 说的纯组件/以属性为中心一端,也采用了 SoA 式的数据布局(这是对比归纳);
  • UObject 反射系统为 C++ 补上了 GEA 列出的 RTTI、反射和抽象构造,见 UObject 与反射系统。
  1. Game_Engine_Architecture_4th_Edtion_Volume_2_-_Jason_Gregory.pdf
  2. Jason Gregory《Game Engine Architecture》第 4 版(2026)卷 II 第 17 章 17.1–17.2 节

相关页面

游戏开发游戏世界与游戏对象Game World & Game Objects游戏由玩法 (gameplay) 而不是技术定义;游戏机制 (game mechanics) 是支配游戏中各实体交互的规则,以及目标、成败标准、玩家能力、非玩家实体和整体流程(GEA 引用的论文称实现玩法的软件系统为游戏的 G 因子)。大多数游戏世界由静态元素和动态元素组成,划分为若干世界块,并由高层游戏流程组织玩家目标;动态元素通常实现为游戏对象 (GO),引擎提供的建模设施称为游戏对象模型,且分为工具侧和运行时两套(GEA4 卷 II 16、16.1–16.2)。游戏开发组件模式Component Pattern组件模式 (Component Pattern) 让一个实体横跨多个领域(输入、物理、图形、AI……),同时不让这些领域彼此耦合:每个领域的代码放进自己的组件类,实体本身退化为组件的容器。它是现代引擎实体模型的基础,代价是对象变成需要正确装配的一组对象,组件之间如何通信也成了要做的设计决策。游戏开发 · UE5Gameplay 框架Gameplay FrameworkGameplay 框架 (Gameplay Framework) 是 UE 提供的一组相互配合的类,用来搭建游戏的基本结构:Actor 与组件 (Component) 构成场景中的一切;GameMode 在服务器上定规则并生成玩家;玩家由“控制器 (Controller) + 棋子 (Pawn)”组成;GameState 与 PlayerState 负责把全局与每个玩家的状态复制给所有客户端;GameInstance 跨关卡存活。游戏开发 · UE5MassEntity 数据导向框架MassEntity FrameworkMassEntity 是 UE5 面向玩法的数据导向 (Data-Oriented) 计算框架,属于实体-组件-系统 (Entity Component System, ECS) 一类设计:实体 (Entity) 只是一个 ID 加上一组片段 (Fragment) 数据,相同组成的实体归入同一原型 (Archetype) 并按内存块 (Chunk) 连续存放,无状态的处理器 (Processor) 通过实体查询 (EntityQuery) 批量处理片段。它适合成千上万个同质对象(人群、交通),MassGameplay 插件在其上提供表现、生成、LOD、复制、StateTree 等功能。游戏开发数据局部性Data Locality数据局部性 (Data Locality) 是一种优化思路:CPU 读取内存时会把相邻的一整条缓存行一起载入,所以把数据按处理顺序连续存放,就能大幅减少缓存未命中。具体手段包括:组件按类型存进连续数组,不再到处跟随指针;把活跃对象排在前面;把冷热数据拆开。代价是要放弃一部分继承、接口与指针带来的抽象。游戏开发 · UE5UObject 与反射系统UObject & Reflection System虚幻引擎 (Unreal Engine) 的对象基类是 UObject。在 C++ 类、属性、函数上加 UCLASS/UPROPERTY/UFUNCTION 等宏,再由构建前的预处理工具虚幻头文件工具 (Unreal Header Tool, UHT) 扫描头文件生成代码,引擎就能在运行时“看见”这些类型——这就是反射系统 (Reflection System)。垃圾回收、序列化、编辑器面板、蓝图、网络复制都建立在它之上。游戏开发C++ 内存布局与对齐C++ Memory Layout & Alignment理解 C/C++ 程序在内存中的样子,是写高性能引擎代码的前提。主要内容包括:声明与定义、内部与外部链接;可执行映像的几个段(text、data、BSS、rodata)以及栈和堆;结构体成员的自然对齐和填充;继承和虚函数如何改变类的布局(vpointer 和 vtable)。GEA 也简要回顾了面向对象:多重继承容易产生致命菱形,所以通常只允许 mix-in;组合往往比继承更好。游戏开发世界块数据格式与生成器World Chunk Data Formats & Spawners世界块中的动态部分要为每个游戏对象记录两样东西:属性初始值和对象类型。GEA 比较了三种存储方式:二进制对象映像(生成最简单,但要处理指针、虚表和字节序,且对变化很脆弱)、序列化描述(XML/JSON/私有二进制格式,配合反射与类工厂),以及生成器 (spawner) + 类型模式 (type schema):生成器是只含类型 id 和键值对的轻量数据块,彻底切断世界编辑器与运行时代码的耦合;模式里的默认值配合"与默认值相同就省略键"可以让默认值的修改自动传播到旧实例(GEA4 卷 II 17.3)。游戏开发游戏对象引用与查询Game Object References & Queries游戏对象需要唯一 id 来区分、查找和充当通信目标;找到对象后还要能引用它,引用的寿命可以从一次函数调用到许多分钟。GEA 比较三种实现:裸指针最快最简单,但有孤儿对象、悬垂指针、非法指针三类问题,且不能配合内存重定位;智能指针可以检测对象已删除、做引用计数,但很难写对,GEA 个人建议远离它,非用不可就用 C++11 标准库或 Boost 的成熟实现;句柄是全局句柄表的整数索引,加上唯一 id 防止槽位复用后指向错误对象,简单而安全。对象查询则不追求通用数据库,而是为最常用的查询建专门的数据结构(GEA4 卷 II 17.5)。游戏开发游戏对象的实时更新Updating Game Objects in Real Time游戏对象更新就是根据上一时刻状态 Si(t − Δt) 求出当前状态 Si(t)。"每帧对每个对象调一次虚函数 Update(),并在里面顺带更新它的动画、物理、渲染组件"的简单做法在商业引擎里行不通:底层子系统需要批量更新才能高效,而对象之间、子系统之间又有依赖顺序。GEA 介绍的对策依次是:子系统由游戏循环直接批量更新、对象只操纵子系统中的"代理状态";在帧内多个阶段挂回调;按依赖森林把对象分桶,逐桶运行完整的分阶段更新(Naughty Dog 的做法);并用状态缓存和时间戳对付更新过程中的状态不一致和一帧延迟(GEA4 卷 II 17.6)。游戏开发事件与消息传递Events & Message Passing游戏天然是事件驱动的:爆炸、玩家被敌人发现、血包被捡起都是事件,系统要通知感兴趣的对象并让它们以各自的方式处理。用虚函数 OnExplosion() 通知属于静态类型的后期绑定,迫使基类为所有可能的事件声明虚函数;GEA 主张把事件封装成对象(类型 + 参数),实现动态类型的后期绑定,从而获得单一处理函数、持久性和盲转发。GEA 依次讨论事件类型(枚举 vs 字符串/字符串 id)、参数(派生类、Variant 数组、键值对)、处理器、职责链、兴趣注册、排队与否的利弊,最后是数据驱动的事件系统和以数据通路连接端口的图形化系统(如 UE 蓝图)(GEA4 卷 II 17.8)。游戏开发游戏脚本系统Game Scripting脚本语言是主要用来让用户控制和定制软件行为的编程语言;在游戏引擎中,它是高层、相对易用、能方便访问引擎大多数常用功能的语言,程序员和非程序员都能用它开发新游戏或给现有游戏做 mod。GEA 先区分数据定义语言和运行时脚本语言,梳理语言分类与游戏脚本的典型特征(解释执行、轻量、快速迭代、方便),介绍 QuakeC、UnrealScript、Lua、Python、Pawn,列出从"脚本回调"到"脚本驱动整个游戏"的六种架构,再以 Naughty Dog 的 DCScript 为例讲虚拟机与原生函数接口,最后是对象引用、事件收发、面向对象脚本、脚本状态机和协作式多线程脚本(GEA4 卷 II 17.游戏开发游戏世界的加载与流送Loading & Streaming Game Worlds世界加载系统负责两件事:把世界块和所需资源从磁盘读进内存的文件 I/O,以及这些资源的内存分配与释放;另外还要管理游戏对象的生成与销毁。GEA 按演进顺序介绍:一次只载一个关卡的栈式加载(LSR 资源压栈底),用小块气闸 (air lock) 区域实现基本无缝的过渡,再到把所有资产切成等大块、用块池分配器按关卡加载区域流送的细粒度方案(Naughty Dog);对象生成的内存问题有"不许运行时分配"、按尺寸分池、小内存分配器和内存重定位四种对策;存档则分检查点和随处存档(GEA4 卷 II 17.4)。综合引擎运行时架构总览Engine Runtime Architecture Overview本页把 wiki 中分散在 GEA 第 1、6、8、17 章及 GPP、UE5 相关页面里的内容串成一条线,回答"一个游戏引擎在运行时是怎样组织起来的":静态结构上是自下而上的分层,上层依赖下层;启动关闭按依赖顺序显式进行;每一帧由主循环驱动各子系统批量更新,游戏对象的逻辑被安排在子系统阶段之间,并按依赖分桶或设先后关系;多核上一帧被拆成成百上千个作业;玩法基础系统(对象模型、事件、脚本、世界加载流送、高层流程)位于引擎与游戏的分界线附近。各结论的细节与出处见所链接的概念页。综合游戏中的内存管理Memory Management in Games本页把 wiki 中关于内存的内容串成一条线:内存从两方面影响游戏,一是访问模式(缓存未命中远比计算昂贵),二是分配本身(通用堆分配慢且产生碎片,而主机上没有自动换页的虚拟内存兜底)。对策按生命周期组织:常驻与关卡数据用栈、每帧临时数据用单帧/双缓冲分配器、同尺寸小对象用池或小内存分配器、大小不一的动态对象用堆加碎片整理与句柄;资源按引用计数和分块加载管理,流送系统把世界切成等大的块;UE 则在 UObject 层用垃圾回收管理对象生命周期。最后,没有准确的追踪工具,以上一切都无从验证。各结论的细节与出处见所链接的概念页。游戏开发字符串与唯一标识符Strings & Unique Identifiers字符串在 C/C++ 中只是字符数组,变长意味着要么硬编码长度上限、要么动态分配;比较和复制都是 O(n)。引擎内部大量用字符串做资源名和对象 ID,处理不当会严重拖慢游戏。GEA 的建议:运行时代码尽量避免字符串类,若用则了解其成本并按引用传递;路径类是值得专门做的例外;对象与资产的唯一标识用哈希字符串 ID(string id),兼得字符串的可读性与整数的比较速度。

被这些页面引用1 篇

输入关键词开始搜索

滚轮或双指缩放 · 拖动平移 · 双击放大 / 还原