Ped 架构:CPed、工厂、标志、组件与 PedGroup
本页解释 GTAV 中“行人(ped)”这一实体类型在 C++ 层的骨架:CPed 持有哪些子对象、如何被 CPedFactory 创建与销毁、两套标志系统(config flags / reset flags)如何在帧内被重置、一个 ped 每帧经历的处理顺序、它与 CPedModelInfo 的数据交接、外观组件(变体/道具)的数据层、玩家专有的 CPlayerInfo,以及编组系统 CPedGroup。最后简述位于 RAGE suite 的 pedsafezone(它实际服务于车辆形变)。
ped 是整个游戏最复杂的实体:Ped.cpp 单文件约 33,000 行,ped.h 约 3,700 行。理解本页后再阅读 AI、人口、运动与渲染子页会更顺畅。
背景与相关页面#
- 实体类层次(
CEntity→CDynamicEntity→CPhysical→CPed)、CGameWorld的场景更新回调:见 ../05-world/world-and-entities.md。 CPedModelInfo(ped 原型、peds.meta、变体信息加载):见 ../05-world/model-info.md。gameSkeleton注册与主循环:见 ../01-runtime/game-skeleton-and-main-loop.md;池与gameconfig.xml:见 ../01-runtime/memory-and-pools.md。- 同目录子页:
- ped-intelligence-and-population.md:
CPedIntelligence、AI LOD、关系组、人口与 PopCycle。 - ped-motion-and-rendering.md:运动数据/运动任务、动画接口、变体流送、头部混合、伤害贴花、头像。
- ped-subsystems.md:Horse、Action、CriminalCareer、PedWeapons、几何分析器、IK 设置等其余子系统与文件盘点。
- ped-intelligence-and-population.md:
- 任务系统细节(
CTask/任务树/事件响应):见 ../07-ai-tasks/task-system.md 与 ../07-ai-tasks/events-and-responses.md。 - 网络同步(
CNetObjPed):见 ../17-network-sync/network-object-model.md。
相关源码文件#
| 文件 | 作用 |
|---|---|
src/dev_ng/game/Peds/ped.h、Ped.cpp | CPed 类定义与实现(构造、每帧处理、物理、渲染前处理、变体入口) |
src/dev_ng/game/Peds/pedDefines.h | 池大小宏、状态枚举(死亡/逮捕/布娃娃)、任务树与优先级枚举 |
src/dev_ng/game/Peds/PedFlags.h、PedFlags.cpp、PedFlagsMeta.psc | CPedConfigFlags/CPedResetFlags 与 parser 生成的位集枚举 |
src/dev_ng/game/Peds/PedFactory.h、PedFactory.cpp | 创建/销毁 ped 与玩家 ped、池初始化、(已停用的)销毁缓存 |
src/dev_ng/game/Peds/PedType.h | ePedType |
src/dev_ng/game/Peds/PedComponentSetInfo.*、PedCapsule.*、HealthConfig.*、TaskData.*、PedIKSettings.*、NavCapabilities.* | 由模型信息哈希引用的数据驱动配置 |
src/dev_ng/game/Peds/rendering/PedVariationDS.h | ePedVarComp、CPedVariationData、CPedHeadBlendData、CPedVariationInfoCollection |
src/dev_ng/game/Peds/rendering/PedProps.h | 道具锚点与 CPedPropData |
src/dev_ng/game/Peds/PlayerInfo.h、PlayerInfo.cpp、PlayerInfo.psc | 玩家附加数据 CPlayerInfo |
src/dev_ng/game/PedGroup/PedGroup.h、PedGroup.cpp、formation.h、formation.cpp | ped 编组、成员与编队 |
src/dev_ng/rage/suite/src/pedsafezone/pedsafezone.h、.cpp | 车辆座舱保护区 CPedSafeZone |
src/dev_ng/game/renderer/Entities/PedDrawHandler.h | CPedDrawHandler(变体数据实际的存放处,渲染侧) |
1. 对象模型总览#
CPed 派生自 CPhysical,并通过 FW_REGISTER_CLASS_POOL(CPed) 使用独立对象池;其构造函数是 protected 且只对 CPedFactory 开放(注释强调“必须使用 CPedFactory”)src/dev_ng/game/Peds/ped.h:214-238。一个 ped 的大部分行为由挂在它身上的“组件”对象实现:
图中证据:智能、玩家信息、库存、武器管理器、面部、电话、头盔、动画/布娃娃实例指针 src/dev_ng/game/Peds/ped.h:2866-2878;m_MotionData、m_IkManager src/dev_ng/game/Peds/ped.h:2736、ped.h:2754;m_pedAiLod ped.h:2882;CPedDrawHandler 访问器 ped.h:1924-1925;绘制句柄中的变体/道具数据与流送渲染 gfx src/dev_ng/game/renderer/Entities/PedDrawHandler.h:20-44。
要点:
- 外观数据(
CPedVariationData、CPedPropData)不在CPed内,而在其CPedDrawHandler中;CPed::SetVariation等接口只是转发(见第 8 节)。 CPedIntelligence也有独立池(见 ped-intelligence-and-population.md),由CPedIntelligenceFactory::Create分配src/dev_ng/game/Peds/Ped.cpp:1621-1622。- “控制者”由
CControlledByInfo两个位描述:是否网络控制、是否玩家;由此派生本地 AI / 网络 AI / 本地玩家 / 网络玩家四种判定src/dev_ng/game/Peds/ped.h:159-190。构造时写入m_isControlledByNetwork/m_isControlledByPlayerPed.cpp:1327-1329。
2. CPed 的内存布局与关键成员#
2.1 池与“人口缓存”过滤#
CPed与CPedIntelligence池均用CONFIGURED_FROM_FILE实例化(容量取自gameconfig.xml),COMMERCE_CONTAINER构建使用无 flex 溢出的变体src/dev_ng/game/Peds/PedFactory.cpp:74-80。MAXNOOFPEDS不再是编译期常量,而是CPoolHelpers::GetPedPoolSize(),注释说明“曾经是 90,现在可经 gameconfig.xml 设置”src/dev_ng/game/Peds/pedDefines.h:10-16;值由system.cpp从配置管理器读取"Peds"池大小后写入src/dev_ng/game/system/system.cpp:965,GetPedPoolSize()在未设置时TrapZPedFactory.cpp:1112-1134。fwPool<CPed>::GetSlot有模板特化:处于“人口缓存”的 ped(GetIsInPopulationCache())对池遍历不可见src/dev_ng/game/Peds/ped.h:3604-3620。注意:这是m_bIsInPopulationCache位,与人口复用池的CPED_CONFIG_FLAG_PedIsInReusePool是两套不同机制(见第 6.4 节和人口页)。
2.2 “不要乱动”的位域块#
ped.h 中有一段注释为“THE FOLLOW CODE IS OPTIMIZED! DON'T FUCK WITH IT!”的紧凑位域区,把调试 AI 时最常查看的状态压在一起:控制者、人口计数归属、m_bRemoveAsSoonAsPossible、m_bIsInPopulationCache 等布尔位,以及 ePedLodState:4、eRagdollState:4、eArrestState:3、eDeathState:3、ePedType:7、期望/当前布娃娃池各 4 位、约束类型 3 位,之后是伤害集 ID 等 u8 src/dev_ng/game/Peds/ped.h:2756-2864。注释里的 “65/64 .. nice .. off by one...” 说明该块已超出 64 位 1 位。
2.3 关键成员分组#
| 分组 | 代表成员 | 说明 / 证据 |
|---|---|---|
| 运动与地面 | m_vDesiredVelocity、m_vecGroundPos、m_vecGroundNormal、m_vGroundVelocity*、m_MotionData | 期望速度、地面探测结果、站在移动物体上的参考速度 ped.h:2698-2736 |
| 音频/脚步 | m_PedFootStepHelper、m_PedAudioEntity、m_pSpeechAudioEntity | ped.h:2738-2740、ped.h:2874 |
| 人口计时 | m_delayedRemovalResetTimeMs/AmountMs、m_delayedConversion*、m_InFovTime、m_creationTime | 删除/转换延迟计时器,供人口系统判断 ped.h:2919-2925 |
| 数据集指针(不拥有) | m_pCapsuleInfo、m_pComponentClothInfo、m_pComponentSetInfo、m_pMotionTaskDataSet、m_pDefaultTaskDataSet、m_pIKSettings、m_pTaskData | 由模型信息哈希解析得到 ped.h:2950-2973 |
| 每 ped 物理原型 | m_pPhysicsArchetype | 注释:每个 ped 持有自己原型的实例,以便自由修改 bounds ped.h:2967-2968 |
| 掩体 | m_pCoverPoint、m_pDesiredCoverPoint | ped.h:2975-2976 |
| 约束 | m_RotationalConstraintX/Y、m_CylindricalConstraint | 防止胶囊体俯仰/翻滚 ped.h:3018-3025 |
| 特殊能力 | m_specialAbilities[PSAS_MAX]、m_selectedAbilitySlot | PSAS_MAX = 2 ped.h:3027-3028、PlayerInfo.h:87-93 |
| 布料/布娃娃 | m_ClothCollision、m_CClothController[PV_MAX_COMP]、m_pRagdollInst(为 SPU 代码取地址而 public) | ped.h:3061-3065 |
| 标志 | m_PedConfigFlags、m_PedResetFlags、缓存的 m_CachedSceneUpdateFlags | ped.h:3067-3071 |
| 生命值/护甲 | m_fMaxEndurance、m_nArmour、受伤阈值 m_f*HealthThreshold | ped.h:3132-3147 |
| 地面物理体 | m_pGroundPhysical、m_pLastValidGroundPhysical、m_pTrainRidingOn | ped.h:3175-3183 |
| 空间索引 | m_spatialArrayNode(静态 ms_spatialArray) | ped.h:3219-3220、ped.h:3548 |
| 编组 | m_iPedGroupIndex | 注释:由 CPedGroups::Process() 每帧开始时设置 ped.h:3284 |
| 动作模式 | m_pMovementModes、m_MovementModeClipSets、四个 fwClipSetRequestHelper | 个性化移动模式与其片段集流送 ped.h:3315-3345 |
调试(!__FINAL) | m_debugPedName[16]、m_damageRecords(最多 10 条) | ped.h:3379-3391 |
平台/构建差异:
RSG_PC下m_nArmour包装为sysLinkedData<float, ReportArmorMismatch>,其他平台为裸floatped.h:3137-3141。这属于反篡改范畴,本 Wiki 只记录其存在,细节见 ../23-debug-security/security-overview.md。- 马匹相关成员(
m_pMyMount、m_pSeatManager、m_pHorseComponent)只在ENABLE_HORSE下存在ped.h:3153-3155、ped.h:3238-3243;GTAV 构建中该宏为 0(见 ped-subsystems.md)。 - 喷气背包
m_pJetpack在ENABLE_JETPACK下存在ped.h:3157-3159;ENABLE_JETPACK仅在!__FINAL && !__XENON为 1src/dev_ng/game/task/Movement/JetpackObject.h:9-13。 ENABLE_DRUNK定义为 0ped.h:81。
2.4 关键状态枚举#
| 枚举 | 值 | 证据 |
|---|---|---|
ePedLodState | PLS_HD_NA / NONE / REQUESTED / AVAILABLE / REMOVING(HD 资源状态) | ped.h:136-145 |
eDeathState | Alive / Dying / Dead | pedDefines.h:78-86 |
eArrestState | None / Arrested | pedDefines.h:69-76 |
eRagdollState | ANIM_LOCKED / ANIM_PRESETUP / ANIM / ANIM_DRIVEN / PHYS_ACTIVATE / PHYS | pedDefines.h:88-98 |
ePedType | PLAYER_0..2、NETWORK_PLAYER、CIVMALE/CIVFEMALE、COP、11 种帮派、DEALER/MEDIC/FIRE/...、SWAT、ANIMAL、ARMY | src/dev_ng/game/Peds/PedType.h:11-46 |
IsDead() 不是单看死亡状态:要求 GetIsDeadOrDying() 且生命值 ≤ 0;注释说明后者在 MP 中仍然需要 ped.h:1402-1418。
3. 标志系统:Config Flags 与 Reset Flags#
两个标志类都基于 parser 生成的位集枚举(PedFlagsMeta.psc 中 <enumdef ... generate="bitset">):
| 类 | 语义 | 规模 | 证据 |
|---|---|---|---|
CPedConfigFlags | “相对静态”的能力/选项/特殊设置,持续有效直到被改写 | ePedConfigFlags 462 个枚举值;另有 4 个小位域(不可被撞下车模式、腿部 IK 模式、手势模式、组内乘客座位索引)与 fClimbRateOverride | src/dev_ng/game/Peds/PedFlags.h:15-60、PedFlagsMeta.psc:5-468 |
CPedResetFlags | 每帧自动复位的“通知”标志,主要由任务设置以触发别处代码 | ePedResetFlags 460 个枚举值;另有若干倒计数器与每帧复位的浮点修正量 | PedFlags.h:63-183、PedFlagsMeta.psc:470-993 |
(数量为对 .psc 中 <enumval> 行的计数。)
3.1 Reset flags 的分阶段复位#
CPedResetFlags 并非“每帧一次清零”,而是持有 7 个静态掩码,在帧内不同时间点按掩码清除不同子集(IntersectNegate):
| 复位函数 | 头文件注释给出的时机 | 实际调用点 |
|---|---|---|
Reset | 每帧 | CPed::ProcessControl_ResetVariables Ped.cpp:30674 |
ResetPreAIPostInventory | AI 前、库存后 | ProcessControl_WeaponsAndAccessoriesPreIntelligence Ped.cpp:31183 |
ResetPreTask | 任务前、事件处理后 | CPedIntelligence::Process_Tasks PedIntelligence.cpp:3136-3139 |
ResetPrePhysics | 物理前、ProcessControl 后 | CPed::ProcessPhysics Ped.cpp:19970 |
ResetPostPhysics | 物理后、脚本前 | CPedIntelligence::ProcessPostPhysics PedIntelligence.cpp:1434-1438 |
ResetPostMovement / ResetPostPreRender | 脚本后 / PreRender 后 | 注释 PedFlags.cpp:1090-1113 |
ResetPostCamera | 相机后 | CPed::ProcessPostCamera Ped.cpp:31472 |
函数声明与注释见 PedFlags.h:71-80,掩码在 StaticInit 中填充 PedFlags.cpp:433-470,StaticInit 由 CPed::InitSystem 调用 Ped.cpp:2225。
Reset() 的细节 PedFlags.cpp:981-1058:
- 先根据
DontActivateRagdollFromAnyPedImpact(reset 或 config 版本)同步布娃娃实例的FLAG_NM_NO_PLAYER_PED_ACTIVATE。 - 保存需要“跨帧保留”的少数位(
ResetMoveGroupAfterRagdoll、KnockedToTheFloorByPlayer在仍处于布娃娃时保留;InternalWalkingRndPlayer转写为IsWalkingRoundPlayer)。 - 掩码清除主位集。
- 倒计数类字段(
m_nDontAcceptIKLookAts、m_nHasJustLeftVehicle、m_nIsInCover、m_nSetEntityZFromGround、m_nKnockedByDoor)各减 1——头文件注释解释:为避免设置与复位的先后顺序问题,这类标志设为 2/3 后逐帧递减PedFlags.h:132-137。 - 浮点修正量(动画旋转、移动动画速率、座位间距)恢复默认,并始终置位
SyncDesiredHeadingToCurrentHeading。
ResetPostPhysics 还会给本地玩家重新打开 BF_CanDoDrivebys 战斗行为位,ResetPostPreRender 会清除 SpawnedThisFrameByAmbientPopulation PedFlags.cpp:1075-1113。
维护提示:新增 reset flag 时,除了在
.psc中加枚举,还要决定它属于哪个掩码;不加入任何掩码的位不会被自动清除(推断:依据StaticInit显式逐位登记的写法)。
4. 每帧处理顺序#
4.1 帧级时序(与 ped 相关的部分)#
以下按 CGame 更新组注册顺序和 CGameWorld::Process 内部顺序整理:
| 顺序 | 调用 | 对 ped 的作用 | 证据 |
|---|---|---|---|
| 1 | CPedVariationStream::ProcessStreamRequests、CPedPropsMgr::ProcessStreamRequests、MeshBlendManager::Update(组 “Ped and Vehicle assets”) | 完成变体/道具流送请求,推进头部混合 | src/dev_ng/game/Core/game.cpp:2769-2775 |
| 2 | CPedPopulation::Process、CPedAILodManager::Update(组 “World and Population / Peds”) | 生成/回收;重新分配 AI/动画 LOD | game.cpp:2803-2807 |
| 3 | CScene::Update → CPopCycle::Update → CGameWorld::Process | 人口周期更新后进入世界处理 | game.cpp:2826、src/dev_ng/game/scene/scene.cpp:803-809 |
| 3a | CGameWorld::StartAnimationUpdate | 启动异步动画更新(ped 的 StartAnimUpdate 受动画时间切片控制) | src/dev_ng/game/scene/world/GameWorld.cpp:1858-1861 |
| 3b | CPedGeometryAnalyser::Process | 收集上一帧异步 ped↔ped LOS 结果 | GameWorld.cpp:1873-1874 |
| 3c | CPed::StartAiUpdate | 更新脚步辅助类数据、刷新所有 ped 在 CSpatialArray 中的位置 | GameWorld.cpp:1949、Ped.cpp:6294-6308 |
| 3d | fwSceneUpdate::Update(SU_UPDATE) → SceneUpdateUpdateCb → CPed::ProcessControl(克隆 ped 只跑网络对象的 ProcessControl) | 主 AI/逻辑更新;被冻结的实体走 ProcessFrozen | GameWorld.cpp:1954、GameWorld.cpp:2832-2871 |
| 3e | CRelationshipManager::UpdateRelationshipGroups、EndAnimationUpdate、删除列表处理、CPedGroups::Process | 关系组清理、等待动画完成、编组维护 | GameWorld.cpp:1995-2036 |
| 3f | CPhysics::Update → SceneUpdatePreSimPhysicsUpdateCb → CPed::ProcessPhysics | 每个物理时间片调用;可返回 POSTPONE | GameWorld.cpp:2060-2062、GameWorld.cpp:2912-2950 |
| 3g | CPedScriptedTaskRecord::Process | 汇总脚本任务记录 | GameWorld.cpp:2125-2127 |
| 4 | CPedFactory::ProcessDestroyedPedsCache、CTheScripts::Process、CGameWorld::ProcessAfterAllMovement | 缓存老化(见 6.4)、脚本、移动后处理 | game.cpp:2853、game.cpp:2862、game.cpp:2876 |
| 5 | camManager::Update → CGameWorld::ProcessPedsEarlyAfterCameraUpdate → CPed::ProcessPostCamera | 相机后 IK 请求、可选“即时”AI/动画更新 | game.cpp:2900-2923、GameWorld.cpp:2423-2457 |
| 6 | PreRender 阶段:CPed::PreRender / PostPreRender | LOD 状态、IK、伤害优先级、着色器参数 | Ped.cpp:23301-23420 |
ProcessPedsEarlyAfterCameraUpdate 在多线程动画更新前把动画导演设为“全局锁定”,并把启动了即时动画更新的 ped 原始指针压入数组,注释明确依赖“此间不会删除该 ped”的假设 GameWorld.cpp:2429-2453。整体帧时序的权威描述见 ../01-runtime/game-skeleton-and-main-loop.md 与 ../05-world/world-and-entities.md。
4.2 CPed::ProcessControl 内部顺序#
Ped.cpp:18670-18842:
图中证据:入口与早退 Ped.cpp:18674-18687;时间切片判定 Ped.cpp:18693-18698;AnimStateUpdate 等待并交换缓冲 Ped.cpp:30520-30547;fullUpdate 分支 Ped.cpp:18704-18727;其余步骤 Ped.cpp:18738-18783;末尾调用基类 Ped.cpp:18840-18841。
细节:
- 时间切片:
fullUpdate来自 AI LOD 管理器;TUNE_BOOL(TimesliceProcessControl, true)关闭时所有非智能部分恢复每帧完整更新,而智能部分始终使用原始fullUpdateForIntelligencePed.cpp:18693-18698。 - 跳过 AI:
CPED_RESET_FLAG_SkipAiUpdateProcessControl置位时不调用ProcessControl_IntelligencePed.cpp:18750-18755。 ProcessControl_Intelligence依次:UpdateMovementMode()→CPedMotionData::ProcessPreTaskTrees()→CPedIntelligence::Process()→ProcessPostTaskTrees()→ 重置可查询接口中的脚本任务信息 → 玩家 reset flags 的ResetPostAIPed.cpp:31225-31246。- 低 LOD 早退:
ProcessControl_Animation和ProcessControl_Graphics在AL_LodEventScanning置位(低事件扫描 LOD)时直接返回;ProcessControl_Audio在该 LOD 下不播放脚步Ped.cpp:30584-30597、Ped.cpp:30779-30781。 ProcessControl_Physics处理“被玩家踢/被门撞”前后帧标志、布娃娃状态、NM 足部碰撞历史、玻璃碰撞测试,并在满足条件时请求运动学(kinematic)物理模式Ped.cpp:31003-31076。ProcessFrozen(被冻结时替代 ProcessControl)只推进事件计时、移除无效事件、停语音、处理“特殊网络离开”Ped.cpp:18558-18576;濒死但尚未死亡的 ped 不允许被冻结,以便播放死亡动画Ped.cpp:18578-18588。- “特殊网络离开”:MP 中 ped 在
m_specialNetworkLeaveTimeRemaining内按网络工具给出的曲线淡出 alpha,计时结束后从载具移除、隐藏并标记网络对象删除(或FlagToDestroyWhenNextProcessed)Ped.cpp:30697-30770。
4.3 物理阶段:ProcessPrePhysics / ProcessPhysics#
ProcessPrePhysics:若 ped 使用低 LOD 物理(AL_LodPhysics)或OverridePhysics,直接调用SimulatePhysicsForLowLod(),并把自己从SU_PRESIM_PHYSICS_UPDATE场景更新中移除(这样不会进入ProcessPhysics),只做少量CPhysical::ProcessPrePhysics的必要工作后返回;否则确保加回该场景位再走基类Ped.cpp:19827-19895。ProcessPhysics开头断言低 LOD ped 不会到达这里Ped.cpp:19899-19904。- 单步优化:当
IsPedProcessPhysicsSingleStepActive为真(且未被 reset flag 强制每步),非首时间片只做站立弹簧力(或复用首片缓存的m_CurrentSpringForceZ)然后返回;首片把时间步乘以片数一次性处理Ped.cpp:19917-19956。 - 推迟:站在其它物理体上时返回
PHYSICS_POSTPONE,若地面物理体是网络克隆则返回PHYSICS_POSTPONE2(需要等克隆的网络混合器更新)Ped.cpp:19972-19985;世界侧的推迟队列长度受PreSimControl::MAX_POSTPONED_ENTITIES限制GameWorld.cpp:2922-2950。 - 之后依次:地面移动、参考系速度、
ApplyMovementRequests、克隆的网络混合器ProcessPrePhysics、ProcessBounds(无变化时 AI ped 每 4 帧才处理一次)、任务树ProcessPhysics、睡眠判定、站立弹簧、浮力、期望速度Ped.cpp:20038-20177。
物理/布娃娃的深入内容见 ../09-physics/game-physics-and-ragdoll.md;运动侧见 ped-motion-and-rendering.md。
5. 系统级初始化#
| 入口 | 内容 | 证据 |
|---|---|---|
CGame::CreateFactories | CPedFactory::CreateFactory()、CPedIntelligenceFactory::CreateFactory() | game.cpp:588-594 |
CPed::InitSystem(由 scene.cpp 在物理初始化后调用) | 伤害管理器、纹身装饰管理器、头像管理器、ped 自定义着色器类、CPedGroups::Init、任务序列、CPedResetFlags::StaticInit、CAi::Init、变体流送/道具系统、按池大小(向上对齐到 4)分配 CSpatialArray | Ped.cpp:2215-2247、scene.cpp:659 |
CPed::Init(INIT_SESSION)(gameSkeleton 注册) | 打包/流送变体初始化、动机数据、道具管理器、CRelationshipManager::Init、精度修正数据 | Ped.cpp:2275-2309、game.cpp:2326 |
6. CPedFactory:创建、销毁与玩家 ped#
6.1 工厂单例与池#
CPedFactory 是单例(ms_pInstance)src/dev_ng/game/Peds/PedFactory.h:39-47。构造函数负责初始化多个 ped 相关池:CPed(MAXNOOFPEDS)、CPedTargetting、CTaskInfo(在 flex 内存中,非旧主机为 400 KB 堆)、CChatHelper、TaskSequenceInfo;析构时对称关闭 PedFactory.cpp:117-173。注释给出 TaskInfo 堆的估算:“268 peds × 8 task infos × 平均 90 字节 ≈ 192 kB” PedFactory.cpp:129-135。
6.2 CreatePed(非玩家)#
证据:PedFactory.cpp:219-350、SetUpAsCivilianPed PedFactory.cpp:891-916、SetUpAsCopPed PedFactory.cpp:930-970、SetPedModelMatAndMovement PedFactory.cpp:1031-1048、ConcreteCreatePed PedFactory.cpp:1062-1109。
要点与边界:
- 玩家必须用
CreatePlayerPed,对玩家控制信息调用CreatePed会断言PedFactory.cpp:222-225。注意:无论传入什么控制信息,CreatePed实际都用本地 AI 控制信息构造 pedPedFactory.cpp:237。 - 池满时
ConcreteCreatePed返回 NULL,断言提示“在 common/data/gameconfig.xml 增大 PoolSize”,除非bFailSilentlyIfOutOfPedsPedFactory.cpp:1072-1084。成功创建会累加人口系统的“本周期已创建”计数PedFactory.cpp:1103-1106。 - 注释强调“网络注册必须在对 ped 做任何进一步状态修改之前完成”
PedFactory.cpp:295-308。 - 随机生成(非脚本)的平民会被设为可被激怒,且非执法者禁止使用低优先级座位;随机警察获得
LOADOUT_COP、射速修正 0.3、可逮捕,并改为漫游默认任务PedFactory.cpp:903-912、PedFactory.cpp:943-966、PedFactory.cpp:84。 !__FINAL下全局gbAllowPedGeneration可完全禁止生成PedFactory.cpp:250-255。
其它创建入口:CreatePedFromSource、ClonePed、ClonePedToTarget(克隆外观/伤害/头部混合)PedFactory.h:49-52。
6.3 构造函数与 Init 做了什么#
构造函数 Ped.cpp:1211-1377:
- 设置实体类型
SetTypePed()、可见性类型VIS_ENTITY_PED,重置删除/转换延迟计时。 - 从
CPedModelInfo读取默认ePedType、默认格斗风格、默认徒手武器。 - 用模型信息中的哈希查表绑定:胶囊信息(
CPedCapsuleInfoManager)、组件布料(可为空)、组件集(CPedComponentSetManager)、运动任务数据集、默认任务数据集。缺失时断言。 - 调用
CPed::Init(false, pModelInfo)。
CPed::Init Ped.cpp:1379-1700 依据 CPedComponentSetInfo 按需创建可选组件(库存与武器管理器、面部、布娃娃约束、马匹组件、电话、头盔),然后创建 CPedIntelligence 并安装 4 棵任务树及默认任务:
| 树 | 类型 | 槽位数 | 初始任务 |
|---|---|---|---|
PED_TASK_TREE_PRIMARY | CTaskTreePed | PED_TASK_PRIORITY_MAX(5) | 默认槽 CTaskDoNothing(-1) |
PED_TASK_TREE_MOVEMENT | CTaskTreeMovement | PED_TASK_MOVEMENT_MAX(3) | 默认槽 CTaskMoveStandStill |
PED_TASK_TREE_SECONDARY | CTaskTree | PED_TASK_SECONDARY_MAX(1) | — |
PED_TASK_TREE_MOTION | CTaskTreeMotion | PED_TASK_MOTION_MAX(1) | 由 SetModelId → StartPrimaryMotionTask 安装 |
证据:Ped.cpp:1644-1655、枚举 pedDefines.h:264-307。任务树语义见 ../07-ai-tasks/task-system.md。
Init(bool reinit, ...) 的 reinit 参数用于复用路径:所有 rage_new 都有 !reinit 守卫,复用时断言物理实例已存在 Ped.cpp:1629-1640。
6.4 销毁#
DestroyPed:若有网络对象先NetworkInterface::UnregisterObject(克隆脚本对象被销毁时断言),再DestroyPedInternalPedFactory.cpp:1140-1175。DestroyPedInternal顺序:关闭布料碰撞 → 人口计数(销毁计数 + 从人口计数移除)→ 标记PedBeingDeleted→ 取消脚本注册 → 从本地控制的编组移除 → 清CreatedByFactory→CGameWorld::Remove→ 标记“正在删除的 ped”、让手持道具一并销毁、FlushImmediately终止所有任务 →deletePedFactory.cpp:545-616。- 析构函数断言必须在工厂销毁函数中执行(
__DEV)、必须已离开场景图(否则自行Remove并警告)、不在空间数组中,并在复用池计数上回退模型引用Ped.cpp:1883-1914。
“销毁 ped 缓存”实际已停用。 CPedFactory 仍保留 MAX_DESTROYED_PEDS_CACHED = 30、10 秒过期的缓存数组和 ProcessDestroyedPedsCache(每帧注册调用)PedFactory.h:107-116、PedFactory.cpp:95-98、PedFactory.cpp:1312-1354,但所有“放入缓存/从缓存取出/池满时驱逐”的调用点都包在 #if 0 中:CreatePed 的 CreatePedFromDestroyedCache PedFactory.cpp:239-243、DestroyPedInternal 的 AddDestroyedPedToCache PedFactory.cpp:580-586、DestroyPed 的缓存检查 PedFactory.cpp:1145-1152、ConcreteCreatePed 的 EjectOnePedFromCache PedFactory.cpp:1074-1076。在源码中检索,AddDestroyedPedToCache 再无其他调用者,因此推断该缓存永远为空。实际生效的 ped 复用机制是人口系统的 复用池(CPedPopulation::AddPedToReusePool,见 ped-intelligence-and-population.md),它复用了工厂的 InitCachedPed src/dev_ng/game/Peds/pedpopulation.cpp:3946。
6.5 玩家 ped#
CreatePlayerPed PedFactory.cpp:713-780:
- 若未传入
CPlayerInfo:网络开启时取本地网络玩家的CPlayerInfo,否则rage_new CPlayerInfo;只有在未传入时才注册网络对象(传入意味着网络对象已存在)。 SetUpAsPlayerPed互相挂接CPed::SetPlayerInfo/CPlayerInfo::SetPlayerPed,在可行时建立以玩家为首领的编组(MP 中需要先有网络对象和物理玩家索引),并设置“可在载具中嘲讽”和全自动射击模式PedFactory.cpp:981-1019。- 本地玩家写入
m_pLocalPlayer;覆盖已有本地玩家只允许在“更换玩家模型”时发生,旧玩家关闭第一人称标志;MP 中注册为CLONEALWAYS | SCRIPTOBJECTPedFactory.cpp:749-770。
DestroyPlayerPed 会停用特殊能力、FlushImmediately、注销网络对象;若网络玩家仍拥有该 CPlayerInfo 则不删除它(由网络玩家负责),__BANK 下还检查是否仍有其他 ped 引用该 CPlayerInfo PedFactory.cpp:789-877。玩家模型切换由 CGameWorld 驱动(调用 CreatePlayerPed/DestroyPlayerPed/SetLocalPlayer)GameWorld.cpp:4537、GameWorld.cpp:4668-4672、GameWorld.cpp:5016-5021。
7. 与 CPedModelInfo 的数据交接#
ped 的数据驱动属性来自其模型信息中的一组哈希。构造函数与 SetModelId 把这些哈希解析成指向全局管理器中条目的指针或运行时状态:
| 模型信息字段(getter) | 解析为 | 管理器 / 数据文件 | 证据 |
|---|---|---|---|
GetPedCapsuleHash | m_pCapsuleInfo | CPedCapsuleInfoManager(PED_BOUNDS_FILE) | Ped.cpp:1351-1353、src/dev_ng/game/scene/FileLoader.cpp:1480-1499 |
GetPedComponentClothHash | m_pComponentClothInfo(可空) | CPedComponentClothManager | Ped.cpp:1355-1356 |
GetPedComponentSetHash | m_pComponentSetInfo | CPedComponentSetManager | Ped.cpp:1358-1360 |
GetMotionTaskDataSetHash / GetDefaultTaskDataSetHash | m_pMotionTaskDataSet / m_pDefaultTaskDataSet | CMotionTaskDataManager / CDefaultTaskDataManager | Ped.cpp:1362-1368、FileLoader.cpp:1595-1629 |
GetTaskDataHash | m_pTaskData,并把若干 CTaskFlags 映射为逃跑行为/配置标志 | CTaskDataInfoManager | Ped.cpp:2791-2817 |
GetCombatInfoHash | CCombatBehaviour::InitFromCombatData | CCombatInfoMgr(combatbehaviour.meta) | Ped.cpp:2911-2912 |
个性数据 GetPersonalitySettings | 携带金钱、默认武器装备 | — | Ped.cpp:2922-2951 |
GetHasHDFiles / GetIsHDTxdCapable | m_pedLodState 初值、HAS_HD_TEX_DIST 基础标志 | — | Ped.cpp:2953-2965 |
GetIsStreamedGfx | 选择变体实现(流送 vs 打包) | — | Ped.cpp:2863-2866、Ped.cpp:8988-8995 |
SetModelId(fwModelId, ...) 是外观/物理的真正装配点 Ped.cpp:2837-2990:CEntity::SetModelId → InitPhys() 物理实例 → CreateDrawable() → CreateSkeleton() → IK 管理器初始化 → CreateAnimDirector(流送 ped 不传自定义着色器效果)→ 面部片段集 → SwitchToAnimated → StartPrimaryMotionTask → 决策器、关系组、动机、导航能力、感知 → 战斗信息 → 打包 ped 的着色器设置 → InitHealth → 默认装备 → HD 状态 → 可选 CPedVfx → AddPedInstance(模型信息维护实例列表)。注释指出“模型未加载”会断言 Ped.cpp:2841-2843。
CPedComponentSetInfo 决定可选组件是否存在,字段(全部带 parser 默认值):m_HasFacial、m_HasRagdollConstraints、m_HasInventory、m_HasVfx、m_HasSpeech、m_HasPhone、m_HasHelmet、m_HasShockingEventReponse、m_HasReins、m_IsRidable src/dev_ng/game/Peds/PedComponentSetInfo.psc:4-16。例如动物模型可借此不带库存/电话/头盔组件(推断:依据这些开关的语义;具体哪些模型关闭由数据决定,源码中不可见)。
8. 外观组件与道具(数据层)#
本节只描述数据结构;流送、头部混合与渲染的实现见 ped-motion-and-rendering.md。
8.1 组件槽位#
ePedVarComp 定义 12 个组件槽:HEAD, BERD(面具/胡须), HAIR, UPPR, LOWR, HAND, FEET, TEEF, ACCS, TASK, DECL, JBIB src/dev_ng/game/Peds/rendering/PedVariationDS.h:55-72。组件标志 ePedCompFlags 共 21 位(BULKY、JOB、SUNNY、WET、COLD、NOT_IN_CAR、ARMOURED、各类头盔、HIDE_IN_FIRST_PERSON 等),用于随机选装与游戏逻辑(如雨天警察换雨衣)PedVariationDS.h:28-53、PedVariationDS.h:203。容量上限:每组件纹理变体 MAX_TEX_VARS = 32,模型变体 MAX_DRAWBL_VARS 在 __BANK 为 32、否则 16,玩家为 128,调色板 16 PedVariationDS.h:169-181。
8.2 CPedVariationData#
每个 ped 一份(位于 CPedDrawHandler::m_varData)。核心是按槽位的并行数组:drawable 哈希、cloth 变体数据、drawable 索引(u32)、替代 drawable 索引、纹理索引、调色板索引;另有 LOD 淡入值、“高 LOD 组件”位集与若干渲染属性位(含 alpha/decal/cutout、着色器是否已设置、首个变体是否已设置)PedVariationDS.h:248-413。GetIsHighLOD() 即 m_LodFadeValue > 129,GetIsSuperLOD() 即该值为 0 PedVariationDS.h:281-282。
8.3 变体信息集合与 DLC 叠加#
模型级的变体元数据保存在 CPedVariationInfoCollection 中,它持有多个 CPedVariationInfo(基础 + DLC 包)。全局 drawable/道具索引 = 局部索引 + 排在该 DLC 之前的各 info 的最大数量之和,这就是 DLC 服装扩展的索引方案 PedVariationDS.h:742-800。
8.4 CPed 上的变体入口#
| 接口 | 行为 | 证据 |
|---|---|---|
SetVariation(slot, drawbl, alt, tex, palette, streamFlags, force) | 先经 CGen9ExclusiveAssets::CanCreatePedDrawable 过滤;流送模型走 CPedVariationStream::SetVariation,否则 CPedVariationPack::SetVariation;改 BERD 时重置头盔被击次数 | Ped.cpp:8968-9002 |
SetVarRandom(setFlags, clearFlags, ...) | 交给模型的 CPedVariationInfoCollection::SetVarRandom,可带环境变体的组件限制 | Ped.cpp:8936-8966 |
头部混合 SetHeadBlendData 等 | 仅流送 ped 可用,索引必须 < 256 | Ped.cpp:9330-9351 |
预加载 SetPreloadData / HasPreloadDataFinished | 脚本预加载服装 | ped.h:1966-1971 |
8.5 道具#
道具挂在 13 个锚点之一(ANCHOR_HEAD/EYES/EARS/MOUTH/LEFT_HAND/.../PH_R_HAND),每 ped 最多 MAX_PROPS_PER_PED = 6 个道具,每道具最多 8 种纹理;CPedPropData 以 SinglePropData{anchor, propModelID, propTexID} 数组记录 src/dev_ng/game/Peds/rendering/PedProps.h:41-70、PedProps.h:160-200。
9. 数据驱动配置一览#
| 数据 | 加载者 | 证据 |
|---|---|---|
PED_BOUNDS_FILE(胶囊/bounds,xml) | CFileLoader::LoadCapsuleFile → CPedCapsuleInfoManager::Init | FileLoader.cpp:1480-1499、src/dev_ng/game/Peds/PedCapsule.cpp:44-72 |
PED_HEALTH_FILE(pedhealth.meta) | CHealthConfigInfoManager::Init | FileLoader.cpp:1501-1517 |
| 组件集 / 组件布料(meta) | CPedComponentSetManager::Init / CPedComponentClothManager::Init(支持 Append/Remove Extra,用于 DLC) | src/dev_ng/game/Peds/PedComponentSetInfo.cpp:39-108、PedComponentSetInfo.cpp:192-207 |
| 任务数据 / 导航能力 / IK 设置(meta) | CTaskDataInfoManager / CPedNavCapabilityInfoManager / CPedIKSettingsInfoManager | src/dev_ng/game/Peds/TaskData.cpp:33-77、NavCapabilities.cpp:19-39、PedIKSettings.cpp:82 |
| 健康配置字段 | 默认生命/护甲/耐力、疲劳/受伤/濒死/受创阈值、狗扑倒阈值、子弹致扭动阈值、无敌 | src/dev_ng/game/Peds/HealthConfig.h:18-61 |
| ped 标志 | PedFlagsMeta.psc(代码生成枚举,非运行时数据) | PedFlagsMeta.psc:1-6 |
10. CPlayerInfo:玩家专有数据#
CPlayerInfo : fwExtensibleBase,只挂在玩家 ped 上(IsAPlayerPed() 即 m_pPlayerInfo != NULL)ped.h:1915-1919、src/dev_ng/game/Peds/PlayerInfo.h:315。主要内容:
| 方面 | 说明 | 证据 |
|---|---|---|
| 玩家状态机 | PLAYERSTATE_PLAYING / HASDIED / HASBEENARRESTED / FAILEDMISSION / LEFTGAME / RESPAWN / IN_MP_CUTSCENE | PlayerInfo.h:323-333、PlayerInfo.h:821-822 |
| 身份 | rlGamerInfo m_GamerInfo(presence/peer/id/name) | PlayerInfo.h:528 |
| 通缉 | CWanted m_Wanted,在 ProcessControl 中每帧 Update | PlayerInfo.h:1596、PlayerInfo.cpp:2153;详见 ../20-gameplay/wanted-crime-dispatch.md |
| 玩家瞄准 | CPlayerPedTargeting m_targeting | PlayerInfo.h:1483;见 ped-subsystems.md |
| 玩家 reset flags | CPlayerResetFlags(ResetPreAI/ResetPostAI/ResetPostPhysics) | PlayerInfo.h:117-160、PlayerInfo.cpp:2077 |
| 街机模式信息 | CPlayerArcadeInformation(C&C 队伍/角色) | PlayerInfo.h:1249-1250、PlayerInfo.h:1776 |
| 可调参数 | CPlayerInfo::Tunables(冲刺控制、玩家属性、敌人冲锋、战斗逗留、耐力管理等) | src/dev_ng/game/Peds/PlayerInfo.psc:4-102 |
CPlayerInfo::ProcessControl 由 CPed::ProcessControl_Data 调用(同时调用 ProcessLightEffects、ProcessFPSState)Ped.cpp:30557-30569。对网络玩家只做简单处理后立即返回;对本地玩家断言其人口类型为 POPTYPE_PERMANENT,处理第一人称切换计数、水炮计时、站在警车上报案(每 64 帧检查一次)、通缉更新、武器烹饪计时等 PlayerInfo.cpp:2067-2160。SetupPlayerGroup 在玩家专属编组槽上以 POPTYPE_PERMANENT 激活编组并设本玩家为首领 PlayerInfo.cpp:7008-7018。
11. PedGroup:编组、成员与编队#
11.1 全局编组表#
CPedGroups::ms_groups 是固定数组,容量 = 玩家组 MAX_NUM_PHYSICAL_PLAYERS(32) + 脚本组 16 + 环境组 16 = 64 src/dev_ng/game/PedGroup/PedGroup.h:294-336、src/dev_ng/rage/framework/src/fwnet/nettypes.h:85。AddGroup 按人口类型选择区段 src/dev_ng/game/PedGroup/PedGroup.cpp:1480-1543:
| 人口类型 | 槽位区段 |
|---|---|
POPTYPE_PERMANENT(玩家) | [0, 32) |
POPTYPE_MISSION / REPLAY / TOOL / CACHE | [32, 48) |
POPTYPE_RANDOM_* | [48, 64) |
约束:任务(mission)编组必须带脚本句柄,非任务编组不得带脚本句柄;MP 中仍在仲裁(通知其他玩家已清空)的槽位不能复用 PedGroup.cpp:1485-1531。ped 端以 m_iPedGroupIndex(PEDGROUP_INDEX_NONE = 255)记录所在组 PedGroup.h:166。
11.2 成员关系#
CPedGroupMembership 有 MAX_NUM_MEMBERS = CPedFormation::MAX_FORMATION_SIZE(8) 个槽位,最后一个槽(索引 7)固定是首领 PedGroup.h:78-86、src/dev_ng/game/PedGroup/formation.h:132-135。每个成员槽同时保存 RegdPed 和网络 ObjectId(MP 中 ped 可能尚未在本机存在)PedGroup.h:46-69。最大分离距离 ms_fMaxSeparation 与玩家组版本均为 30 m PedGroup.cpp:79-80。
11.3 每帧维护#
CPedGroups::Process(在 CGameWorld::Process 中、物理更新前调用):__ASSERT 下校验每个成员的 m_iPedGroupIndex;对每个活动组调用 CPedGroup::Process;非永久且本地控制的组在成员数为 0 时移除;MP 中首领变更的组要搬到新槽位(组在网络上以首领网络 ID 标识),搬不动就删除 PedGroup.cpp:1707-1778。
CPedGroupMembership::Process(仅本地控制组)PedGroup.cpp:607-673:
- 移除死亡/濒死成员(玩家首领除外),以及“已分配但实体已不存在”的槽。
- 无首领则
AppointNewLeader:选第一个非克隆成员移到首领槽;调度生成的 SWAT 首领可能不加默认任务PedGroup.cpp:675-708。 - 移除离首领超过最大分离距离的跟随者(
NeverLeavesGroup除外);MP 中玩家首领受伤时移除所有跟随者。 - MP 下
AssignNetworkPedIds。
11.4 编队#
CPedFormationTypes 枚举 9 种编队:LOOSE、两种包围、横排、箭头、V 形、纵列跟随、单人、成对;对脚本只暴露 5 种 SCRIPT_FORMATION_* formation.h:52-79。每种编队是 CPedFormation 的子类,持有 CPedPositioning m_PedPositions[8];编队类型与间距会被序列化进网络同步 formation.h:129-247。FORMATION_FOLLOW_IN_LINE 额外保存首领位置/朝向历史以实现“沿路径跟随” formation.h:366-398。跟随任务本身属于 ../07-ai-tasks/task-catalogue.md。
12. suite/src/pedsafezone:车辆座舱保护区#
尽管名为“ped safe zone”,rage::CPedSafeZone 并不处理行人;它计算车辆模型空间中一个“乘员安全包围盒”,用于让车辆形变在座舱区域衰减,从而保护乘客区域 src/dev_ng/rage/suite/src/pedsafezone/pedsafezone.h:36-87。
- 启用条件:
ADD_PED_SAFE_ZONE_TO_VEHICLES只在 Orbis/Durango/PC 为 1,Xenon/PS3 为 0;注释说明安全区写入顶点色绿通道由 ragebuilder 完成,旧主机来不及改数据pedsafezone.h:23-28。 - 计算:查找最多 18 个座位骨骼(
seat_dside_*、seat_pside_*)的默认平移,取 AABB 并按侧面 0.29、脚部 0.93、下方 0.40、头部 0.86、背部 0.64 米扩展;外层“不安全区”再外扩最大损伤范围;若启用车轮保护,还根据 10 个车轮骨骼到安全区的最远距离放大缓冲src/dev_ng/rage/suite/src/pedsafezone/pedsafezone.cpp:15-227。 - 查询:
GetPedSafeAreaMultiplier在安全区内返回最小值 0.05,在不安全区外返回 1,中间按到安全区距离线性插值pedsafezone.cpp:238-258。 - 使用者:游戏侧
CVehicleDamage持有m_PedSafeZonesrc/dev_ng/game/Vehicles/vehicleDamage.h:430-442;工具侧 ragebuilder 在构建车辆资源时用同一算法写入每顶点损伤缩放src/dev_ng/rage/framework/tools/src/cli/ragebuilder/ragebuilder.cpp:2572、ragebuilder.cpp:2758。头文件注释警告:修改相关常量需重建所有vehicles.rpf数据pedsafezone.h:15-21。车辆损伤系统的权威说明见 ../08-vehicles/vehicle-physics-handling-damage.md。
13. 约束与边界行为#
- 必须走工厂:
CPed构造受保护;__DEV下析构函数断言“只能在工厂销毁函数中删除,否则会给网络 ped 带来问题”Ped.cpp:1887-1889;CPed::Init断言当前位于工厂内部Ped.cpp:1385。 - 延迟删除:世界更新期间不能直接删除实体;
SceneUpdateUpdateCb把带REMOVE_FROM_WORLD的实体放进每帧上限MAX_ENTITIES_TO_DELETE_PER_FRAME的列表,统一在 AI 更新后移除GameWorld.cpp:2819-2830、GameWorld.cpp:2007-2021。 - 销毁时的重入保护:
DestroyPedInternal在FlushImmediately前后设置/校验CGameWorld::SetPedBeingDeleted,并断言 flush 没有把 ped 重新插回场景图PedFactory.cpp:588-605。 - 默认任务不可为空:
__ASSERT下世界更新会对没有默认任务的 ped 断言GameWorld.cpp:2852-2859。 - 克隆 ped:网络克隆不执行
CPed::ProcessControl,只执行其网络对象的ProcessControlGameWorld.cpp:2861-2871。 - HD 状态机已失效:
CPed::Update_HD_Models的整个函数体包在#if 0中Ped.cpp:6141-6210,且源码中再无其他把m_pedLodState设为PLS_HD_REQUESTED/AVAILABLE的代码。因此ProcessControl中按PLS_HD_AVAILABLE统计的HIGH_LOD_PEDS计数推断恒为 0Ped.cpp:18729-18736。HD 纹理改由HAS_HD_TEX_DIST走通用纹理 LOD 路径(推断,依据SetModelId中设置该标志Ped.cpp:2962-2965;纹理 LOD 实现不在本页范围)。
14. 未决问题 / 证据不足#
ProcessPrePhysics、ProcessPostMovement、PreRender的世界侧调用者没有逐一追到具体回调函数行号;本页只给出 ped 侧实现,世界侧细节以 ../05-world/world-and-entities.md 为准。gameconfig.xml中Peds池的实际数值不在源码树中(数据文件),本页无法给出。CTaskInfo堆注释提及的 “268 peds” 与MAX_NUM_TASK_INFOS中 PC/Durango/Orbis 的 268 一致src/dev_ng/game/Peds/QueriableInterface.h:34,这表明新世代池大小约为 268,但仍需数据确认。PlayerPed.cpp/Population.cpp存在于目录中但不在构建工程里(见 ped-subsystems.md 的盘点),其中描述的旧接口(如CPlayerPed、CPopulation)不代表运行时行为。