CD Code Digest Hub /GTA V · v2

Ped 智能与人口:CPedIntelligence、AI LOD、关系组与人口生成/回收

63 分钟阅读 7 个章节 2 张图

本页覆盖两个紧密耦合的主题:

  1. 单个 ped 的“大脑”容器 CPedIntelligence:它持有任务管理器、事件组/事件处理器、扫描器、感知、目标系统、战斗/逃跑行为、关系组等,并在每帧以确定顺序驱动它们;以及决定“这一帧是否完整更新”的 AI LOD / 时间切片 系统(CPedAILod、CPedAILodManager)。
  2. ped 的数量从何而来、何时消失:CPedPopulation(生成调度、回收、复用池、删除队列)、CPopCycle(按区域/时段的人口预算)、CPopZones(人口区域)、CPedPlacement、CWildlifeManager。

任务系统本身(CTask FSM、任务树、事件→响应任务的映射)由 ../07-ai-tasks/task-system.md 与 ../07-ai-tasks/events-and-responses.md 负责,本页只描述 ped 侧的装配与调用时序。

背景与相关页面#

相关源码文件#

文件作用
src/dev_ng/game/Peds/PedIntelligence.h/.cppCPedIntelligence、CPedEventDecisionMaker、CAi
src/dev_ng/game/Peds/PedIntelligenceFactory.h/.cpp智能对象池与工厂
src/dev_ng/game/Peds/PedIntelligence/PedAILod.h/.cpp、PedAILodManager.h/.cppAI LOD 位与全局 LOD/时间切片分配
src/dev_ng/game/Peds/PedIntelligence/PedPerception.h/.cpp视觉/听觉感知
src/dev_ng/game/Peds/PedIntelligence/PedMotivation.h/.cpp/.psc情绪/动机
src/dev_ng/game/Peds/PedEventScanner.h/.cpp每 ped 的事件扫描器集合
src/dev_ng/game/Peds/QueriableInterface.h/.cpp可查询任务状态(含网络克隆任务信息)
src/dev_ng/game/Peds/Relationships.h/.cpp/.psc、RelationshipEnums.h关系组
src/dev_ng/game/Peds/CombatBehaviour.h/.cpp、CombatData.psc、CombatInfo.psc、FleeBehaviour.h、DefensiveArea.h/.cpp、Area.h/.cpp战斗/逃跑行为、防守区域
src/dev_ng/game/Peds/PedTaskRecord.h/.cpp、PedStealth.*、pedshockingevents.*脚本任务记录、潜行、震惊事件记忆
src/dev_ng/game/Peds/pedpopulation.h/.cppCPedPopulation
src/dev_ng/game/Peds/PopCycle.h/.cpp/.pscCPopCycle、人口组/日程/配额
src/dev_ng/game/Peds/PopZones.h/.cppCPopZones
src/dev_ng/game/Peds/pedplacement.h/.cpp放置辅助
src/dev_ng/game/Peds/WildlifeManager.h/.cpp、WildlifeManagerMetadata.psc鸟/鱼/地面野生动物生成点
src/dev_ng/game/Peds/Population.h/.cpp、Gangs.h/.cpp旧代码(Population.cpp 不参与构建,见第 13 节)

第一部分:CPedIntelligence#

1. 组成#

CPedIntelligence 有独立的 fwPool(FW_REGISTER_CLASS_POOL),容量与 ped 池相同:CPedIntelligenceFactory 构造时 InitPool(MAXNOOFPEDS),Create() 只是 rage_new CPedIntelligence(pPed) src/dev_ng/game/Peds/PedIntelligenceFactory.cpp:1-22、src/dev_ng/game/Peds/PedIntelligence.h:148-158。池实例化见 src/dev_ng/game/Peds/PedFactory.cpp:74-80。

主要成员(按值内嵌,除非标为指针)PedIntelligence.h:1027-1214:

概念成员类型职责
任务管理CPedTaskManager m_taskManager(4 棵树)主/次/移动/运动任务树,见 ped-architecture.md 第 6.3 节
事件CEventGroupPed m_eventGroup、CEventHandler m_eventHandler事件队列与响应任务计算
决策器CPedEventDecisionMaker m_PedDecisionMaker每 ped 的决策器 ID 与“响应冷却”记录 PedIntelligence.h:74-142
扫描器CEventScanner m_eventScanner;实体扫描 CVehicleScanner/CPedScanner/CObjectScanner/CDoorScanner生成事件;维护附近实体列表
环境检测CClimbDetector、CDropDownDetector、CPedNavCapabilityInfo攀爬/下落检测、导航能力
目标CPedTargetting* m_pPedTargetting(按需从池分配)威胁列表与最佳目标
掩体CCoverFinder* m_pCoverFinder(按需获取/释放)掩体搜索
行为参数CCombatBehaviour m_combatBehaviour、CFleeBehaviour m_fleeBehaviour、CDefensiveAreaManager战斗/逃跑参数,防守区域
关系m_pRelationshipGroupDefault、m_pRelationshipGroup、缓存的 m_uRelationshipGroupIndex关系组(见第 4 节)
感知/动机/潜行CPedPerception、CPedMotivation、CPedStealth视觉/听觉、情绪、被发现难度
查询CQueriableInterface m_queriableInterface他人/网络可查询的任务状态
指挥RegdOrder m_pOrder、RegdCombatDirector m_pCombatDirector调度命令、战斗导演

构造函数为扫描器各自指定 CExpensiveProcess 类型(PPT_VehicleScanner、PPT_PedScanner 等),即这些扫描受全局“昂贵操作分摊器”节流 PedIntelligence.cpp:115-135(分摊器 CExpensiveProcessDistributer::Update 每帧在世界更新开头调用 src/dev_ng/game/scene/world/GameWorld.cpp:1840)。

2. 每帧处理:CPedIntelligence::Process#

由 CPed::ProcessControl_Intelligence 调用(见架构页 4.2)。顺序 PedIntelligence.cpp:1255-1376:

图中证据:PedIntelligence.cpp:1269-1375。

关键语义:

  • 时间切片下的时间步:非完整更新的帧只累计时间;完整更新时把累计值 fTimeslicedTimeStep 作为时间步传给事件处理与任务更新,然后清零 PedIntelligence.cpp:1269-1272、PedIntelligence.cpp:1372-1375。即被时间切片的 ped 以“大时间步”追赶。
  • 事件扫描每帧都跑(传入 fullUpdate 让扫描器自行裁剪),但事件处理(计算响应任务)只在完整更新时执行,且包在 aiEvent::BeginEventUpdates/EndEventUpdates 中 PedIntelligence.cpp:1298-1326。
  • Process_Tasks:若 TasksNeedProcessMoveSignalCalls 置位,即使非完整更新也对主/运动/次任务树调用 ProcessMoveSignals(避免漏掉 MoVE 信号);完整更新时依次 ResetPreTask reset flags、同步扫射状态、IK PreTaskUpdate、面部数据前处理、m_taskManager.Process(dt)、面部后处理、清除“任务刚被设置需要更新”标志 PedIntelligence.cpp:3114-3175。
  • Process_NearbyEntityLists:在 AL_LodEntityScanning 未置位时扫描附近载具与 ped;门扫描只在 SearchingForDoors reset flag 请求时执行,并据最近门距离(1.5 m)设置 IsNearDoor;物体扫描器只推进计时器 PedIntelligence.cpp:2957-3012。
  • 目标系统按需存在:GetBestTarget() 首次调用时从 CPedTargetting 池分配;UpdateTargetting 返回 false(长时间无活动)时删除,池满时返回 NULL 让 ped 保留默认目标 PedIntelligence.cpp:415-455。

其它阶段回调(由 CPed 在对应阶段按 reset flag 调用):

函数作用证据
ProcessPhysics仅当 ProcessPhysicsTasks* reset flag 置位时,对主/移动/运动树调用任务的 ProcessPhysics(次树不调用);返回“需要保持唤醒”PedIntelligence.cpp:1378-1428
ProcessPostPhysicsResetPostPhysics;玩家 reset flags 物理后复位PedIntelligence.cpp:1434-1469
ProcessPostMovement本地玩家 CPlayerInfo::ProcessPostMovement;非布娃娃时主/运动树的 PostMovement(载具中定位)PedIntelligence.cpp:1471-1487
ProcessPostCamera / ProcessPreRender2 / ProcessPostPreRender相机后、PreRender2、PreRender 后的任务回调PedIntelligence.cpp:1489-1515
InstantTaskUpdate相机后“即时”任务更新(克隆与本地两条路径)PedIntelligence.cpp:2343、src/dev_ng/game/Peds/Ped.cpp:31304-31322

3. 事件扫描器集合#

CEventScanner 内嵌一组专用扫描器 src/dev_ng/game/Peds/PedEventScanner.h:926-1016:载具潜在碰撞、熟人(喜欢/憎恨的 ped,可异步 LOS)、激怒、侵入个人空间、附近火焰、乘客、编组事件、玩家走入、入水、物理(例如站在行驶车顶)、卡住检测、碰撞事件、静止检测(想动却动不了)、震惊事件、健康扫描。多数扫描器继承 CExpensiveProcess(例如熟人扫描器构造时 RegisterSlot())PedEventScanner.h:208-231。扫描生成的事件如何转化为响应任务,见 ../07-ai-tasks/events-and-responses.md。

4. 关系组#

4.1 数据结构#

  • CRelationshipGroup : fwRefAwareBase,池容量 MAX_RELATIONSHIP_GROUPS = 300 src/dev_ng/game/Peds/RelationshipEnums.h:6、src/dev_ng/game/Peds/Relationships.cpp:27。
  • 每个组持有一张稠密关系表 u8 m_relations[300](按对方组的池索引寻址)与每种关系的引用计数 m_relationTypeRef[7] src/dev_ng/game/Peds/Relationships.h:54-72。因此 CheckRelationship 是 O(1) 的数组比较 Relationships.h:75-86。(推断:每组约 300 字节表,全池约 90 KB。)
  • 关系类型 eRelationType:RESPECT(0)、LIKE、IGNORE、DISLIKE、WANTED、HATE、DEAD;注释要求与脚本头 commands_ped.sch 保持一致 RelationshipEnums.h:9-24。归属类型 RT_mission / RT_random。
  • ped 侧在 CPedIntelligence 中保存默认组与实际组,并把组索引另存一份 u16 以减少他人访问时的缓存未命中 PedIntelligence.h:1176-1186。

4.2 加载与生命周期#

  • CRelationshipManager::Init 初始化池并解析 common:/DATA/Relationships.DAT(文本格式:行首为组名切换“当前组”,Hate/Dislike/Like/Respect 行列出目标组;未知组按 RT_random 自动创建)Relationships.cpp:138-144、Relationships.cpp:239-348。由 CPed::Init(INIT_SESSION) 调用 Ped.cpp:2293-2294。
  • AddRelationshipGroup 识别 8 个特殊组并缓存静态指针(PLAYER、COP、CIVMALE、CIVFEMALE、NO_RELATIONSHIP、AGGRESSIVE_INVESTIGATE、ARMY、SECURITY_GUARD)Relationships.cpp:166-232。注意一个与参数语义不一致的实现细节:除 COP、CIVMALE、CIVFEMALE、NO_RELATIONSHIP 外,其余组(包括从 .DAT 以 RT_random 创建的组)都被 SetType(RT_mission) 覆盖;而“对自身 LIKE”的初始化只看传入参数是否为 RT_mission Relationships.cpp:187-222。
  • 清理:每 2 秒遍历池,删除 RT_mission、允许清理(m_bCanBeCleanedUp,默认 false,由脚本设置)且不再被引用的组 Relationships.cpp:356-372;调用点在世界 AI 更新之后 GameWorld.cpp:1995-1996。
  • 按哈希查找组是线性扫描整个池 Relationships.cpp:384-396。

4.3 ped 的默认组与判定#

SetDefaultRelationshipGroup PedIntelligence.cpp:1577-1624:玩家/单机玩家类型 → PLAYER;COP/SWAT → COP;ARMY → ARMY;任务 ped → NO_RELATIONSHIP;否则取 CPedModelInfo::GetRelationshipGroupHash() 对应的组(找不到时回退到当前默认组)。

判定规则证据
IsFriendlyWith非“友好例外”;且(关系表为 RESPECT 或 LIKE,或同一 CPedGroup);否则若双方都未设 IgnorePedTypeForIsFriendlyWith,同为 MEDIC / FIRE / COP 也视为友好PedIntelligence.cpp:1679-1738
IsFriendlyWithDueToScenarios双方都是随机 ped 且最近使用的场景点相距 ≤ 3.5 m(可调)PedIntelligence.cpp:1762-1796
IsThreatenedByHATE / WANTED(可选 DISLIKE;战斗中可由配置标志把 DISLIKE 或“非友好”升级为敌意);MP 中执法 ped 对有通缉的玩家一律视为威胁;最后若同组或友好则取消PedIntelligence.cpp:1798-1852

注释“The relationship groups aren't correctly set up in MP games”说明 MP 下的警察敌意是硬编码补丁 PedIntelligence.cpp:1831-1844。

5. 感知 CPedPerception#

  • 每 ped 的参数(侵入距离、视/听距离、外周视距、方位角/仰角视野、凝视中心角、是否全向感知等)在 SetModelId 阶段由 CPedIntelligence::SetPedPerception 从 CPedPerceptionInfoManager(以模型信息中的感知哈希索引)拷贝 PedIntelligence.cpp:2915-2940;数据结构定义在 CPedModelInfo 侧 src/dev_ng/game/modelinfo/PedModelInfo.h:1373-1395。
  • 静态默认值:感知距离 60 m、外周 5 m、识别 20 m、方位角 ±90°、仰角 −75°~60°、凝视中心 60° src/dev_ng/game/Peds/PedIntelligence/PedPerception.cpp:26-41。
  • ComputeFOVVisibility:把目标转换到视图空间,先比方位角再比仰角;落在“凝视中心”内用聚焦距离判定,否则用外周距离;“高度敏锐”的 ped 方位角视为 ±180° PedPerception.cpp:140-194。
  • 脚本可设置警察感知覆盖(ms_bCopOverridesSet 及一组 ms_fCop*Override)PedPerception.cpp:48-55;__BANK 下 CPed::ProcessControl 每帧可从调试可调项写回这些覆盖 Ped.cpp:18785-18804。
  • 任务 ped 的视/听距离还会被世界更新中的感知回调统一设置 GameWorld.cpp:2899-2910。

6. 战斗、逃跑、防守区域、动机#

  • CCombatBehaviour 保存移动风格(CM_Stationary/Defensive/WillAdvance/WillRetreat)、能力(Poor/Average/Professional)、攻击距离(Near~VeryFar)、丢失目标响应、目标受伤反应、行为位集 CCombatData::BehaviourFlags、一组浮点属性 kAttribFloat*(由 X-macro BH_CC_FLOAT_ATTRIB_LIST 生成)、射速修正与射击模式 src/dev_ng/game/Peds/CombatBehaviour.h:60-257、src/dev_ng/game/Peds/CombatData.psc:5-50。
  • 初值来自 CCombatInfoMgr,加载 common:/data/ai/combatbehaviour.meta,并支持 COMBAT_BEHAVIOUR_OVERRIDE_FILE 覆盖/还原 src/dev_ng/game/Peds/CombatBehaviour.cpp:206-300;CCombatInfoMgr::Init 在 gameSkeleton 注册 src/dev_ng/game/Core/game.cpp:2377。按名称查找为线性扫描 CombatBehaviour.cpp:233-243。
  • CDefensiveAreaManager 管理主防守区域、可选次区域与“当前区域”指针(CDefensiveArea : CArea)src/dev_ng/game/Peds/DefensiveArea.h:10-80。
  • CPedMotivation 维护 5 种状态(HAPPY/FEAR/ANGRY/HUNGRY/TIRED),每帧向默认值衰减,IsAfraid() 以恐惧阈值判定 src/dev_ng/game/Peds/PedIntelligence/PedMotivation.h:27-98;配置来自 MOTIVATIONS_FILE PedMotivation.cpp:255-259。
  • CPedScriptedTaskRecord 跟踪脚本命令从“事件→活动任务→休眠”的阶段,最多 175 条 src/dev_ng/game/Peds/PedTaskRecord.h:14-80。

第二部分:AI LOD 与时间切片#

7. CPedAILod:每 ped 的 LOD 位#

AiLodFlags 每一位置 1 表示该方面处于低 LOD src/dev_ng/game/Peds/PedIntelligence/PedAILod.h:27-47:

位低 LOD 含义
AL_LodEventScanning停止核心事件扫描;CPed 的动画/图形/脚步处理也在此位下早退
AL_LodMotionTask换成简化运动任务 CTaskMotionPedLowLod
AL_LodPhysics不做物理模拟,沿导航网格表面移动(SimulatePhysicsForLowLod)
AL_LodNavigation(预留)降低寻路质量;切换处理代码已被注释掉
AL_LodEntityScanning停止实体扫描
AL_LodRagdollInVehicle载具内不运行 NM 附着任务
AL_LodTimesliceIntelligenceUpdateAI 被时间切片
AL_LodTimesliceAnimUpdate动画被时间切片

三组掩码:m_LodFlags(当前)、m_BlockedLodFlags(每帧重置的“禁止降级”)、m_ForcedLodFlags(强制降级)。默认禁止降级集合:随机 ped 为 MotionTask|Physics|TimesliceIntelligence,任务 ped 与执法 ped 额外禁止 EventScanning PedAILod.h:40-46。也就是说,默认情况下 ped 不允许进入低物理/低运动 LOD,需要任务主动 ClearBlockedLodFlag(例如漫游、场景、威胁响应等任务以及人口生成刚完成时)——源码中有 66 处此类调用,示例 src/dev_ng/game/task/Default/TaskWander.cpp:233、src/dev_ng/game/Peds/pedpopulation.cpp:5250-5253。

SetLodFlags 合并强制位(__BANK 下还合并调试位)后调用 ProcessLodChange:物理位变化 → CPed::SetUsingLowLodPhysics;运动任务位变化 → SetUsingLowLodMotionTask 并设置 0.5 s 的切换冷却;事件扫描位变化 → 清空附近火焰缓存 PedAILod.h:220-231、PedAILod.cpp:78-110。本地玩家不允许改变 LOD PedAILod.cpp:112-121。

8. CPedAILodManager::Update:全局 N-LOD 分配#

在“World and Population / Peds”更新组中、CPedPopulation::Process 之后调用 game.cpp:2803-2807。

评分:每 5 帧(ms_pedAiPriorityBatchFrames)通过 CSpatialArray 计算所有 ped 的距离平方与 AI/动画评分并按 AI 评分排序;栈数组上限 kMaxPeds = 256,断言其 ≥ ped 池大小 src/dev_ng/game/Peds/PedIntelligence/PedAILodManager.cpp:195-213、PedAILodManager.cpp:139、src/dev_ng/game/Peds/PedIntelligence/PedAILodManager.h:22-45、PedAILodManager.cpp:1421-1482。可见 ped 的距离会按全局 LOD 缩放(狙击镜放大时附近 ped 提升 LOD)PedAILodManager.cpp:263-266。

逐 ped 决策(按评分顺序)PedAILodManager.cpp:271-705:

维度高 LOD 条件默认阈值 / 名额
物理 + 实体扫描被禁止降级,或距离 < 物理 LOD 距离;非禁止情况下还受“高 LOD 名额”裁剪;站在其它物理体上、本地玩家、水中(非鱼)强制高 LOD30 m;名额 10
运动任务被禁止降级、在载具中,或距离 < 阈值(屏幕外用更短阈值);切换冷却未结束则跳过屏幕内 30 m / 屏幕外 7.5 m;名额 8
事件扫描被禁止降级或距离 < 阈值30 m;名额 20
载具内布娃娃距离 < 阈值30 m
AI 时间切片全局关闭、被强制/禁止、脚本强制、本地玩家 → 不切片;否则 AI 评分距离 < 阈值且名额未满12 m;名额 5
动画时间切片按动画评分落在哪个频率级别取更新周期(1 = 每帧),相位按排序序号错开见 UpdateAnimTimeslicing

阈值与名额定义 PedAILodManager.cpp:44-99。额外规则:

  • “屏幕内最近的步行 ped”:在总 ped 数 ≤ 60 时(带 4 个的回差),最多 3 个屏幕内步行、非场景、非玩家 ped 可在 60 m 内保持高物理/运动 LOD PedAILodManager.cpp:226-254、PedAILodManager.cpp:309-329。MP 中若该位置碰撞未加载则不给此优待,以避免网络冻结/隐藏造成闪烁 PedAILodManager.cpp:404-431。
  • 从低物理切回高物理前,必须先强制扫描附近物体/载具并通过 IsSafeToSwitchToFullPhysics()(不与最近实体相交)PedAILodManager.cpp:479-503、PedAILod.cpp:123-135。
  • 若某 ped 本帧是被时间切片跳过的,它的禁止位尚未被任务重新设置,因此不改变其 LOD,只把它计入名额(注释解释否则会导致高 LOD 运动任务反复启动)PedAILodManager.cpp:331-349。
  • 每个 ped 处理完后重置默认禁止位(任务/执法/随机三种)、清强制位 PedAILodManager.cpp:683-703。

AI 时间切片窗口:UpdateTimeslicing 从上次停下的池索引继续向后扫描,挑出最多 min(8, 低 LOD 数 / 4)(至少 1)个处于时间切片 LOD 的 ped,记录首尾指针;ShouldDoFullUpdate 用指针区间(含环绕)判断 ped 是否在本帧窗口内——注释说明用指针而非索引是为了避免整数除法 PedAILodManager.cpp:928-1010、PedAILodManager.cpp:721-803。死亡 ped 额外按池索引奇偶(周期倍数 2)再减半更新频率;等待寻路结果或强制即时更新的 ped 总是完整更新。

全局开关来自命令行(-notimeslicepeds、-notimeslicepedanims、-nopedspringsinglestep、-nopedphyssinglestep 等)PedAILodManager.cpp:144-167。

动画时间切片在 ped 侧的效果:CPed::StartAnimUpdate 若 ShouldUpdateAnimsThisFrame() 为假则不启动动画更新,否则用累计的 m_fTimeSinceLastAnimUpdate 作为时间步 Ped.cpp:6719-6750;相位推进在 LOD 管理器中完成 PedAILodManager.cpp:286-307。

低 LOD 物理:SimulatePhysicsForLowLod 把速度更新按池索引分摊到每 4 帧(可见)或每 8 帧(不可见)一次 Ped.cpp:19281-19330。

第三部分:人口#

9. 总体结构#

图中证据:区域查找与日程绑定 src/dev_ng/game/Peds/PopCycle.cpp:1198-1230;配额计算 PopCycle.cpp:2583-2633;流送变化通知 PopCycle.cpp:1162-1171;路径服务器参数与坐标缓存 pedpopulation.cpp:1116-1121;野生动物 pedpopulation.cpp:1164-1165;AddPed 复用/新建分支 pedpopulation.cpp:1847-1907;回收分流 pedpopulation.cpp:3214-3234。

10. 预算:CPopZones → CPopCycle#

10.1 人口区域#

  • CPopZones 维护两个并行数组:CPopZone(名称、文本 ID、泥污参数、VFX 区域、人口范围缩放、“脏乱度”、执法响应延迟、无警察标志、MP 帮派领地等位域)与 CPopZoneSearchInfo(s16 包围盒、类别、启用位、SP/MP 两个日程索引)src/dev_ng/game/Peds/PopZones.h:59-119、PopZones.h:213-229。
  • INIT_BEFORE_MAP_LOADED 时先插入覆盖全世界的 DEFAULT 区域(新世代平台植被纹理索引为 7,旧主机为 2)src/dev_ng/game/Peds/PopZones.cpp:130-179;其余区域由地图文件加载器逐行解析后 CreatePopZone src/dev_ng/game/scene/FileLoader.cpp:3022-3050。
  • FindSmallestForPosition 取包含该点的体积最小区域,带 4 项结果缓存(点移动不超过 min(10 m, 到边缘距离×0.75) 时复用)PopZones.cpp:872-958。

10.2 人口周期#

  • 数据:CPopGroupList(人口组:模型 + 可选环境变体,带标志 POPGROUP_AMBIENT/SCENARIO/RARE/AERIAL/AQUATIC/WILDLIFE/IN_VEHICLE/...)、CPopScheduleList(日程)、CPopAllocation(每格配额:最大环境 ped、最大场景 ped、车辆百分比、停车数、警察占比、组百分比等,默认 25/15/...)src/dev_ng/game/Peds/PopCycle.h:33-110、PopCycle.h:169-229、src/dev_ng/game/Peds/PopCycle.psc:44-66。
  • 每个日程有 POPCYCLE_WEEKSUBDIVISIONS(1) × POPCYCLE_DAYSUBDIVISIONS(12) 个配额格,即每 2 小时一格;周末区分已被注释掉,只剩 POPCYCLE_WEEKDAY PopCycle.h:236-286、PopCycle.cpp:1126-1158。
  • 加载:INIT_AFTER_MAP_LOADED 时加载人口组、日程、区域→日程绑定(zonebind);DLC 通过 CDataFileMount 注册 DLC_POP_GROUPS / POPGRP_FILE_PATCH / POPSCHED_FILE / ZONEBIND_FILE 四类数据文件挂载器 PopCycle.cpp:1015-1068、PopCycle.cpp:175-229。
  • 更新:CPopCycle::Update 由 CScene::Update 在 CGameWorld::Process 之前调用 src/dev_ng/game/scene/scene.cpp:803-809。区域每秒最多重算一次(或移动超过 5 m),MP 用 ZONETYPE_MP 和 MP 日程索引 PopCycle.cpp:1198-1224、PopCycle.cpp:141。时段/区域/日程变化时通知人口流送与车辆人口 PopCycle.cpp:1162-1171。脚本可设置覆盖组与百分比,其余组按比例压缩 PopCycle.cpp:1247-1265。

10.3 密度预算公式#

UpdatePercentages PopCycle.cpp:2583-2633:

text
ambient  = alloc.MaxNumAmbientPeds  × 天气 × 高速 × 室内 × 通缉 × 战斗 × 总环境密度倍率 × gameconfig.AmbientPedsMultiplier%
scenario = alloc.MaxNumScenarioPeds ×        高速 × 室内 × 通缉 × 战斗 × 总环境密度倍率 × gameconfig.ScenarioPedsMultiplier%
二者再 × PopCycleMaxPopulationScaler;若 ambient+scenario > gameconfig.MaxTotalPeds 则按比例缩到上限
cop      = floor(警察占比 × ambient);ambient -= cop 部分

各倍率:

倍率规则证据
天气1 + sqrt(rain) × (0.2 − 1),即大雨时降到 0.2PopCycle.cpp:2882-2908
高速(ped)恒为 1(原逻辑被注释)PopCycle.cpp:2915-2926
室内玩家所在主房间带 ROOM_REDUCE_PEDS 时为 0.33PopCycle.cpp:2948-2966
通缉2/3/4/5 星分别 0.9/0.7/0.5/0.2;MP 中环境 ped 少于 36 时不低于 0.6PopCycle.cpp:3007-3044、PopCycle.cpp:129
战斗战斗中 ped 8~24 个之间线性下降,最多减半(每 2 秒统计一次)PopCycle.cpp:3128-3142、PopCycle.cpp:125-128、PopCycle.cpp:2635-2640
临时上限原为过场动画上限,现恒返回 9999PopCycle.cpp:3172-3198

GET_POPULATION_VALUE(name) 从 CGameConfig 的人口配置读取(带 _Base 值)src/dev_ng/game/control/population.h:42。

人口系统再把预算转成“还需生成多少”:

  • 场景:min(当前最大场景数(室内 +5) × 场景密度倍率, 临时上限) × 内存预算倍率 − 步行场景 ped 数 pedpopulation.cpp:4903-4971。
  • 环境:(最大环境 + 最大警察) − 步行环境 ped 数,再乘脚本密度区域倍率(按相机位置)与内存预算倍率 pedpopulation.cpp:4974-5039。

11. CPedPopulation::Process#

注册在“World and Population / Peds”更新组 game.cpp:2803-2805。流程 pedpopulation.cpp:824-1399:

  1. 前置条件:玩家已定位;Replay 控制世界时只维护复用池;暂停或人口禁用时返回 pedpopulation.cpp:839-874。
  2. 人口控制点 GetPopulationControlPointInfo:取占主导的已渲染相机导演的位置/朝向/速度(__DEV 可用调试相机,脚本可临时覆盖)pedpopulation.cpp:1518-1560。
  3. 启动模式:ShouldUseStartupMode() 或瞬间填充且屏幕基本淡出时使用近距离生成范围、关闭视锥测试 pedpopulation.cpp:905、pedpopulation.cpp:997-1002。
  4. 范围计算:baseRangeScale = 室内深度缩放 × 全区缩放(0.8) × 全局 LOD 缩放(过场中至少 1)× 飞行器缩放;视野内生成范围随速度最多放大到 2 倍(最小值约 1.2 倍);删除速率随速度最多 22 倍 pedpopulation.cpp:924-989。相机回看时,视野外生成带改为与视野内相同,避免趁玩家回头在前方生成 pedpopulation.cpp:1005-1012。结果写入 ms_CurrentCalculatedParams 并构建“钥匙孔”生成形状 pedpopulation.cpp:1029-1059。
  5. 回收:按可见重要性删除/标记、优先删除、按通缉等级保留警察(0~5 星分别保留 0/2/4/6/8/12 名,刚清除通缉 60 s 内按 1 星处理)、清理过多尸体(周围 15 / 全局 30,MP 周围 7)pedpopulation.cpp:1061-1103、pedpopulation.cpp:523;随后 UpdatePedReusePool pedpopulation.cpp:1107。
  6. 生成(见 11.1)。
  7. 收尾:ms_aboveAmbientPedLimit、载具内例外计数、巡逻 henchmen 生成、MP 可见性测试计时、后台复用 ped 动画导演重初始化等 pedpopulation.cpp:1308-1391。

11.1 生成调度(“人口周期”限流)#

生成以人口周期为单位节流:s_TimeSteps += min(dt, maxFrameTime) × (PopulationFrameRate × CyclesPerFrame),每消耗 1 个周期最多生成 1 个 ped;周期数受每帧上限约束;与车辆人口在同一帧都做“补帧”时会错开 pedpopulation.cpp:1168-1197。默认 SP 25 Hz、MP 20 Hz、每帧 1 周期(均可由 gameconfig 覆盖)pedpopulation.cpp:397-403、pedpopulation.cpp:628-647。

每个周期 pedpopulation.cpp:1197-1281:

  1. 本周期尚无销毁时,处理删除队列(每帧默认删 1 个)。
  2. GeneratePedsAtScenarioPoints(若允许场景 ped)——选点并排队。
  3. AddAmbientPedToPopulation(若环境 ped 不足)——选点/选模型并排队。
  4. 在三个“已排队”生成器之间轮转(载具内 → 场景 → 环境),直到本周期生成了一个 ped 或三者都试过;多生成的计数会“溢出”到下个周期,从而阻止后续生成。

队列容量:环境 5、场景 3(必须 > 1,因为需要流送资源的场景 ped 要两段式处理)、载具内 8、删除 32、复用池 15 pedpopulation.cpp:503-523。排队项超过 10 帧(调度生成 30 帧)未能生成会被丢弃(SCHEDULED_PED_TIMEOUT)pedpopulation.cpp:503-505。启动模式/瞬间填充时不排队,直接 AddPed pedpopulation.cpp:5197-5205。

11.2 环境 ped:位置与模型#

AddAmbientPedToPopulation pedpopulation.cpp:5054-5256:

  1. MP:网络人口管理器必须允许再注册一个本地环境 ped。
  2. 位置来自路径服务器后台线程生成并缓存的坐标(g_CachedPedGenCoords);主线程每帧把现有 ped 位置(含“静止场景 ped”标志)交给后台任务以避免扎堆 pedpopulation.cpp:5685-5718。取坐标时还有一个硬编码 AABB 排除区(注释引用 bug 号)pedpopulation.cpp:5733-5757。
  3. 坐标校验 IsPedGenerationCoordCurrentlyValid:非创建区域、路径服务器“关闭 ped”区域、视锥外、室内需已加载且深室内(地铁)需有出口门户、MP 中 250 m 内若有 gamer ID 更大的远端玩家则让给对方生成、放置位置无载具/物体碰撞、MP 中不在任何远端玩家视野内(连续失败 200 次后关闭该测试 30 s)pedpopulation.cpp:5874-6048。
  4. 可选胶囊体碰撞探测(高 1.8 m、半径 0.25 m)pedpopulation.cpp:5111-5132。
  5. CPopCycle::FindNewPedModelIndex 选模型:比较环境与警察缺口;无警察条件(区域无警察、玩家有通缉、禁止随机警察等)时警察缺口归零;平民按人口组百分比与已加载模型挑选,结果带 CPopCycleConditions(空中/水中/野生)PopCycle.cpp:1831-1930。
  6. 空中/野生/水生模型改由 CWildlifeManager 求生成点 pedpopulation.cpp:5145-5187。
  7. 根位置 = 地面 + 1 m,随机朝向;排队时对模型加引用防止被流送卸载 pedpopulation.cpp:5189-5229。
  8. 新生成的 ped:设置默认决策器/关系组/删除范围倍率,置 IsStanding,并强制进入低物理 LOD(清除物理禁止位并设强制位)以避免远处生成时激活物理 pedpopulation.cpp:5242-5253。

AddPed 本体:先查复用池同模型 ped,命中则 ReusePedFromPedReusePool,否则 CPedFactory::CreatePed(..., bFailSilentlyIfOutOfPeds=true);随后决定服装:有 NPC 选择集哈希时应用之,否则 SetVarRandom 并按条件清除组件标志(警察雨天不穿晴天装、19 点后/8 点前不穿晴天装、普通 ped 禁止“仅自行车”装)pedpopulation.cpp:1812-1970。

11.3 场景 ped#

GeneratePedsAtScenarioPoints:MP 中不允许再建本地场景 ped 时直接返回(注释说明这能省下昂贵的“建了又删”开销);要求 ped 池至少剩 10 个空位;预算为 0 时仍可为高优先级场景点留 1 个名额(受场景密度倍率 ≥ 0.5 约束);外部 ped 被冻结时不在室外生成 pedpopulation.cpp:7503-7603、pedpopulation.cpp:386。场景点有效性检查分为 General / ScenarioType / 实体与列车附着 / Interior / ScenarioInfo / Position 六类 src/dev_ng/game/Peds/pedpopulation.h:568-595;缓存最多 768 个场景点 pedpopulation.h:797-798。场景系统本身见 ../07-ai-tasks/task-catalogue.md。

11.4 生成范围常量#

常量默认值含义
ms_addRangeBaseOutOfViewMin/Max50 / 75 m视野外生成带
ms_addRangeBaseInViewMin/Max90 / 130 m视野内生成带
ms_initAddRangeBaseMin/Max1 / 30 m启动模式生成带
ms_addRangeExtendedScenarioNormal/Extended120 / 250 m扩展范围场景
ms_cullRangeBaseOutOfView/InView90 / 140 m视野外/内删除距离
ms_cullRangeScaleSpecialPeds/Cops1.6特殊 ped/警察删除距离倍率
ms_removalDelayMsCivRefreshMin/Max1500 / 2500 ms平民离开视野后的最短删除延迟
ms_removalDelayMsEmergencyRefreshMin/Max4000 / 6000 ms紧急人员删除延迟
ms_maxSpeedBasedRemovalRateScale / ms_maxSpeedExpectedMax22 / 50速度相关删除速率
ms_pedRemovalTimeSliceFactor3每帧检查约 1/3 的 ped

证据:pedpopulation.cpp:290-372。旧的 Population.h 中还定义了另一套 POPULATION_* 常量,但所在 .cpp 不参与构建(见第 13 节)。

12. 回收:删除、复用、删除队列#

12.1 候选判定#

PedsRemoveOrMarkForRemovalOnVisImportance 每帧只检查约 已用槽位 / 3 个 ped,从上次位置继续环形遍历;集群(cluster)中的 ped 由集群自行管理 pedpopulation.cpp:3175-3247。

ShouldRemovePed pedpopulation.cpp:3250-3296:执法 ped 走专门的警察路径;候选还需满足“延迟删除时间已过”(离开视野后计时,速度越快越短),不满足条件时重置计时器。

CanPedBeRemoved 排除:玩家、CanBeDeleted() 为假、在载具中/附着于载具、在复用池中、巡逻 ped、带有效命令且标记不删、有命令且原载具还在、集群载具附近(30 m 内)、网络克隆、编组中非首领的成员 pedpopulation.cpp:4044-4128。

CanRemovePedAtPedsPosition:非删除区域;人口控制点在室外时,深室内(地铁)无出口门户房间里的 ped 直接删除;计算视野内/外删除半径(场景 ped 只有在场景预算剩余 < 5 时才允许在视野外被删,扩展范围场景另有加大;CullExtraFarAway 的 ped ×1.6;再乘每 ped 的删除距离倍率);按XY 平面距离判定:超过视野内半径直接删,超过视野外半径且不在屏幕上则删 pedpopulation.cpp:8980-9087、pedpopulation.cpp:9089-9141。

12.2 复用池#

满足 CanPedBeReused 的候选进入复用池,否则进入删除队列 pedpopulation.cpp:3216-3234。条件:启用复用(MP 可通过 -noReuseInMP 关闭)、frag 缓存未超阈值、(默认)从未被看见、非死亡、非玩家/警察、随机人口、模型未被弃用且仍在合适人口组中、同模型在池中少于 3 个、当前活动任务就是默认任务且无当前事件 pedpopulation.cpp:3777-3851。

AddPedToReusePool 实际做了一次“软销毁”:池满时踢掉最旧的、脱离附着、注销网络对象、删小地图图标、清碰撞历史、FlushImmediately 任务、清扫描器/命令/动机/伤害/装饰、退出编组、删除脚本 GUID、移除 IK 求解器、移除物理并把布娃娃实例移到 DELETED_ENITITY_ZPOS、从人口计数和世界移除、冻结、设置 PedIsInReusePool、离开空间数组 pedpopulation.cpp:3634-3769。

ReusePedFromPedReusePool 反向恢复:解冻、清复用标志、按需重建动画导演、调用 CPedFactory::InitCachedPed、恢复控制信息/所有者/计时器/逮捕与死亡状态/生命值/AI LOD 默认值、再次 FlushImmediately pedpopulation.cpp:3911-3960。

UpdatePedReusePool:池中 ped 存活超过 15 帧(ms_ReusedPedTimeout)后复查,若复用被关闭、MP 禁用、模型变为可丢弃或不再属于合适人口组则真正销毁,否则重置计时 pedpopulation.cpp:3414-3466。

12.3 删除队列#

ProcessPedDeletionQueue 每次最多删除 ms_numPedsToDeletePerFrame(1)个:网络 ped 先尝试把控制权移交给仍在范围内的其他玩家;若 ped 在屏幕上且比入队时记录的视野内删除距离更近则跳过(防止当面消失);否则 CPedFactory::DestroyPed pedpopulation.cpp:3298-3363。FlushPedQueues 清空所有排队并强制删除全部待删 ped 与复用池 pedpopulation.cpp:3372-3393。

12.4 人口计数#

计数分为步行/载具内 × 环境/场景/其他(以及警察/SWAT/紧急人员)若干静态整数 pedpopulation.h:280-303。UpdatePedCountIfNotAlready 借助 ped 上的 m_isInOnFootPedCount/m_isInInVehPedCount 位保证幂等;玩家不计入;载具内环境计数不统计网络克隆 pedpopulation.cpp:2482-2600。CPed::ProcessControl_Population 每次完整更新都调用 UpdatePedCountTracking 以跟随上下车变化 Ped.cpp:30989-31001。

13. 其它人口相关模块#

模块说明证据
CPedPlacementIsPositionClearForPed、IsPositionClearOfCars、FindZCoorForPed(下车、路障警察等放置辅助)src/dev_ng/game/Peds/pedplacement.h:29-38、pedplacement.cpp:35-148
CWildlifeManager鸟(高度图上方)、地面野生动物(地表材质探测)、鱼(水下深度、水面探测)与强制鲨鱼投放的生成点管理;模型是否属于各类由 CPedModelInfo 的“默认在空中/水中/作为野生动物生成”决定;Init/Shutdown 由 CPedPopulation 调用src/dev_ng/game/Peds/WildlifeManager.h:26-120、WildlifeManager.cpp:51-130、pedpopulation.cpp:755
CGangs帮派武器配置;ChooseGangPedModel 实际直接返回 MI_INVALID,旧实现整体被注释src/dev_ng/game/Peds/Gangs.h:34-63、src/dev_ng/game/Peds/Gangs.cpp:120-155
CPopulation(Population.h/.cpp)GTA IV 时代的旧人口类;Population.cpp 不在 game2_lib 工程中(对比 vs_project2_lib/game2_lib_2015.vcxproj 的编译列表),也无其他调用者(Gangs.cpp 中的引用位于注释块内)src/dev_ng/game/Peds/Population.h:43-134

14. 约束与边界行为#

  • 网络优先级:环境 ped 坐标在 250 m 内由 gamer ID 较大的玩家负责生成,避免多端重复生成 pedpopulation.cpp:5939-5960;网络 ped 删除优先尝试移交控制权 pedpopulation.cpp:3314-3320。
  • 主线程独占物理:生成阶段设置 PHLEVEL->SetOnlyMainThreadIsUpdatingPhysics(true) 以跳过部分物理锁 pedpopulation.cpp:1130-1133。
  • 复用池与 fwPool 过滤:复用池中的 ped 仍可被 CPed::GetPool()->GetSlot 枚举(只有 m_bIsInPopulationCache 才会被过滤),因此大量系统显式检查 PedIsInReusePool(AI LOD 管理器、相机、Replay 等)PedAILodManager.cpp:277-278、src/dev_ng/game/control/replay/ReplayInterfacePed.cpp:116。
  • 时间切片的副作用:被切片的 ped 事件处理会延迟,任务以累计时间步更新;设置任务时通过 SetTaskSetNeedIntelligenceUpdate 强制下一帧完整更新 PedAILod.h:87-91。
  • __BANK 诊断:PedPopulationFailureData 统计 45 种生成失败原因并每秒清零 pedpopulation.h:410-516、pedpopulation.cpp:1393-1398。

15. 未决问题 / 证据不足#

  • GeneratePedsFromScenarioPointList、SpawnScheduledScenarioPed、SpawnScheduledPedInVehicle 的内部细节(场景点概率、链式场景、载具场景)未逐行展开。
  • UpdateAnimTimeslicing/ComputeAnimTimeslicingDistances 中动画频率级别如何随帧率自适应(ms_AnimUpdateDesiredMaxNumPerFrame = 45 等)只给出入口,未展开公式 PedAILodManager.cpp:1012、PedAILodManager.cpp:119。
  • Relationships.DAT、popcycle/popgroups/zonebind、combatbehaviour.meta 等数据文件内容不在源码树中,本页未验证实际组名与数值。
  • CPedTargetting 的评分细节放在 ped-subsystems.md;CQueriableInterface 的网络序列化细节属于 ../17-network-sync/sync-trees-and-serialisation.md。