游戏物理层、WorldProbe 与 Ragdoll / NaturalMotion
本页解释 GTA V 游戏代码(game/physics)如何驱动 RAGE 物理引擎:CPhysics 何时初始化、每帧在 CGame 更新序列中的位置与子步(time slice)调度、碰撞层(ArchetypeFlags)、游戏 phInst 子类与碰撞事件回调、材质 ID 打包、WorldProbe 形状测试(含异步队列)、浮力;以及 ragdoll 的完整链路:NaturalMotion(Euphoria)库在源码中的结构、fragNMAssetManager/fragInstNM 胶水、NM agent 池与游戏 ragdoll 池、ragdoll 约束、动画 ↔ 物理切换规则。
背景与相关页面#
- 引擎本体(关卡、模拟器、求解器、bound、形状测试框架):rage-physics-engine.md。
fragInst/fragType/缓存、布料、玻璃、绳索:fragments-cloth-glass-rope.md。CTaskNM*任务族(NM 行为任务、CTaskNMControl等):只在 ../07-ai-tasks/task-catalogue.md 展开,本页只讲它们依赖的物理/NM 接口。CPhysical::ProcessPhysics/ProcessCollision、CEntity层次:../05-world/world-and-entities.md。CPed、ragdoll inst 的所有权:../06-peds/ped-architecture.md。- 车辆物理(轮子、操控、
CVehicleDeformation):../08-vehicles/vehicle-physics-handling-damage.md。 gameSkeleton注册与主循环:../01-runtime/game-skeleton-and-main-loop.md。- 动画侧(MoVE、
CMovePed、ragdoll 帧过滤):../10-animation/game-animation-and-ik.md。
相关源码文件#
| 文件 | 作用 |
|---|---|
game/physics/physics.h/.cpp | CPhysics:初始化、每帧更新、碰撞事件分发、ragdoll 共享数据、调试 |
game/physics/gtaArchetype.h/.cpp | ArchetypeFlags 碰撞层;gtaFragType |
game/physics/gtaInst.h/.cpp | phInstGta、fragInstGta、fragInstNMGta、ARTFeedbackInterfaceGta、PH_INST_* 游戏类型号 |
game/physics/gtaMaterial*.h/.cpp、gtaMaterialManager.psc | phMaterialGta、phMaterialMgrGta(材质 ID 打包、材质数据加载) |
game/physics/CollisionRecords.h/.cpp | CCollisionHistory/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.psc | ragdoll 池与 NM 调优(任务本身见 07) |
1. CPhysics 的生命周期#
CPhysics::Init(initMode) 按模式分派 src/dev_ng/game/physics/physics.cpp:1248-1293:
| 模式 | 调用者 | 动作 |
|---|---|---|
INIT_CORE | CScene::Init 直接调用(依赖 InitPools,CPed::InitSystem 依赖它)src/dev_ng/game/scene/scene.cpp:651-659 | InitCore();NAVMESH_EXPORT 时初始化请求列表 |
INIT_BEFORE_MAP_LOADED | gameSkeleton 注册 src/dev_ng/game/Core/game.cpp:2118 | 按默认值为破碎玻璃分配内存(bgGlassManager::Malloc) |
INIT_SESSION | gameSkeleton 注册 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_NODES | 1500 | src/dev_ng/game/physics/physics.h:72-81 |
最大激活数 iMaxActiveObjects | maxPeds*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 + 64 | physics.cpp:789 |
| manifold / 复合 / contact | iMaxActiveObjects*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 matrix | phLevelNew::MAX_INST_LAST_MATRICES | physics.cpp:822 |
| 延迟八叉树容量 | iMaxActiveObjects * 4 | physics.cpp:840-842 |
| scratchpad | 64 位 2MB、否则 1MB(-phscratchpad) | physics.cpp:861-868 |
| 高优先级 manifold | (maxPeds+maxVehicles)*0.5*2 | physics.cpp:873 |
| 外部速度 manifold / 实例行为 | 256 / 256 | physics.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 下创建phMouseInputphysics.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 = 2physics.cpp:169,每帧开头重置(除非-physicsupdates或ms_OverriderNumTimeSlices)。子步 dt = 帧 dt / n,写入fwTimer::SetRagePhysicsUpdateTimeStepphysics.cpp:2204-2225。 - 若系统帧时间 < 1/60 秒(高帧率),改为单子步,dt 截断到 1/30 秒;
ms_bIgnoreForcedSingleStepThisFrame可跳过一次physics.cpp:2226-2245。 finalUpdate(push 与休眠)默认只在最后一个子步为真:onlyPushOnLastUpdate = !ms_DisableOnlyPushOnLastUpdateForOneFrame && ms_OnlyPushOnLastUpdate && !ms_bInStuntModephysics.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:
- 首个子步处理 GPU 写回的车辆损坏(
GPU_DAMAGE_WRITE_ENABLED);可选取回车辆录像数据。 - 通过
fwSceneUpdate::Update(SU_PRESIM_PHYSICS_UPDATE, &preSimControl)让每个实体执行ProcessPhysics;回调可把实体放进"推迟"数组aPostponedEntities(上限 50,另有第二组推迟数组)以便晚于其它实体处理。 - 推迟实体再
ProcessPhysics;返回PHYSICS_NEED_SECOND_PASS的进入第二遍ProcessPhysics2,再返回PHYSICS_NEED_THIRD_PASS的进入第三遍ProcessPhysics3(第二/三遍数组容量 =VEHICLE_POOL_SIZE,physics.h:93-119);第二组推迟实体最后处理。 - 网络克隆在合适的那一遍(首个子步、或"先混合再物理"的实体)调用
netBlender->Update()。 FRAGNMASSETMGR->StepPhase1(dt)(NM 前半步,§9.2),PreUpdateInstanceBehaviors。- 若脚本请求清 manifold(
ms_bClearManifoldsEveryFrame或剩余帧数),重置所有 pruned pair 的 manifold(断言提示一般只应在室内用)。 - 首子步清
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:
- 遍历激活对象:
- 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。
- ped 的 ragdoll inst(状态 >
- 对待切换的 ped 调用
SwitchToRagdollPostPhysicsSimUpdate;网络克隆额外发送NM_BALANCE_MSG(start)。 - 复制激活索引后遍历:最后子步做网络混合
ProcessPostPhysics,CPhysical::UpdateEntityFromPhysics、ProcessProbes、重置 no-collision、补加寻路动态障碍。 FRAGNMASSETMGR->StepPhase2(dt)(NM 后半步)、CExplosionManager::Update、螺旋桨碰撞、对已"安定"的 ped 调DeactivatePhysicsAndDealWithRagdoll。
3. 碰撞层(ArchetypeFlags)#
ArchetypeFlags 把 32 位 type/include 标志用作碰撞层;必须与 ragebuilder 的副本保持同步(注释)src/dev_ng/game/physics/gtaArchetype.h:27-72:
| 位 | 名称 | 位 | 名称 |
|---|---|---|---|
| 1 | GTA_MAP_TYPE_WEAPON | 17 | GTA_EXPLOSION_TYPE |
| 2 | GTA_MAP_TYPE_MOVER | 18 | GTA_PICKUP_TYPE |
| 3 | GTA_MAP_TYPE_HORSE | 19 | GTA_FOLIAGE_TYPE |
| 4 | GTA_MAP_TYPE_COVER | 20 | GTA_FORKLIFT_FORKS_TYPE |
| 5 | GTA_MAP_TYPE_VEHICLE | 21 | GTA_WEAPON_TEST |
| 6 | GTA_VEHICLE_NON_BVH_TYPE | 22 | GTA_CAMERA_TEST |
| 7 | GTA_VEHICLE_BVH_TYPE | 23 | GTA_AI_TEST |
| 8 | GTA_BOX_VEHICLE_TYPE | 24 | GTA_SCRIPT_TEST |
| 9 | GTA_PED_TYPE | 25 | GTA_WHEEL_TEST |
| 10 | GTA_RAGDOLL_TYPE | 26 | GTA_GLASS_TYPE |
| 11 / 12 | GTA_HORSE_TYPE / GTA_HORSE_RAGDOLL_TYPE | 27 | GTA_RIVER_TYPE |
| 13 | GTA_OBJECT_TYPE | 28 | GTA_SMOKE_TYPE |
| 14 | GTA_ENVCLOTH_OBJECT_TYPE | 29 | GTA_UNSMASHED_TYPE |
| 15 | GTA_PLANT_TYPE | 30 | GTA_STAIR_SLOPE_TYPE |
| 16 | GTA_PROJECTILE_TYPE | 31 | GTA_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_WEAPONsrc/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)。
| 类 | 基类 | 覆写的关键钩子 | 证据 |
|---|---|---|---|
phInstGta | phfwInst | ShouldFindImpacts(FLAG_NO_COLLISION、同实体、上下车时与自己车辆、门规则…)、PreComputeImpacts、外部控制速度、激活/去激活 | gtaInst.cpp:153-810 |
fragInstGta | phfwFragInst | 以上 + ModifyImpulseMag(车辆 frag 冲量函数)、PartsBrokeOff、CollisionDamage、FindBreakStrength、缓存相关 | gtaInst.cpp:836-2337 |
fragInstNMGta | fragInstNM | ragdoll 的碰撞过滤、激活检查、动画姿态/速度注入、车辆弹出、LOD | gtaInst.cpp:2448-4092 |
4.2 碰撞事件回调链#
RAGE 不提供"碰撞事件队列",游戏在每个子步模拟完成后遍历 manifold 自取:
CPhysics::IterateOverManifolds遍历phSimulator的重叠对数组,跳过约束 manifold;复合 manifold 遍历其子 manifold;只有接触数 > 0 时才进行昂贵的ValidateAndRefreshRootManifold(核对 inst/entity 仍有效)physics.cpp:3323-3383。IterateManifold先累计激活接触的平均冲量;校验复合组件索引(物体换 LOD 或破碎后可能失效);逐接触(或按 manifold,取决于g_vfxMaterial设置)构造VfxCollisionInfo_s调ProcessCollisionVfxandSfx,然后调ProcessCollisionData;可变形物体处理可变形骨骼physics.cpp:2926-3140。ProcessCollisionData先缓存接触数据(回调可能删除接触),再分别对 A、B 调CPhysical::ProcessCollision(...),B 之前检查 A 的 inst 是否仍在关卡中(B*299855)physics.cpp:3775-3826。CPhysical::ProcessCollision写入CCollisionHistory(AddCollisionRecord)src/dev_ng/game/scene/Physical.cpp:1892、Physical.cpp:2005-2035。CCollisionHistory每帧清空、跨子步合并,每个碰撞实体一条记录,记录池COLLISIONHISTORY_RECORDSTORESIZE = 600src/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)ID | UnpackProcId |
| 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:
PreLoadGtaMtlFile("materials")扫描common:/data/materials/materials.dat,统计非#行数以预留容量(断言 < 0xffff)gtaMaterialManager.cpp:61-92。LoadGtaMtlFile先加载 rumble profile,再逐行用phMaterialGta::LoadGtaMtlData解析(首行是版本号)gtaMaterialManager.cpp:99-131。- 用
FindMaterialId缓存几十个常用 ID(如g_idSnowDeep、联机特殊表面TEMP_01..09、特技坡道STUNT_RAMP_SURFACE等)。 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_READYsrc/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.hwpcommon.h:79-81。 - 容量:每测试最多 32 个交点、
PROCESS_LOS_MAX_EXCEPTIONS32、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 行为,产出效应器目标)、postStepmanager.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:
FLAG_NEVER_ACTIVATE或无物理骨架(body type)→ 拒绝;已有 agent 时直接返回(在InsertAgent内部重入)。- 调
fragInst::PrepareForActivation(进入 frag 缓存、取得phArticulatedCollider,见 frag 页)。 - 高 LOD 人类套用 NM 管理器的自碰撞集合。
- 是否尝试 NM:未被阻止、有 ART 资产且加载成功、有可用 agent、且当前为
RAGDOLL_LOD_HIGH;否则标记为"非 NM ragdoll"(纯 RAGE 关节体模拟)。 - 取 agent(随机种子用于网络同步),部件数与 NM 角色不符则回收 agent 并把该 fragType 的 ART 资产 ID 置 -1。
- 把复合 bound 的当前/上一帧部件矩阵转成世界矩阵写给 NM(
setComponentTMscurrent/previous),InsertAgent,initialiseCharacter(计算 COM、钳制速度)。 - 可选清零上一帧矩阵位置、重算复合包围盒并重建复合 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_DRIVEN | ragdoll 激活但由动画驱动(仅玩家) |
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、可从缓存条目移除关节体、在释放约束后DeactivateObjectragdoll、可切到静态帧、重新添加动画物理Ped.cpp:5178-5390。 - 全局开关:联机且不允许 NM、或回放控制世界时
CPhysics::CanUseRagdolls()为 falsephysics.cpp:4258-4275。
9.6 Ragdoll 池、约束与调参#
- 池:
CTaskNMBehaviour维护三个CRagdollPool:kRagdollPoolNmGameplay、kRagdollPoolRageRagdoll、kRagdollPoolLocalPlayer;RagdollPoolHasSpaceForPed可按优先级分数(CalcRagdollScore)中止最低分的现有 ragdollsrc/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、切回时RemoveRagdollConstraintssrc/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/的差异未比对。