1. 设计主线:共享描述与运行实例分离#
世界层同时处理三类信息:描述一种模型的 archetype / model info,表示某个摆放实例的 entity,以及描述一片地图内容的 map data。前者保存可共享的模型属性,实体持有当前位置和运行状态,地图内容组织加载与卸载。这使一份模型资源可以服务大量实例,也允许地图区域独立进出内存。
游戏类建立在 RAGE Framework 的类型上:CBaseModelInfo : fwArchetype、CEntity : fwEntity、CMapData : fwMapData。因此游戏目录主要补充 GTA 的语义,通用空间组织、资源机制和实体框架应继续向 rage/framework 追踪。
2. 实体继承与扩展#
| 层次 | 核心语义 | 阅读要点 |
|---|---|---|
CEntity | 游戏世界实体、可见性、模型和扩展关联 | 并非每个实体都需要完整动态物理 |
CDynamicEntity | 动态状态、population 类型、动画等集成 | NPC、任务对象、回放对象的来源不同 |
CPhysical | 游戏对象与物理、附件、破坏状态的结合 | 物理实例与游戏对象生命周期相互影响 |
CObject | 通用可交互物件 | 门、道具、脚本物件、对象 intelligence |
| 模型信息子类 | Ped、Vehicle、Weapon、MLO、Time 等模型差异 | 不应把所有模型都按同一种资源结构处理 |
实体通过 fwExtension 附加功能。CIsVisibleExtension 用 bit flags 记录不同模块的可见性影响,枚举包含脚本、摄像机、过场、网络、前端、回放等来源;CPhysical 还提供破碎/隐藏组件标志扩展。这种按需附加方式减少把低频功能全部塞进基类的压力,但也增加了“扩展是否存在”“由谁创建和释放”的检查要求。
CEntityFlags 有明确的位宽和 CompileTimeAssert,包含火焰、灯光、预渲染、水面影响、第一人称等细粒度状态。这是内存紧凑和跨模块直接协作的折中:热点判断便宜,状态的合法组合与写入来源则需要仔细追踪。
3. 世界处理按依赖阶段拆分#
CGameWorld 不只有一个 Process():还有全局事件、车辆事件、移动完成后、摄像机更新后、PreRender 后等入口。这里反映的是数据依赖:事件可能改变行为,行为改变移动,移动后的附件和观察信息又服务于后续摄像机或呈现。
将某段逻辑放到“每帧调用一次”的位置还不够。例如附件依赖最终变换时,过早更新会读到旧姿态;相机附近的对象决策则需要明确使用前一帧还是当前帧摄像机。世界层提供阶段化接口来避免每个模块自行猜测时机。
CGameScan 负责连接世界扫描与呈现准备:SetupScan 构造参数,PerformScan 配合 gPostScan.ProcessPvsWhileScanning,KickVisibilityJobs 提前发起可见性工作。它还构造 HD、LOD、室内、速度方向和体积相关 streaming cull shape。因此视锥、可见性和资源需求有关联,却不是同一套集合:可见对象需要渲染,未来可能接近的对象也可能需要预加载。
4. 地图装载不是只创建建筑#
CMapDataContents 的接口包括:
| 内容 | 装载/卸载职责 |
|---|---|
| Entities | PreAdd、PostAdd、PostRemove |
| Interior proxies | 构造室内代理 |
| Car generators | 创建和销毁车辆生成点 |
| TimeCycle modifiers | 创建和销毁局部环境修饰 |
| Occluders | 创建和销毁遮挡信息 |
| LOD lights | 管理普通和远距离 LOD 灯光 |
CMapData 直接持有 TimeCycle modifiers、car generators、LOD lights 的数据。这说明地图分区是多个系统的生命周期容器,卸载区域必须同时撤销人口、灯光和环境副作用。地图“已经卸载”却仍残留灯光或生成器时,应检查对应 Destroy 路径,而不是只查实体删除。
室内与室外并不只有一个布尔差异。源码包含 MLO 模型信息、interior proxies、房间状态与独立的室内 streaming cull shape。相关能力分别位于模型描述、地图内容和扫描阶段,维护时需要组合阅读。
5. Dummy 与真实物件转换#
CObjectPopulation 实现 ConvertToRealObject 和 ConvertToDummyObject。这是比单纯几何 LOD 更深一层的资源控制:物件可以在轻量表示与完整游戏对象之间切换,避免所有背景道具长期承担动态状态、物理与脚本开销。
转换并非任意可行。源码对脚本所有权、script brain、bNeverDummy 等情况进行区分;ConvertToDummyObject 内有关于脚本对象的断言。由此可知,视觉上可简化的物件,未必能在玩法语义上被替换。角色握持、任务引用、门状态或网络身份都可能使对象需要保留完整形态;具体限制需检查调用路径,不能仅按距离删除。
population 类型也参与语义区分。CDynamicEntity 的 ePopType 包含随机常驻、停放、巡逻、scenario、ambient、永久、任务、回放和缓存等值;注释说明其数量还影响网络编码位数。改枚举并不是局部代码整理,可能触及序列化与网络兼容性。
6. 模型信息与实例数据的维护边界#
车辆模型目录按座位、颜色、灯光、警笛、车牌、变体和改装拆分;Ped 和 Weapon 也有独立数据描述,多个定义使用 .psc。这些文件承担结构声明与数据驱动接口,不能仅修改某个生成后的字段而忽略源定义和资源内容。
修改共享 model info 会影响所有采用该模型的实例;修改实体只影响相应实例。若要实现“这一辆车改变外观”或“这个物件暂时隐藏”,必须先判断正确的状态归属。错误地改共享描述可能造成全世界同模型同步变化,错误地复制资源则会放大内存成本。
7. 性能与故障分析#
主要成本包括实体数量、动态/物理对象数量、扫描与 PreRender、扩展及引用维护、Dummy 转换抖动。场景异常也常来自不同状态机的交叉:资源已经加载但实体未加入世界;实体存在却未通过某模块的可见性条件;模型可见但物理或交互表示尚未就绪。
排查时建议分别记录模型状态、实体身份与所有权、地图归属、可见性来源、物理激活及转换状态。本文未以运行数据确认任何热点,以上为从源码边界导出的检查路径。
8. 源码依据#
| 源码 | 关键位置 |
|---|---|
| Entity.h | CEntity、flags、可见性扩展 |
| DynamicEntity.h | 47 行附近 ePopType;103 行继承 |
| Physical.h | 破坏/隐藏扩展与 CPhysical |
| BaseModelInfo.h | 283 行附近 fwArchetype 派生 |
| MapData.h | Contents 接口、区域附属内容 |
| GameWorld.cpp | Process 和分阶段处理函数 |
| GameScan.cpp | 扫描、PVS、提前可见性任务与 cull shapes |
| objectpopulation.cpp | 1307、1427 行附近,两种对象转换 |