Ped 智能与人口:CPedIntelligence、AI LOD、关系组与人口生成/回收
本页覆盖两个紧密耦合的主题:
- 单个 ped 的“大脑”容器
CPedIntelligence:它持有任务管理器、事件组/事件处理器、扫描器、感知、目标系统、战斗/逃跑行为、关系组等,并在每帧以确定顺序驱动它们;以及决定“这一帧是否完整更新”的 AI LOD / 时间切片 系统(CPedAILod、CPedAILodManager)。 - ped 的数量从何而来、何时消失:
CPedPopulation(生成调度、回收、复用池、删除队列)、CPopCycle(按区域/时段的人口预算)、CPopZones(人口区域)、CPedPlacement、CWildlifeManager。
任务系统本身(CTask FSM、任务树、事件→响应任务的映射)由 ../07-ai-tasks/task-system.md 与 ../07-ai-tasks/events-and-responses.md 负责,本页只描述 ped 侧的装配与调用时序。
背景与相关页面#
CPed对象模型、每帧顺序、工厂:ped-architecture.md。- 场景点(scenario)与
CScenarioManager:../07-ai-tasks/task-catalogue.md;路径服务器的“环境 ped 生成坐标”后台任务:../07-ai-tasks/navigation-and-pathserver.md。 - 人口流送(
gPopStreaming):../05-world/game-streaming-and-scene-streamer.md;车辆人口:../08-vehicles/vehicle-ai-and-road-network.md。 - 人口密度倍率区域、地图区域:../20-gameplay/world-rules-clock-weather.md;通缉与调度:../20-gameplay/wanted-crime-dispatch.md。
- 网络对象人口管理(
CNetworkObjectPopulationMgr):../17-network-sync/network-object-model.md。
相关源码文件#
| 文件 | 作用 |
|---|---|
src/dev_ng/game/Peds/PedIntelligence.h/.cpp | CPedIntelligence、CPedEventDecisionMaker、CAi |
src/dev_ng/game/Peds/PedIntelligenceFactory.h/.cpp | 智能对象池与工厂 |
src/dev_ng/game/Peds/PedIntelligence/PedAILod.h/.cpp、PedAILodManager.h/.cpp | AI 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/.cpp | CPedPopulation |
src/dev_ng/game/Peds/PopCycle.h/.cpp/.psc | CPopCycle、人口组/日程/配额 |
src/dev_ng/game/Peds/PopZones.h/.cpp | CPopZones |
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 信号);完整更新时依次ResetPreTaskreset flags、同步扫射状态、IKPreTaskUpdate、面部数据前处理、m_taskManager.Process(dt)、面部后处理、清除“任务刚被设置需要更新”标志PedIntelligence.cpp:3114-3175。Process_NearbyEntityLists:在AL_LodEntityScanning未置位时扫描附近载具与 ped;门扫描只在SearchingForDoorsreset 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 |
ProcessPostPhysics | ResetPostPhysics;玩家 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 = 300src/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_missionRelationships.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 |
IsThreatenedBy | HATE / 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-macroBH_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_FILEPedMotivation.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_LodTimesliceIntelligenceUpdate | AI 被时间切片 |
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 名额”裁剪;站在其它物理体上、本地玩家、水中(非鱼)强制高 LOD | 30 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;其余区域由地图文件加载器逐行解析后CreatePopZonesrc/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_WEEKDAYPopCycle.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:
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.2 | PopCycle.cpp:2882-2908 |
| 高速(ped) | 恒为 1(原逻辑被注释) | PopCycle.cpp:2915-2926 |
| 室内 | 玩家所在主房间带 ROOM_REDUCE_PEDS 时为 0.33 | PopCycle.cpp:2948-2966 |
| 通缉 | 2/3/4/5 星分别 0.9/0.7/0.5/0.2;MP 中环境 ped 少于 36 时不低于 0.6 | PopCycle.cpp:3007-3044、PopCycle.cpp:129 |
| 战斗 | 战斗中 ped 8~24 个之间线性下降,最多减半(每 2 秒统计一次) | PopCycle.cpp:3128-3142、PopCycle.cpp:125-128、PopCycle.cpp:2635-2640 |
| 临时上限 | 原为过场动画上限,现恒返回 9999 | PopCycle.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:
- 前置条件:玩家已定位;Replay 控制世界时只维护复用池;暂停或人口禁用时返回
pedpopulation.cpp:839-874。 - 人口控制点
GetPopulationControlPointInfo:取占主导的已渲染相机导演的位置/朝向/速度(__DEV可用调试相机,脚本可临时覆盖)pedpopulation.cpp:1518-1560。 - 启动模式:
ShouldUseStartupMode()或瞬间填充且屏幕基本淡出时使用近距离生成范围、关闭视锥测试pedpopulation.cpp:905、pedpopulation.cpp:997-1002。 - 范围计算:
baseRangeScale = 室内深度缩放 × 全区缩放(0.8) × 全局 LOD 缩放(过场中至少 1)× 飞行器缩放;视野内生成范围随速度最多放大到 2 倍(最小值约 1.2 倍);删除速率随速度最多 22 倍pedpopulation.cpp:924-989。相机回看时,视野外生成带改为与视野内相同,避免趁玩家回头在前方生成pedpopulation.cpp:1005-1012。结果写入ms_CurrentCalculatedParams并构建“钥匙孔”生成形状pedpopulation.cpp:1029-1059。 - 回收:按可见重要性删除/标记、优先删除、按通缉等级保留警察(0~5 星分别保留 0/2/4/6/8/12 名,刚清除通缉 60 s 内按 1 星处理)、清理过多尸体(周围 15 / 全局 30,MP 周围 7)
pedpopulation.cpp:1061-1103、pedpopulation.cpp:523;随后UpdatePedReusePoolpedpopulation.cpp:1107。 - 生成(见 11.1)。
- 收尾:
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 个)。
GeneratePedsAtScenarioPoints(若允许场景 ped)——选点并排队。AddAmbientPedToPopulation(若环境 ped 不足)——选点/选模型并排队。- 在三个“已排队”生成器之间轮转(载具内 → 场景 → 环境),直到本周期生成了一个 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:
- MP:网络人口管理器必须允许再注册一个本地环境 ped。
- 位置来自路径服务器后台线程生成并缓存的坐标(
g_CachedPedGenCoords);主线程每帧把现有 ped 位置(含“静止场景 ped”标志)交给后台任务以避免扎堆pedpopulation.cpp:5685-5718。取坐标时还有一个硬编码 AABB 排除区(注释引用 bug 号)pedpopulation.cpp:5733-5757。 - 坐标校验
IsPedGenerationCoordCurrentlyValid:非创建区域、路径服务器“关闭 ped”区域、视锥外、室内需已加载且深室内(地铁)需有出口门户、MP 中 250 m 内若有 gamer ID 更大的远端玩家则让给对方生成、放置位置无载具/物体碰撞、MP 中不在任何远端玩家视野内(连续失败 200 次后关闭该测试 30 s)pedpopulation.cpp:5874-6048。 - 可选胶囊体碰撞探测(高 1.8 m、半径 0.25 m)
pedpopulation.cpp:5111-5132。 CPopCycle::FindNewPedModelIndex选模型:比较环境与警察缺口;无警察条件(区域无警察、玩家有通缉、禁止随机警察等)时警察缺口归零;平民按人口组百分比与已加载模型挑选,结果带CPopCycleConditions(空中/水中/野生)PopCycle.cpp:1831-1930。- 空中/野生/水生模型改由
CWildlifeManager求生成点pedpopulation.cpp:5145-5187。 - 根位置 = 地面 + 1 m,随机朝向;排队时对模型加引用防止被流送卸载
pedpopulation.cpp:5189-5229。 - 新生成的 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/Max | 50 / 75 m | 视野外生成带 |
ms_addRangeBaseInViewMin/Max | 90 / 130 m | 视野内生成带 |
ms_initAddRangeBaseMin/Max | 1 / 30 m | 启动模式生成带 |
ms_addRangeExtendedScenarioNormal/Extended | 120 / 250 m | 扩展范围场景 |
ms_cullRangeBaseOutOfView/InView | 90 / 140 m | 视野外/内删除距离 |
ms_cullRangeScaleSpecialPeds/Cops | 1.6 | 特殊 ped/警察删除距离倍率 |
ms_removalDelayMsCivRefreshMin/Max | 1500 / 2500 ms | 平民离开视野后的最短删除延迟 |
ms_removalDelayMsEmergencyRefreshMin/Max | 4000 / 6000 ms | 紧急人员删除延迟 |
ms_maxSpeedBasedRemovalRateScale / ms_maxSpeedExpectedMax | 22 / 50 | 速度相关删除速率 |
ms_pedRemovalTimeSliceFactor | 3 | 每帧检查约 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. 其它人口相关模块#
| 模块 | 说明 | 证据 |
|---|---|---|
CPedPlacement | IsPositionClearForPed、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。