CD Code Digest Hub /GTA V · v2

游戏物理层、WorldProbe 与 Ragdoll / NaturalMotion

75 分钟阅读 13 个章节 2 张图

本页解释 GTA V 游戏代码(game/physics)如何驱动 RAGE 物理引擎:CPhysics 何时初始化、每帧在 CGame 更新序列中的位置与子步(time slice)调度、碰撞层(ArchetypeFlags)、游戏 phInst 子类与碰撞事件回调、材质 ID 打包、WorldProbe 形状测试(含异步队列)、浮力;以及 ragdoll 的完整链路:NaturalMotion(Euphoria)库在源码中的结构、fragNMAssetManager/fragInstNM 胶水、NM agent 池与游戏 ragdoll 池、ragdoll 约束、动画 ↔ 物理切换规则。

背景与相关页面#

相关源码文件#

文件作用
game/physics/physics.h/.cppCPhysics:初始化、每帧更新、碰撞事件分发、ragdoll 共享数据、调试
game/physics/gtaArchetype.h/.cppArchetypeFlags 碰撞层;gtaFragType
game/physics/gtaInst.h/.cppphInstGta、fragInstGta、fragInstNMGta、ARTFeedbackInterfaceGta、PH_INST_* 游戏类型号
game/physics/gtaMaterial*.h/.cpp、gtaMaterialManager.pscphMaterialGta、phMaterialMgrGta(材质 ID 打包、材质数据加载)
game/physics/CollisionRecords.h/.cppCCollisionHistory/CCollisionRecord 每帧碰撞记录
game/physics/WorldProbe/*WorldProbe::CShapeTestManager、描述符、结果、任务
game/physics/Floater.h/.cpp、WaterTestHelper.*CBuoyancy 浮力
game/physics/RagdollConstraints.*CRagdollConstraintComponent(手铐/绑脚/挂钩)
game/physics/Breakable.*、Deformable.*、Tunable.*、PhysicsHelpers.*、Rope.*frag 回调、可变形骨骼、可调物体参数、碰撞辅助、绳索调试 bank
rage/naturalmotion/src/fragmentnm/*fragNMAssetManager、fragInstNM、NMBehaviorPool
rage/naturalmotion/src/rockstar/*ART::NmRsEngine、NmRsCharacter、NmRsCBU_* 行为实现
game/task/Physics/TaskNM.h/.cpp、Metadata.pscragdoll 池与 NM 调优(任务本身见 07)

1. CPhysics 的生命周期#

CPhysics::Init(initMode) 按模式分派 src/dev_ng/game/physics/physics.cpp:1248-1293:

模式调用者动作
INIT_CORECScene::Init 直接调用(依赖 InitPools,CPed::InitSystem 依赖它)src/dev_ng/game/scene/scene.cpp:651-659InitCore();NAVMESH_EXPORT 时初始化请求列表
INIT_BEFORE_MAP_LOADEDgameSkeleton 注册 src/dev_ng/game/Core/game.cpp:2118按默认值为破碎玻璃分配内存(bgGlassManager::Malloc)
INIT_SESSIONgameSkeleton 注册 game.cpp:2334重力恢复 GRAV_EARTH、缓存 XMAS 天气索引

Shutdown 对称:SHUTDOWN_CORE → ShutdownCore;SHUTDOWN_WITH_MAP_LOADED → 关闭 g_StaticBoundsStore、释放玻璃;SHUTDOWN_SESSION → CExplosionManager::Shutdown physics.cpp:1298-1317。注册所在的 init 段由 SET_INIT_TYPE 宏切换(INIT_BEFORE_MAP_LOADED 从 2099 行、INIT_SESSION 从 2281 行开始)game.cpp:2099、game.cpp:2281。

1.1 CPhysics::InitCore:容量与配置#

InitCore 依次创建物理关卡、模拟器、材质管理器、布料/绳索/frag 管理器、玻璃系统,并配置 worker src/dev_ng/game/physics/physics.cpp:675-1165。容量全部由池大小推导:

量公式 / 值证据
MAX_OCTREE_NODES1500src/dev_ng/game/physics/physics.h:72-81
最大激活数 iMaxActiveObjectsmaxPeds*2 + maxVehicles*2 + MAX_ACTIVE_COBJECTS(64) + MAX_FRAGMENT_CACHE_OBJECTS(64)physics.cpp:712-724
约束上限562(导航网格导出时 1024)physics.cpp:715、physics.cpp:729
inst 池phInstGta 2234、fragInstGta 1600、fragInstNMGta = MAXNOOFPEDS(导出模式 35000 / ×8)physics.cpp:738-781
frag 缓存条目maxPeds + maxVehicles + 128(PC)/ +64(其它)physics.cpp:783-787
托管碰撞体100 + 64 + 64physics.cpp:789
manifold / 复合 / contactiMaxActiveObjects*2;其中 1/2 为复合;非复合 × 4 个接触physics.cpp:790-793
关卡最大对象各 inst 池 + 缓存条目 + 玻璃碎片(默认 25,-maxphysicalglassshards)+ MAX_GLASS_PANES(10),再 min(…, 32766)physics.cpp:742-743、physics.cpp:795-804、physics.cpp:600
关卡范围WORLDLIMITS_*physics.cpp:816
宽相最大对数4000(覆盖 RAGE 构造函数默认的 10240)physics.cpp:820
last matrixphLevelNew::MAX_INST_LAST_MATRICESphysics.cpp:822
延迟八叉树容量iMaxActiveObjects * 4physics.cpp:840-842
scratchpad64 位 2MB、否则 1MB(-phscratchpad)physics.cpp:861-868
高优先级 manifold(maxPeds+maxVehicles)*0.5*2physics.cpp:873
外部速度 manifold / 实例行为256 / 256physics.cpp:876-877

其余关键配置:

  • 宽相必须是 btAxisSweep3,并安装 CPhysics::KeepBroadphasePair 作为 keep-pair 回调(否则 Verifyf 报错)physics.cpp:824-850。
  • 模拟器回调:所有碰撞完成后 phInstBehaviorExplosionGta::ProcessAllCollisions;破碎许可 CBreakable::AllowBreaking;重力 9.8、Z 轴向上 physics.cpp:889-895。
  • 材质:phMaterialMgrGta::Create() + MATERIALMGR.Load();开启并冻结引用计数;force solver 最大 phase 1024;延迟 SAP(注释:避免流送造成物理尖峰);最小凹体厚度 0.001;LCP 迭代 2/2(RAGE 默认 4/4)physics.cpp:902-930。
  • 静态 bound 流送:fwBoxStreamer 接口与 g_StaticBoundsStore(地图碰撞的流送见 ../05-world/game-streaming-and-scene-streamer.md);WorldProbe::GetShapeTestManager()->Init(INIT_CORE) physics.cpp:937-943。
  • fragTune 读取 common:/data/fragment.xml;BANK 下创建 phMouseInput physics.cpp:949-958。
  • 布料/绳索/frag 管理器与回调(详见 frag 页)physics.cpp:964-1043;InitSharedRagdollFragTypeData(§9.4)。
  • worker 数(仅 __OPTIMIZED 且无 -nomtphys):Xenon 4、PS3 3、Durango 4、其它 sysTaskManager::GetNumThreads();主线程参与;PS3 上 force solver 主线程不参与 physics.cpp:1133-1155。

2. 每帧更新:在 CGame 中的位置与子步调度#

2.1 调用链#

CScene::Update 在 gameSkeleton 的更新组中注册 src/dev_ng/game/Core/game.cpp:2826,它调用 CGameWorld::Process() src/dev_ng/game/scene/scene.cpp:808-811;后者在处理完实体 process control 与 ped group 后执行 CPhysics::UpdateRequests() 与 CPhysics::Update()(单机预算 7.5ms、联机 6ms 的 timebar)src/dev_ng/game/scene/world/GameWorld.cpp:2041-2062。

证据:CPhysics::Update 全流程 src/dev_ng/game/physics/physics.cpp:2190-2379;SimUpdate physics.cpp:2466-2515;ProcessAfterAllMovement 开头等待 verlet 任务并打开 main-thread-only 模式、结尾提交延迟八叉树 GameWorld.cpp:2182-2199、GameWorld.cpp:2414-2416,它注册于 ProcessAfterMovement 更新组 game.cpp:2874-2878;角色布料与 LATE 绳索在 CRenderer::PreRender 中(注释:必须在动画表达式完成之后)src/dev_ng/game/renderer/Renderer.cpp:1013-1020;之后 CGame 等待 verlet 任务并 VerletSimPostUpdate game.cpp:3489-3495。

2.2 子步数与时间步#

  • 默认 DEFAULT_NUM_PHYS_TIMESLICES = 2 physics.cpp:169,每帧开头重置(除非 -physicsupdates 或 ms_OverriderNumTimeSlices)。子步 dt = 帧 dt / n,写入 fwTimer::SetRagePhysicsUpdateTimeStep physics.cpp:2204-2225。
  • 若系统帧时间 < 1/60 秒(高帧率),改为单子步,dt 截断到 1/30 秒;ms_bIgnoreForcedSingleStepThisFrame 可跳过一次 physics.cpp:2226-2245。
  • finalUpdate(push 与休眠)默认只在最后一个子步为真:onlyPushOnLastUpdate = !ms_DisableOnlyPushOnLastUpdateForOneFrame && ms_OnlyPushOnLastUpdate && !ms_bInStuntMode physics.cpp:2473-2474。特技模式下每个子步都做 push。
  • 回调期间 PHLEVEL->SetOnlyMainThreadIsUpdatingPhysics(true/false) 成对包裹 pre-update、PreSimUpdate、PostSimUpdate、post-update,以省去关卡锁开销(注释 8/7/12)physics.cpp:2265-2274。
  • PhysicsSynchronizationPoint 只在 __PFDRAW 下工作:用信号量让渲染线程在指定时机绘制物理调试图(超时 200ms 后放弃)physics.cpp:2010-2082。

2.3 PreSimUpdate:实体的多遍物理处理#

src/dev_ng/game/physics/physics.cpp:1374-1591:

  1. 首个子步处理 GPU 写回的车辆损坏(GPU_DAMAGE_WRITE_ENABLED);可选取回车辆录像数据。
  2. 通过 fwSceneUpdate::Update(SU_PRESIM_PHYSICS_UPDATE, &preSimControl) 让每个实体执行 ProcessPhysics;回调可把实体放进"推迟"数组 aPostponedEntities(上限 50,另有第二组推迟数组)以便晚于其它实体处理。
  3. 推迟实体再 ProcessPhysics;返回 PHYSICS_NEED_SECOND_PASS 的进入第二遍 ProcessPhysics2,再返回 PHYSICS_NEED_THIRD_PASS 的进入第三遍 ProcessPhysics3(第二/三遍数组容量 = VEHICLE_POOL_SIZE,physics.h:93-119);第二组推迟实体最后处理。
  4. 网络克隆在合适的那一遍(首个子步、或"先混合再物理"的实体)调用 netBlender->Update()。
  5. FRAGNMASSETMGR->StepPhase1(dt)(NM 前半步,§9.2),PreUpdateInstanceBehaviors。
  6. 若脚本请求清 manifold(ms_bClearManifoldsEveryFrame 或剩余帧数),重置所有 pruned pair 的 manifold(断言提示一般只应在室内用)。
  7. 首子步清 CCollisionHistory 记录;CCollisionHistory::PreSimUpdate;更新绳索视口。

2.4 SimUpdate 与 PostSimUpdate#

SimUpdate:phSimulator::Update → IterateOverManifolds(碰撞事件,§4)→ FRAGMGR->SimUpdate → UpdateInstanceBehaviors → 可选车辆录像 physics.cpp:2466-2515。

PostSimUpdate src/dev_ng/game/physics/physics.cpp:2524-2903:

  1. 遍历激活对象:
    • ped 的 ragdoll inst(状态 > RAGDOLL_STATE_ANIM_DRIVEN):取最显著碰撞记录交给 CCollisionEventScanner::ProcessRagdollImpact;若 ped 尚未使用 ragdoll,记入"待切换"数组(上限 MAX_RAGDOLLS_TO_SWITCH = 32,超出断言)。RAGDOLL_STATE_PHYS_ACTIVATE 也会产生一次零冲量 impact。
    • 车辆:当"本帧碰撞冲量和 > 阈值 × 车辆质量"或车辆倒置时,对每个座位的乘员调用 ProcessVehicleImpact 判断是否被撞下(阈值:摩托/四轮摩托/水上摩托类为 0,其余默认 5;易着陆/特技飞行模式 30000;摩托撞摩托 100;摩托类玩家撞带坡道的车 30000);处理汽车"尾部弹起"冲量;CheckSimpleColliderMode 后若碰撞体退化为刚体则重算包围盒。
    • 抛射物:收集后统一 PostSimUpdate。
  2. 对待切换的 ped 调用 SwitchToRagdollPostPhysicsSimUpdate;网络克隆额外发送 NM_BALANCE_MSG(start)。
  3. 复制激活索引后遍历:最后子步做网络混合 ProcessPostPhysics,CPhysical::UpdateEntityFromPhysics、ProcessProbes、重置 no-collision、补加寻路动态障碍。
  4. FRAGNMASSETMGR->StepPhase2(dt)(NM 后半步)、CExplosionManager::Update、螺旋桨碰撞、对已"安定"的 ped 调 DeactivatePhysicsAndDealWithRagdoll。

3. 碰撞层(ArchetypeFlags)#

ArchetypeFlags 把 32 位 type/include 标志用作碰撞层;必须与 ragebuilder 的副本保持同步(注释)src/dev_ng/game/physics/gtaArchetype.h:27-72:

位名称位名称
1GTA_MAP_TYPE_WEAPON17GTA_EXPLOSION_TYPE
2GTA_MAP_TYPE_MOVER18GTA_PICKUP_TYPE
3GTA_MAP_TYPE_HORSE19GTA_FOLIAGE_TYPE
4GTA_MAP_TYPE_COVER20GTA_FORKLIFT_FORKS_TYPE
5GTA_MAP_TYPE_VEHICLE21GTA_WEAPON_TEST
6GTA_VEHICLE_NON_BVH_TYPE22GTA_CAMERA_TEST
7GTA_VEHICLE_BVH_TYPE23GTA_AI_TEST
8GTA_BOX_VEHICLE_TYPE24GTA_SCRIPT_TEST
9GTA_PED_TYPE25GTA_WHEEL_TEST
10GTA_RAGDOLL_TYPE26GTA_GLASS_TYPE
11 / 12GTA_HORSE_TYPE / GTA_HORSE_RAGDOLL_TYPE27GTA_RIVER_TYPE
13GTA_OBJECT_TYPE28GTA_SMOKE_TYPE
14GTA_ENVCLOTH_OBJECT_TYPE29GTA_UNSMASHED_TYPE
15GTA_PLANT_TYPE30GTA_STAIR_SLOPE_TYPE
16GTA_PROJECTILE_TYPE31GTA_DEEP_SURFACE_TYPE

设计要点(全部来自同一头文件的组合定义 gtaArchetype.h:74-129):

  • 地图碰撞按用途分层:地图碰撞有 mover/weapon/horse/cover/vehicle 等不同类型位;GTA_ALL_MAP_TYPES 只等于 GTA_MAP_TYPE_MOVER,所以 ped/ragdoll/object 默认只撞 mover 层,而 GTA_WEAPON_TYPES 用 GTA_MAP_TYPE_WEAPON,GTA_PROJECTILE_NEAR_INCLUDE_TYPES 用 weapon 层。
  • "查询类型"也是类型位:GTA_WEAPON_TEST/CAMERA_TEST/AI_TEST/SCRIPT_TEST/WHEEL_TEST 让形状测试作为"type"去匹配实体的 include(GTA_ALL_SHAPETEST_TYPES 被多数 include 集合包含)。
  • 例外集合如 GTA_CAR_DOOR_LATCHED_INCLUDE_TYPES(锁住的车门只接受核心查询与抛射物)、GTA_VEH_SEAT_INCLUDE_FLAGS、GTA_WHEEL_INCLUDE_TYPES。
  • 编译期断言确保 ped/ragdoll/object/vehicle 的 include 集合都不含 GTA_MAP_TYPE_WEAPON src/dev_ng/game/physics/physics.cpp:3828-3833。

宽相 keep-pair 回调 CPhysics::KeepBroadphasePair:同一 CEntity(user data 相同,如 ped 胶囊与 ragdoll)的两个 inst 永不配对;纯 GTA_MAP_TYPE_WEAPON 对象与 ped/ragdoll/horse/vehicle/object 永不配对——在 SAP 新增配对时就丢弃,比后续 MatchFlags 更早 physics.cpp:3836-3884。注释提醒此回调只在两对象开始重叠时调用,不应依赖动态条件。

4. 游戏 phInst 子类与碰撞事件#

4.1 类型与标志#

游戏类型号从 fragInst::PH_INST_GDF_LAST+1 开始:PH_INST_MAPCOL/BUILDING/VEH/PED/OBJECT/PROJECTILE/EXPLOSION/PED_LEGS,frag 系列 PH_INST_FRAG_VEH/PED/OBJECT/BUILDING/CACHE_OBJECT;注释要求与 NmRsCharacter.h 中 ART 命名空间的副本保持一致 src/dev_ng/game/physics/gtaInst.h:42-63。phInstGta 额外的 inst 标志:FLAG_USERDATA_PARENT_INST、FLAG_NO_COLLISION、FLAG_IS_DOOR(门不与门碰撞)、FLAG_BEING_DELETED、FLAG_NM_NO_PLAYER_PED_ACTIVATE(16 位已用尽)gtaInst.h:78-98。三个类都用 FW_REGISTER_CLASS_POOL 池化(池大小见 §1.1)。

类基类覆写的关键钩子证据
phInstGtaphfwInstShouldFindImpacts(FLAG_NO_COLLISION、同实体、上下车时与自己车辆、门规则…)、PreComputeImpacts、外部控制速度、激活/去激活gtaInst.cpp:153-810
fragInstGtaphfwFragInst以上 + ModifyImpulseMag(车辆 frag 冲量函数)、PartsBrokeOff、CollisionDamage、FindBreakStrength、缓存相关gtaInst.cpp:836-2337
fragInstNMGtafragInstNMragdoll 的碰撞过滤、激活检查、动画姿态/速度注入、车辆弹出、LODgtaInst.cpp:2448-4092

4.2 碰撞事件回调链#

RAGE 不提供"碰撞事件队列",游戏在每个子步模拟完成后遍历 manifold 自取:

  1. CPhysics::IterateOverManifolds 遍历 phSimulator 的重叠对数组,跳过约束 manifold;复合 manifold 遍历其子 manifold;只有接触数 > 0 时才进行昂贵的 ValidateAndRefreshRootManifold(核对 inst/entity 仍有效)physics.cpp:3323-3383。
  2. IterateManifold 先累计激活接触的平均冲量;校验复合组件索引(物体换 LOD 或破碎后可能失效);逐接触(或按 manifold,取决于 g_vfxMaterial 设置)构造 VfxCollisionInfo_s 调 ProcessCollisionVfxandSfx,然后调 ProcessCollisionData;可变形物体处理可变形骨骼 physics.cpp:2926-3140。
  3. ProcessCollisionData 先缓存接触数据(回调可能删除接触),再分别对 A、B 调 CPhysical::ProcessCollision(...),B 之前检查 A 的 inst 是否仍在关卡中(B*299855)physics.cpp:3775-3826。
  4. CPhysical::ProcessCollision 写入 CCollisionHistory(AddCollisionRecord)src/dev_ng/game/scene/Physical.cpp:1892、Physical.cpp:2005-2035。CCollisionHistory 每帧清空、跨子步合并,每个碰撞实体一条记录,记录池 COLLISIONHISTORY_RECORDSTORESIZE = 600 src/dev_ng/game/physics/CollisionRecords.h:18-26、CollisionRecords.h:62-108。AI 事件扫描、车辆损坏、ragdoll 激活(§2.4)都从这里取数据。

其它辅助:ClearManifoldsForInst 清某实例所有 manifold 的接触;WarpInstance 通过 PHSIM->TeleportObject 瞬移 physics.cpp:3886-3931;GetFoliageRadius 说明植被碰撞只能是球/胶囊(或 BVH 中的球/胶囊图元)physics.cpp:4086-4130。

5. 材质 ID 编码与材质数据#

phMaterialMgrGta 的 64 位 packed ID 布局 src/dev_ng/game/physics/gtaMaterialManager.h:86-101:

位字段访问器
0–7材质索引(最多 256 种)UnpackMtlId;GetMaterialMask() = 0xff
8–15程序化(procedural)IDUnpackProcId
16–20房间 ID(5 位)UnpackRoomId
21–23行人密度(3 位)UnpackPedDensity
24–39多边形标志(16 位)UnpackPolyFlags
40–47材质颜色索引UnpackMtlColour(HACK_GTA4_64BIT_MATERIAL_ID_COLORS)

多边形标志(必须与 ragebuilder 的 gtamaterialmgr.cpp 对齐):STAIRS、NOT_CLIMBABLE、SEE_THROUGH、SHOOT_THROUGH、NOT_COVER、WALKABLE_PATH、NO_CAM_COLLISION、SHOOT_THROUGH_FX、NO_DECAL、NO_NAVMESH、NO_RAGDOLL、VEHICLE_WHEEL(运行期复用工具专用位)、NO_PTFX、TOO_STEEP_FOR_PLAYER、NO_NETWORK_SPAWN、NO_CAM_COLLISION_ALLOW_CLIPPING src/dev_ng/game/physics/gtaMaterial.h:50-73。材质级标志:SEE_THROUGH(AI 探针穿过)、SHOOT_THROUGH、SHOOT_THROUGH_FX、NO_DECAL、POROUS、HEATS_TYRE gtaMaterial.h:36-47。GetIsSeeThrough 等查询把材质标志与多边形标志取或 gtaMaterialManager.h:168-176。

phMaterialGta 在 RAGE 的摩擦/弹性之外携带 VFX 组、扰动类型、武器反应类型、手柄震动 profile、密度、轮胎抓地/湿地修正/阻力/极速倍率、震动、柔软度、噪声、穿透阻力与标志 gtaMaterial.h:280-298。加载流程 Load src/dev_ng/game/physics/gtaMaterialManager.cpp:225-335:

  1. PreLoadGtaMtlFile("materials") 扫描 common:/data/materials/materials.dat,统计非 # 行数以预留容量(断言 < 0xffff)gtaMaterialManager.cpp:61-92。
  2. LoadGtaMtlFile 先加载 rumble profile,再逐行用 phMaterialGta::LoadGtaMtlData 解析(首行是版本号)gtaMaterialManager.cpp:99-131。
  3. 用 FindMaterialId 缓存几十个常用 ID(如 g_idSnowDeep、联机特殊表面 TEMP_01..09、特技坡道 STUNT_RAMP_SURFACE 等)。
  4. SetMaterialInformation(array, mask),最后加载 material_override_pairs.dat 覆盖对(弹性超过 0.9 会断言)gtaMaterialManager.cpp:148-210。

ragdoll 的每个 bound 部件被赋予连续的专用材质(g_idButtocks + 部件号),中/低 LOD 通过 MapRagdollLODComponentCurrentToHigh 映射回高 LOD 部件号,从而碰撞材质可直接反推身体部位 physics.cpp:1781-1821。

6. WorldProbe:游戏的形状测试接口#

WorldProbe 是游戏代码使用 RAGE 形状测试(rage-physics-engine.md §11)的唯一入口:调用方填一个描述符(CShapeTestProbeDesc/CapsuleDesc/SphereDesc/BoundDesc/BoundingBoxDesc/BatchDesc),交给单例 CShapeTestManager::SubmitTest(desc, blockingMode),结果写入调用方拥有的 CShapeTestResults。

6.1 描述符与选项#

  • 测试类型 eShapeTestType:DIRECTED_LINE_TEST、UNDIRECTED_LINE_TEST、CAPSULE_TEST、SWEPT_SPHERE_TEST、POINT_TEST、SPHERE_TEST、BOUND_TEST、BOUNDING_BOX_TEST、BATCHED_TESTS;域 TEST_IN_LEVEL(关卡剔除)或 TEST_AGAINST_INDIVIDUAL_OBJECTS(只测指定实例);阻塞模式同步/异步;结果状态 TEST_NOT_SUBMITTED/TEST_ABORTED/SUBMISSION_FAILED/WAITING_ON_RESULTS/RESULTS_READY src/dev_ng/game/physics/WorldProbe/wpcommon.h:96-185。
  • LOS 选项位:忽略 shoot-through / see-through / 无碰撞实体 / 无相机碰撞材质 / 多边形 shoot-through / not-cover、穿过玻璃、忽略 shoot-through-fx、测试车轮胎、不排序结果;ELosContext 标记调用来源(AI、相机、音频、武器、脚本、HUD、网络等)供统计与调试过滤 wpcommon.h:98-144。LOS 选项通过 ConvertLosOptionsToIgnoreMaterialFlags 转为材质忽略标志。
  • 描述符接口:include/type/exclude-type/state 标志、包含/排除实例或实体(EIEO_DONT_ADD_VEHICLE 等选项控制是否连带车辆乘员、武器、附件)、上下文、"把多面体当图元"等 src/dev_ng/game/physics/WorldProbe/shapetestdesc.h:49-180。注释说明输入用的 type/include 标志定义在 gtaArchetype.h wpcommon.h:79-81。
  • 容量:每测试最多 32 个交点、PROCESS_LOS_MAX_EXCEPTIONS 32、include/exclude 实例 128;系统中最多 120 个异步请求,同时交给任务管理器的最多 20 个 wpcommon.h:53-77。

6.2 同步与异步执行#

SubmitTest src/dev_ng/game/physics/WorldProbe/shapetestmgr.cpp:508-671:

  • 同步:在栈上构造 CShapeTestTaskData;"个体对象"域把实例列表拷进 phShapeTestCullResults(READ);测轮胎时用 256 实例的 WRITE 剔除结果;批量测试为每个子测试准备结果描述符;PerformTest() 后 ReturnResults(),返回是否有命中。
  • 异步:断言不支持"个体对象"域;在 ms_AsyncShapeTestPoolCs 下从 CShapeTestTaskData 池(容量 MAX_ASYNCHRONOUS_REQUESTED_SHAPE_TESTS)分配,池满时结果标 SUBMISSION_FAILED 并返回 false;否则状态置 WAITING_ON_RESULTS 并追加到 ms_RequestedAsyncShapeTests 队列 src/dev_ng/game/physics/WorldProbe/shapetesttask.cpp:19。
  • 只有 __ASSERT 构建会检查"该类型能否异步"(只有有向线段与扫掠球返回 true,其余类型强制改为同步)shapetestmgr.cpp:228-268、shapetestmgr.cpp:530-547。

异步队列的驱动者是 RAGE 的 phAsyncShapeTestMgr 工作线程:Init(INIT_CORE) 时创建它(16KB 栈)并注册 AsyncWorkerThreadCallBack;同时在 PC/主机上创建名为 ShapeTestAsyncSched 的专用调度器(64KB 栈、32KB scratch、线程掩码 (1<<1)|(1<<2)|(1<<3)|(1<<5));NM 也共享这个异步管理器(ART::NmRsCharacter::SetAsyncShapeTestMgr)shapetestmgr.cpp:89-160。回调每次:轮询已创建任务(sysTaskManager::Poll),完成的 ReturnResults 后析构并归还池;为尚未创建任务的请求 CreateTask,同时处理数不超过 20 shapetestmgr.cpp:365-500。主线程通过 CShapeTestManager::KickAsyncWorker 唤醒它,注册在游戏逻辑更新组中,并在渲染前再踢一次 src/dev_ng/game/Core/game.cpp:2855、game.cpp:3558。

CShapeTestTaskData 内部按类型 reinterpret 为 phShapeTest<phShapeProbe/Edge/Capsule/SweptSphere/Sphere/Object/Batch> 并调用 phShapeTestTaskData::Set* shapetesttask.cpp:283-389;结果经 FilterAndSortResults 过滤排序 shapetesttask.cpp:48-75。CShapeTestResults 析构或重置时如仍在等待,会调用 AbortTest(管理器可能接管结果内存)src/dev_ng/game/physics/WorldProbe/shapetestresults.h:242-263——这是调用方生命周期的关键约束:结果对象必须活到 RESULTS_READY 或主动中止。

脚本的 START_SHAPE_TEST_* 等原生命令建立在此之上,见 ../14-script-runtime/natives-and-commands.md。

7. 重力、休眠阈值、浮力与数据驱动参数#

  • 重力档位:GRAV_EARTH/MOON/LOW/ZERO 对应 9.8 / 2.4 / 0.1 / 0.0(与 commands_misc.sch 同步)src/dev_ng/game/physics/physics.h:83-91、physics.cpp:4032-4047。SetGravitationalAcceleration 同时设置车辆重力、RAGE 重力向量与休眠阈值 physics.cpp:4064-4083。
  • 休眠阈值随重力缩放:以 FRAME_LIMITER_MIN_FRAME_TIME / ms_NumTimeSlices 为子步,把 RAGE 的默认阈值(0.005/0.01/0.2)换算为与当前重力成比例,使 g=9.8 时恰好等于 RAGE 默认值 physics.cpp:4132-4157。
  • 浮力 CBuoyancy:每个物理实体最多 MAX_WATER_SAMPLES = 45 个水采样点(偏移、尺寸、浮力倍率、所属组件、keel/pontoon 位);CBuoyancyInfo 还有浮力常数、升力/龙骨倍率、XY/Z 向上/Z 向下阻力倍率、船采样行 src/dev_ng/game/physics/Floater.h:26-80。Process 跳过无碰撞体、基础附着、浮力系数≈0 的实体,然后逐采样计算浮力/升力/龙骨/阻力/阻尼/贴面力 src/dev_ng/game/physics/Floater.cpp:239-330、Floater.h:111-131;远处物体走低 LOD 浮力(ProcessLowLodBuoyancy)。例如 CObject 在物理处理中按子步调用它 src/dev_ng/game/Objects/object.cpp:2729。NM 也有自己的 bvid_buoyancy 行为与每资产浮力倍率(§9)。水面高度查询与河流见 ../11-rendering/environment-rendering.md。
  • 可调物体参数 CTunableObjectManager:按模型名覆盖质量、6 组阻尼、质心偏移、叉车附着区域、低 LOD 浮力距离/偏移、浮力/阻力系数、最大生命、撞下摩托冲量系数、玻璃框标志等(Tunable.psc 中 -1 表示不覆盖),从 CDataFileMgr::TUNABLE_OBJECTS_FILE 加载 src/dev_ng/game/physics/Tunable.psc:4-40、src/dev_ng/game/physics/Tunable.cpp:109-110。
  • 可变形物体 CDeformableObjectManager:DEFORMABLE_OBJECTS_FILE,碰撞冲量驱动可变形骨骼(DEFORMABLE_OBJECTS 下由 IterateManifold 调用)src/dev_ng/game/physics/Deformable.cpp:112-113。
  • 辅助类:PhysicsHelpers 提供按对方实体查找碰撞记录、碰撞方位、车辆碰撞责任判定(FindVehicleCollisionFault),CFlightModelHelper 提供安全施力/扭矩与分类型阻尼 src/dev_ng/game/physics/PhysicsHelpers.h:10-72;CWaterTestHelper 提供同步的河流探针、"是否靠近河流实体"判断以及含河流/海浪的水面高度查询(CBuoyancy 是其友元)src/dev_ng/game/physics/WaterTestHelper.h:25-51。gtaMaterialDebug.* 只是 BANK 下的材质调试界面(如禁用 see-through/shoot-through、清零穿透阻力,见 gtaMaterialManager.h 中的 BANK_ONLY 分支)。

8. game/cloth:已停用的旧实现#

game/cloth 是一套游戏自研布料(CCloth、CClothArchetype、CClothUpdater、约束图、弹簧、吊坠),但 ClothMgr.h 第一行 #define NORTH_CLOTHS 0,且所有 .cpp 都在 #if NORTH_CLOTHS 内;另一条 RAGE_CLOTHS 路径同样为 0 src/dev_ng/game/cloth/ClothMgr.h:1-20、ClothMgr.h:78-80;CPhysics::InitCore 中的 CClothMgr::Init() 也被 #if NORTH_CLOTHS 包裹 physics.cpp:1053-1055。GTA V 实际使用的布料是 RAGE 的 clothManager + environmentCloth/characterCloth,见 fragments-cloth-glass-rope.md。

9. Ragdoll 与 NaturalMotion(Euphoria)#

9.1 rage/naturalmotion 目录结构#

NaturalMotion 是第三方中间件;仓库里既有库源码,也有 Rockstar 侧的行为实现与工具。按用途划分(文件数为本次盘点所得):

目录内容与游戏的关系
src/fragmentnm/(20)fragNMAssetManager、fragInstNM、NMBehaviorPool(nmbehavior.psc)RAGE 侧胶水,游戏直接使用
src/rockstar/(207)ART::NmRsEngine、NmRsCharacter、肢体/效应器/IK、NmRsCBU_* 行为(CBU 任务)、NmRsCBU_DBM* 的 SPU job、bSpy 调试实际运行的行为引擎("RockstarARTPlugin")
src/art/、src/nmutils/、include/ART 内存管理、消息参数、bSpy 服务器、art.h/artfeedback.h/messageparams.h 等公共头接口层
behaviourDocuments/Behaviours.xml(约 1 万行,行为与参数说明)、生成脚本只用于调试调参 UI(见 §9.6)
NMScript/NMBehaviourScript.txt、NMBlockAndVars.txt文本形式的行为参数脚本
Applications/(BSpy、BSpy3、Endorphin、Morpheme)、tools/(viewfragnm、viewnm)、samples/、Assets/外部工具、查看器、样例与测试角色不参与运行
dump/(975)另一份 NM 代码/资源投放副本推断为供应商 drop 的镜像,未与 src/ 逐一比对
lib/、vcproj/、*.bat构建产物调试信息、工程、投放脚本—

行为 ID 枚举 BehaviourID 列出了全部 CBU 行为:buoyancy、dynamicBalancer、bodyBalance、braceForImpact、bodyFoetal、shot、staggerFall、teeter、armsWindmill(Adaptive)、balancerCollisionsReaction、spineTwist、catchFall、injuredOnGround、carried、dangle、yanked、dragged、bodyRollUp、upperBodyFlinch、fallOverWall、highFall、landing、rollDownStairs、pedalLegs、stumble、grab、animPose、onFire、bodyWrithe、pointGun、headLook、pointArm、electrocute、smartFall 等,另有 bvid_DirectInvoke 用于直接调用的消息 src/dev_ng/rage/naturalmotion/src/rockstar/NmRsCBU_Shared.h:56-116。本页不分析各行为算法。

9.2 引擎与 agent 管理#

  • 创建:fragNMAssetManager 构造时建立 NM 内存配置(回调到 RAGE 分配器)、ART::MemoryManager、ART::NmRsEngine,把它设为全局 ART::gRockstarARTInstance,并通过 ART::setRockstarEnvironment(PHSIM, PHLEVEL) 让 NM 直接操作 RAGE 物理关卡/模拟器 src/dev_ng/rage/naturalmotion/src/fragmentnm/manager.cpp:154-187。
  • 资产:NUM_ASSETS = 5(游戏 eNMAssetId:FRED、WILMA、FRED_LARGE、WILMA_LARGE、ALIEN)src/dev_ng/rage/naturalmotion/src/rockstar/NmRsCommon.h:23-31、src/dev_ng/game/physics/gtaInst.h:219-226。Load(name) 为每种角色类型分配 21 个部件、4 个 1-DOF 与 16 个 3-DOF 效应器参数,从 common:/data/naturalmotion/<name>.xml(__BANK 下可用 -nmfolder 改路径)解析 manager.cpp:237-292。
  • agent 上限:ON_DEMAND_AGENT 已定义,因此 agent 按需 createCharacter、移除时 destroyCharacter;可用数 = MAX_AGENTS(10) − 已插入角色数;另有 MAX_NON_NM_RAGDOLLS = 20 的非 NM ragdoll 计数 NmRsCommon.h:31、src/dev_ng/rage/naturalmotion/src/rockstar/NmRsEngine.cpp:48-50、manager.cpp:500-576、manager.cpp:644-652。
  • 每子步两段式:StepPhase1(PreSimUpdate 末尾)→ NmRsEngine::stepPhase1:缓存时间步与重力方向、对每个 agent 应用"incoming transforms"(来自动画的姿态/速度)、preStep、limb manager tick;StepPhase2(PostSimUpdate 中)→ stepPhase2:calculateCoMValues、m_taskManager->tick(dt)(运行所有激活的 CBU 行为,产出效应器目标)、postStep manager.cpp:660-670、NmRsEngine.cpp:579-818。因此 NM 的"大脑"在 RAGE 求解之外运行,通过关节肌肉驱动(效应器)作用于 phArticulatedBody。
  • 消息:游戏通过 fragInstNM::PostARTMessage(name, params) 发送行为消息;它对消息名做 NMutils::hashString 后调用 NmRsEngine::directInvoke,未识别的消息断言;没有 agent 时直接忽略 src/dev_ng/rage/naturalmotion/src/fragmentnm/instance.cpp:65-83。
  • 反馈:NM 通过 ARTFeedbackInterface 的 onBehaviourStart/Failure/Success/Finish/Event/Request 回调游戏 src/dev_ng/rage/naturalmotion/include/art/artfeedback.h:83-88;游戏实现为 ARTFeedbackInterfaceGta(持有父 inst)gtaInst.h:198-216,CTaskNM* 据此推进状态(见 07)。

9.3 fragInstNM:ragdoll 实例与激活#

ragdoll 物理 LOD 的部件数:高 21、中 16、低 12,并有高↔中/低的组件映射表 src/dev_ng/rage/naturalmotion/src/fragmentnm/instance.h:23-25、instance.h:114-118。fragInstNM 还提供配置角色、注册武器、设置探针标志、碰撞查询(HasCollidedWithWorld 等,以两字符掩码选身体部位)、SwitchFromNMToRageRagdoll(放弃 NM 但保持 RAGE ragdoll)等接口 instance.h:27-99。

fragInstNM::PrepareForActivation src/dev_ng/rage/naturalmotion/src/fragmentnm/instance.cpp:667-845:

  1. FLAG_NEVER_ACTIVATE 或无物理骨架(body type)→ 拒绝;已有 agent 时直接返回(在 InsertAgent 内部重入)。
  2. 调 fragInst::PrepareForActivation(进入 frag 缓存、取得 phArticulatedCollider,见 frag 页)。
  3. 高 LOD 人类套用 NM 管理器的自碰撞集合。
  4. 是否尝试 NM:未被阻止、有 ART 资产且加载成功、有可用 agent、且当前为 RAGDOLL_LOD_HIGH;否则标记为"非 NM ragdoll"(纯 RAGE 关节体模拟)。
  5. 取 agent(随机种子用于网络同步),部件数与 NM 角色不符则回收 agent 并把该 fragType 的 ART 资产 ID 置 -1。
  6. 把复合 bound 的当前/上一帧部件矩阵转成世界矩阵写给 NM(setComponentTMs current/previous),InsertAgent,initialiseCharacter(计算 COM、钳制速度)。
  7. 可选清零上一帧矩阵位置、重算复合包围盒并重建复合 BVH;碰撞体设置额外允许穿透 sm_ExtraAllowedRagdollPenetration。

游戏子类 fragInstNMGta::PrepareForActivation 在此之前加入游戏规则:死亡 ped 不被非玩家/正在 ragdoll 的玩家激活(除非本帧允许)、可按 ped 配置标志禁止被车辆激活、拾取物不能激活 ragdoll、站在可站立车辆/火车连接处时不因地面物体激活等 src/dev_ng/game/physics/gtaInst.cpp:2960-3074(函数延续到 3591 行,其余部分未逐行分析)。

9.4 共享 ragdoll 物理数据与动画过滤#

PHYSICS_LOD_GROUP_SHARING = 1 src/dev_ng/rage/suite/src/fragment/typephysicslod.h:23。CPhysics::InitSharedRagdollFragTypeData 在启动时同步加载 5 个"参考骨架"fragType(z_z_fred、z_z_wilma、z_z_fred_large、z_z_wilma_large、z_z_alien)并加引用,作为 5 个 ManagedPhysicsLODGroup 的来源,同时为每个创建"无效 ragdoll 骨骼过滤器";__DEV 下 -physicsrigsatload 可从 common:/data/ragdoll/ragdoll_type_* XML 重新加载 src/dev_ng/game/physics/physics.cpp:1715-1779、src/dev_ng/game/physics/physics.h:530-532。随后:设置 ragdoll 部件材质(§5)、fragNMAssetManager::Create 并加载 5 个 NM 资产(工作目录 platform:/data/naturalmotion)、CTaskNMBehaviour::LoadTaskParameters、动画速度注入钳制(线 3.0 / 角 6.0)、初始化自碰撞组 physics.cpp:1823-1856、physics.cpp:556-557。

每个 ped 模型在 CPedModelInfo::InitMasterFragData 中:若其 fragType 有 ART 资产 ID 且没有自己的物理 LOD 组,就直接指向对应的共享组,并复制缓存大小估计与共享过滤器;否则为它单独创建过滤器 src/dev_ng/game/modelinfo/PedModelInfo.cpp:893-922。也就是说,所有人形 ped 共用五套 ragdoll 刚体/关节定义,只有外观骨架不同。

CreateInvalidRagdollFilter 找出"不映射到 ragdoll 部件、但其父与子都映射到部件"的骨骼,加入 CMovePed::RagdollFilter;进入 ragdoll 时这些骨骼的动画被混合回默认姿态,因为 ragdoll 装备无法处理不在默认姿态的未映射父骨骼(注释)physics.cpp:1883-1946。

9.5 动画 ↔ 物理切换规则#

ped 有两个物理 inst:动画胶囊(GetAnimatedInst)与 ragdoll(m_pRagdollInst,fragInstNMGta),SwitchCurrentPhysicsInst 在二者间切换。状态机 eRagdollState src/dev_ng/game/Peds/pedDefines.h:88-98:

状态含义
RAGDOLL_STATE_ANIM_LOCKED锁定在动画,永不切换
RAGDOLL_STATE_ANIM_PRESETUP动画尚在建立,至少渲染一次前不能激活
RAGDOLL_STATE_ANIM纯动画;ragdoll 不响应碰撞,只有测试探针能命中
RAGDOLL_STATE_ANIM_DRIVENragdoll 激活但由动画驱动(仅玩家)
RAGDOLL_STATE_PHYS_ACTIVATE已开启、正在移除受约束的动画碰撞体
RAGDOLL_STATE_PHYS完全物理(反过来驱动动画)

证据与细节:

  • 入口:CPed::SwitchToRagdoll(CEvent&) 要求事件 RequiresAbortForRagdoll() 且优先级 ≥ E_PRIORITY_SWITCH_2_NM,事件加入成功才切换并向 NM 任务发送 start 消息;否则退化为 CEventSwitch2NM(10000, CTaskNMRelax) src/dev_ng/game/Peds/Ped.cpp:4668-4746。
  • 模拟器运行中延迟:SwitchToRagdollInternal 若发现 PHSIM->IsUpdateRunning(),只 ActivateObject 并设 PHYS_ACTIVATE 后返回,由 PostSimUpdate 再调 SwitchToRagdollPostPhysicsSimUpdate 完成 Ped.cpp:4859-4867、Ped.cpp:4748-4767。
  • 完整切换:处于复用池的 ped 拒绝;替换绳索约束端点;等待动画 pre-physics 完成、更新骨架、关闭运动学物理;加入 ragdoll 池(默认 gameplay 池);把动画平均速度注入 NM;离开车辆/坐骑等;移除动画 inst、SwitchCurrentPhysicsInst(ragdoll)、状态 PHYS;激活 ragdoll 并设置上一帧矩阵,非 NM agent 时把碰撞体速度加到每个部件;CMovePed::SwitchToRagdoll(invalidRagdollFilter);插入启用的 ragdoll 约束;按配置可能立即触发死亡事件 Ped.cpp:4769-5060。
  • 回到动画:SwitchToAnimated 移出 ragdoll 池、切回动画 inst、状态 ANIM、可从缓存条目移除关节体、在释放约束后 DeactivateObject ragdoll、可切到静态帧、重新添加动画物理 Ped.cpp:5178-5390。
  • 全局开关:联机且不允许 NM、或回放控制世界时 CPhysics::CanUseRagdolls() 为 false physics.cpp:4258-4275。

9.6 Ragdoll 池、约束与调参#

  • 池:CTaskNMBehaviour 维护三个 CRagdollPool:kRagdollPoolNmGameplay、kRagdollPoolRageRagdoll、kRagdollPoolLocalPlayer;RagdollPoolHasSpaceForPed 可按优先级分数(CalcRagdollScore)中止最低分的现有 ragdoll src/dev_ng/game/task/Physics/TaskNM.h:764-830。容量来自 tunables(psc 初值:单机 NM 3、RAGE ragdoll 3、为本地玩家保留 1;联机 10/20/1,且联机默认不启用池化)src/dev_ng/game/task/Physics/Metadata.psc:845-853、src/dev_ng/game/task/Physics/TaskNM.cpp:7816-7826。注意联机 NM 上限 10 恰好等于 ART::MAX_AGENTS。
  • ragdoll 约束 CRagdollConstraintComponent:手铐(腕-腕及腕-骨盆)、绑脚、挂钩到世界("meathook" 场景),保存 phConstraintHandle,在切换到 ragdoll 时 InsertEnabledConstraints、切回时 RemoveRagdollConstraints src/dev_ng/game/physics/RagdollConstraints.h:14-52。
  • 调参数据:Behaviours.xml 与 TaskMessages.xml 只在 -nmtuning 时由 CNmTuningSet::AddWidgetsInternal 加载进 NMBEHAVIORPOOL 用于 RAG 控件 TaskNM.cpp:925-948;NMScript/NMBehaviourScript.txt 是 behaviour <name> … behaviourEnd 的键值脚本格式 src/dev_ng/rage/naturalmotion/NMScript/NMBehaviourScript.txt:1-22;运行时任务参数由 CTaskNMBehaviour::LoadTaskParameters 加载(见 07)。

10. 约束与边界行为#

(另见下方"激活与物理 LOD 策略"汇总。)

10.1 激活与物理 LOD 策略汇总#

游戏没有一个统一的"物理 LOD 管理器",而是在多层分别控制物理开销:

层机制证据 / 详见
静态地图碰撞以 FIXED 对象流入关卡,经 g_StaticBoundsStore/fwBoxStreamer 按需加载physics.cpp:937-941;../05-world/game-streaming-and-scene-streamer.md
物体远处物体可为无物理的 dummy(CObjectPopulation::ConvertToDummyObject,例如重置玻璃时使用);被撞时由 INACTIVE 通过 PrepareForActivation 激活physics.cpp:628-640;../05-world/world-and-entities.md
休眠阈值随重力缩放、按岛休眠,只在最后时间片判断§7、rage-physics-engine.md §4.4
frag缓存条目数固定,按"未使用 + 不可见 + 影响最小"淘汰;可简化为刚体碰撞体fragments-cloth-glass-rope.md §1.4
浮力远处物体走低 LOD 浮力(ProcessLowLodBuoyancy,距离由 tunable m_lowLodBuoyancyDistance 控制)Floater.h:164-165、src/dev_ng/game/Objects/object.cpp:2634、Tunable.psc:19-24
ragdoll三级 ragdoll LOD(21/16/12 部件);SetCurrentPhysicsLOD 切换前先移除 ped 的约束;只有高 LOD 才分配 NM agent;SetNMAssetSize 可在普通/大号资产间切换并重载物理数据gtaInst.cpp:3711-3761;§9.3
ragdoll 数量NM agent 上限 10、非 NM ragdoll 计数上限 20、游戏 ragdoll 池(单机 3/3/1)§9.2、§9.6
车辆车辆的 dummy/superdummy 与轮子物理不在本页../08-vehicles/vehicle-physics-handling-damage.md

10.2 其它边界#

  • 所有 SU_* 回调、PreSimUpdate、PostSimUpdate 期间只允许主线程改物理(SetOnlyMainThreadIsUpdatingPhysics);verlet 任务在 ProcessAfterAllMovement 开头与 PreRender 之后被等待,在此之前布料任务可能在其它线程修改物理。
  • PostSimUpdate 中切换 ragdoll 会改变激活集合,代码因此先缓存索引再处理、并断言激活数不减少。
  • ragdoll 待切换数组 32、推迟实体 50、碰撞记录 600 等都是硬上限,超出时只断言/丢弃。
  • 异步 WorldProbe:池满即 SUBMISSION_FAILED;结果对象析构会中止任务;不支持"个体对象"域。
  • NM agent 不足或非高 LOD 时 ragdoll 静默退化为非 NM ragdoll。

未决问题 / 证据不足#

  • fragInstNMGta::PrepareForActivation(约 630 行)、ShouldFindImpacts、PreComputeImpacts 的完整规则未逐行分析,只列出已读到的部分。
  • CTaskNMBehaviour::CanUseRagdoll、CalcRagdollScore 的评分细节属于 07 范围,未在此核对。
  • 实际发布数据中的 tunables 值(可能覆盖 psc 初值)未检查。
  • rage/naturalmotion/dump/ 与 src/ 的差异未比对。