《城市生命力》P10|UE5 行人运动:固定步、过街状态与分层更新

系列《城市生命力》· P10 · UE5 逻辑 Demo

P2 分析成熟开放世界如何把行人位姿、交通槽、动画运动表、贴地与群体反应组织成一条运行链。本篇转向 UE5 重建工程,以 SoA 固定步、人行空间图、过街状态机、群组、避让预算和 Motion Frame 完成可运行原型。P7 仍是人口身份、区域与作息的权威;生产角色、动画与材质留给后续表现方案和 L2 落地阶段。

上一篇:《城市生命力》P9|UE5 交通编排:动态路由、容量门控与冲突区预约

对应分析篇:《城市生命力》P2|开放世界的行人运动

导言:行人运动先是数据连续性,之后才是动画

生成 Character、调用导航并播放走路循环,能够快速得到一群移动角色,却仍有四项运行时责任没有归属:远处没有完整角色时的位置保存、过街等待与车辆互锁、拥挤状态下的计算预算,以及动画图消费的权威状态。

P10 的验收 Demo 使用临时盒体显示行人。默认路线由西侧人行道、斑马线连接和东侧人行道组成,行人以双向 PingPong 路线持续穿越。红色方块是一辆循环经过斑马线的车辆危险源;青色表示普通移动,橙色表示请求或等待,洋红表示过街或清空。盒体大小代表表现层级,不代表身份变化。

这张地图不用于证明角色动画完成,而用于检查以下数据链能否持续运行:

图 1:稳定 AgentId 经过分层资格、固定步、交互协调与 Motion Frame 进入可替换表现
观察项 HUD 证据 运行时合同
完整过街循环 Requested / Waiting / Crossing / Clearing / Completed 状态、信号与冲突区占用同步
群组移动 Groups / Members / Regrouping Leader、Formation Slot 与过街同步
摄像机移动 Full / Simulated / Stub 每个 Agent 独立分层并保留身份
固定步负载 Motion Updated / Skipped 1/2/8 步节拍按稳定散列分摊
查询预算 Crosswalk / Avoidance Processed / Deferred 工作量受预算约束,未处理任务可延期
Save / Restore 路线、状态和层级继续 不保存易失盒体,恢复运动连续性
P10 行人运动 Demo 的人行道、斑马线、车辆危险源与运行时 HUD 总览
Demo 实机 1|高位总览同时显示两侧人行道、斑马线、36 个逻辑行人和红色车辆危险源。HUD 记录固定步、过街阶段、群组、表现层级、运动更新与查询预算。

一、从原工程到 UE5:运动事实与表现消费分开

原工程的行人并非都依赖完整角色 Actor 自主 Tick。交通车道槽提供空间与运动依据,行人控制器在集中更新中生成目标变换;动画运动表根据速度、转向与行为选择运动片段;贴地、预测碰撞和角色表示在后续阶段消费这些结果。近景实体可以拥有更完整的动画与交互,远景人群则使用更轻的数据和表现路径。

原工程中的职责边界同样明确:交通槽不是实体,动画片段不是位移权威,远景匿名点不具备稳定身份;受理移动或生成请求也不代表角色已经挂入世界。行人与车辆可以共享道路拓扑和冲突空间,却不共享相同的运动规则。

P10 的重建选择项目自有运行时,不依赖 Mass、MassTraffic 或 ZoneGraph。它没有复刻原工程动画数据库、实体槽或远景点,而是保留六项逻辑合同:

  1. AgentId 与路线状态独立于临时表现;
  2. 批量运动存放在 SoA,固定步统一推进;
  3. 人行空间与过街连接具有显式拓扑;
  4. 车辆危险、行人避让和群组约束以数据贡献合成;
  5. 过街是可保存状态机,不由动画猜测;
  6. 每个身份都输出统一 Motion Frame,表现只读消费。
图 2:从原工程运动职责、运行时合同到 UE5 Motion Frame 模块的对应关系

Demo 的人行空间

配置阶段建立三条逻辑路线:西侧 Sidewalk、Crosswalk Connector 和东侧 Sidewalk,并创建四个 Portal Link。FCyberPedestrianSpaceRuntime::BuildFromRouteGraph 将路线点转换为可查询节点与线段,随后立即执行 Validate。空间构建失败时 Demo 不进入运行状态。

默认 Crosswalk 的两个等待点被放在 (0,-350) 与 (0,350)。每个行人路线包含六个点,从一侧远端接近等待线,穿过 X=0 的道路,再到另一侧远端。奇数编号路线反转,形成双向人流;前三条横向偏移为 -25、0、25 cm,纵向起点按组逐步后移,避免所有身份完全重叠。

当前每个 Agent 的路径在配置时直接展开为点数组。人行空间图用于验证拓扑与查询,但 SoA 路线没有在运行中执行通用图搜索。它证明“运动状态与空间合同可连接”,不代表已完成大世界行人寻路。

原版运行链中哪些状态不能交给动画

原版行人更新会先根据交通槽或导航结果产生目标位姿,再由角色表示、动画运动表和贴地逻辑消费。动作片段可以改变步态、转身和脚部接触,却不能成为世界位置的唯一来源。否则一旦角色降格、动画暂停或实体卸载,路线进度也会随之消失。

P10 将这条边界收紧为三类状态:

状态类别 例子 权威所有者
身份与意图 AgentId、路线、目标点、PingPong 方向 SoA / Population Runtime
空间与交互 过街阶段、队列槽、群组、危险响应 Motion Coordinator
可见消费 盒体大小、颜色、未来动画参数、材质 Presentation

表现层可以对 Transform 做插值,也可以根据速度选择步态,但不能反向修改 CurrentTargetIndex 或把动画播放结束当作过街完成。过街是否完成由行人位置相对入口/出口的进度判断;动画只负责让这个结果看起来自然。

配置阶段先建图,再生成人群。

Demo 配置不是先创建 Agent,再逐个寻找附近道路。它先建立 Route Graph、Crosswalk 和 Portal,验证节点、边、等待点与信号 ID,再根据确定性编号生成 AgentId 与路线。这样可以在任何行人状态写入前拒绝无效空间数据。

若先生成 300 个 Agent,最后才发现 Crosswalk Connector 缺少出口,清理流程需要逐个撤销群组、过街状态和表现绑定。先验证静态空间再建立动态身份,可以把配置失败限制在空状态边界。未来 World Partition 中的局部人行空间加载也应维持类似协议:区域空间先 Ready,人口迁移和表示申请随后发生。

路线坐标与世界坐标的职责。

当前路线点直接使用世界坐标,适合单地图夹具。生产化后更适合保存区域或人行空间局部坐标,并由区域 Transform 转到世界空间。否则整个街区移动、原点重定位或流送实例化时,数千个持久位置都要改写。

无论采用哪种坐标,Motion Frame 必须明确输出空间。P10 当前向表现层输出世界空间的 Location 与 Facing Direction,SoA 中也保存世界位置;这是一项已知简化,不能从当前字段推断跨区坐标协议已经完成。

图 3:Sidewalk、Crosswalk Connector、Portal 与双向 PingPong 路线

二、SoA 固定步:所有身份共享一套运动事实

路线数据按列保存

FCyberPopulationSoAState 将 AgentId、Position、Velocity、Speed、CurrentTargetIndex、PingPongDirection、AgentFixedStepCount 等拆成等长数组;路线点放在扁平数组中,每个 Agent 通过 RouteOffset 和长度访问自己的区间。AgentToArrayIndex 提供稳定身份到当前数组位置的映射。

Demo 默认速度为 105–141 cm/s,按编号循环变化。前 12 个身份从靠近斑马线的位置开始,其余从路线远端开始;Loop Policy 为 PingPong,到达终点后反转目标索引。

SoA 的权威不是数组下标。保存和恢复按 AgentId 查找当前 Index,再写入对应列;如果快照中出现配置后不存在的 AgentId,恢复失败。后续若引入紧凑删除,也必须维护 AgentToArrayIndex,不能让表现组件缓存裸下标。

图 4:SoA 等长列、当前数组位置与稳定 AgentId 映射

固定步先生成更新掩码

每个逻辑步先根据观察者位置和 LOD Policy 为所有 Agent 计算 Requested Tier 与 Applied Tier,再生成 MovementUpdateFlags。SoA 的 AdvanceFixedStepMasked 只推进标记为 1 的行人,其余增加 Skipped 数量。

更新资格分别为 Full 每步一次、Simulated 每两步一次、Stub 每八步一次。

调度不是把同一层所有人集中到某一帧。每个 Agent 使用 Hash(AgentId) + SchedulerFrameIndex 对间隔取模,使同层工作稳定分散。相同身份、相同帧号和相同层级会得到相同更新决定。

当前 SoA 的掩码跳步意味着最低层位置只在采样步改变,不是按累计 8 倍 Delta 一次补足。若底层函数仍使用普通 DeltaSeconds,Stub 的长期平均移动速度会低于 Full。P10 的目标是证明有界更新与身份可观察性,生产远景运动还需采用累计时间、轨迹外推或到达时间模型,不能把“8 步更新一次”自动解释为速度严格等价。

三个层级共享同一调度时钟、AgentId 和状态协议,但不代表每个身份在相同世界时间内推进了相同距离。当前模型中的“共享运动事实”指事实来源一致,不指多频模拟已经获得时间等价。

Demo Actor 的固定步为 0.05 秒。Tick 先把渲染帧时间加入 FixedStepAccumulator,累加器最多保留 4 个逻辑步,即 0.2 秒;随后循环消费完整的 0.05 秒切片。这个上限可以阻止长帧触发无限补步,但超过 0.2 秒的墙钟时间不会被完整追赶。当前验收保证固定步序列可复现,不等同于长卡顿后的墙钟时间严格守恒。

图 5:从固定步累加器、LOD 更新掩码到 Motion Frame 的执行顺序

Locomotion 与交互在不同阶段

Demo 先执行掩码 SoA 路线推进,再调用 AdvanceInteractionAndMotion 处理过街、群组、交通危险、局部避让和统一输出。Locomotion Policy 还把最大路线偏差设为 65 cm,限制局部修正把行人推出原路径走廊。

基础路线先给出候选位置,交互阶段再根据危险和状态修正。这套顺序不是完整的连续碰撞求解器;过大的固定步仍可能先穿过约束再被校正。当前 0.05 秒节拍与低速盒体适合逻辑验收,不能代替生产角色胶囊扫掠。

SoA 保存逐人固定步计数。

全局 Scheduler Frame 只说明系统运行了多少步,不能说明某个 Agent 实际更新了多少次。Full 每步运行,Simulated 每两步运行,Stub 每八步运行;若只保存全局帧号,压力测试无法验证分层节拍是否真正生效。

AgentFixedStepCount 在该身份实际进入运动更新时递增。它与 MovementUpdatedCount / SkippedCount 共同回答两个问题:本步总共做了多少运动工作,以及每个身份长期是否按照预期间隔获得机会。调度散列使用稳定 AgentId,而不是数组下标,因此未来 SoA 紧凑删除改变数组位置时,不会改变该身份的采样相位。

当前跳步模型的已知风险

多频更新通常采用三类时间模型:

  • 累计 Delta:八步后用 8 × Delta 推进一次;
  • 轨迹外推:低频更新权威点,渲染按速度外推;
  • 到达时间:保存路径区间和时间,按绝对时刻采样。

P10 当前掩码直接跳过 SoA 更新,命中采样步时仍传普通 DeltaSeconds。被掩码跳过时,AdvanceFixedStepMasked 还会把该身份的 Velocity 列清零,因此本步 Motion Frame 可能表现为静止。它验证的是工作分摊和状态合同,不保证三个层级的长期平均速度或动画速度连续。远景盒体仍会间歇移动,不能据此判断速度等价已经成立。

图 6:Full、Simulated 与 Stub 按稳定 AgentId 散列分布更新节拍

后续实现应先确定低频层的时间语义,再接入动画。仅用动画插值平滑画面,逻辑位置仍会落后,升格为 Full 时便会出现追赶或瞬移。

固定步内的读写边界。

同一步中,LOD 先读取步前位置生成更新掩码;SoA 推进被选中的身份;交互协调器再读取新位置、Crosswalk State、群组和危险源,输出修正后的 Motion Frame。表现统一在步尾消费,不能在中途读取一半新、一半旧的数组。

未来若将 SoA 更新并行化,需要为交互阶段提供只读快照或双缓冲,避免邻居查询读到正在写入的位置。当前实现运行在受控顺序中,尚未证明多线程数据竞争安全。


三、过街状态机:等待、占用与离开都要显式保存

六个状态描述一次完整过街

当前枚举依次为 Approach、Requested、Waiting、Crossing、Clearing 与 Completed。

头文件为兼容旧命名保留 Leaving = Clearing 别名,因此 HUD 可能同时出现 Clearing/Leaving 口径,但它们不是两个独立枚举值。旧稿中的四阶段 Approach/Waiting/Crossing/Leaving 已不足以描述当前实现。

每个 FCyberPedestrianCrosswalkAgentState 保存 CrosswalkId、原始目的地、入口、出口、等待点、CrossingAlpha、WaitingSeconds、横向偏移、排队距离、队列槽号、完成次数和方向初始化标志。状态机因此可以在表现不存在时独立运行;检查点可以逐字段恢复该 Agent 的过街状态,但不能据此推出完整路口调度事务已经恢复。

图 7:Approach、Requested、Waiting、Crossing、Clearing 与 Completed 六阶段状态机
P10 Demo 中行人在斑马线入口形成 Requested 与 Waiting 队列
Demo 实机 2|橙色行人在入口提交请求并进入等待队列。此时需求已经存在,但行人尚未占用斑马线冲突区。

过街许可同时读取信号和实际冲突

车辆 Gate 的安全合同应为 SignalPhase == VehicleGreen && NoAgentIn(Crossing, Clearing)。Requested 只形成需求,Waiting 持续等待许可;Crossing 和 Clearing 才代表已承诺并实际占用冲突区。

行人状态 提交需求 占用冲突区 对车辆 Gate 的含义
Approach 否 否 不改变车辆许可
Requested 首次形成 否 推动信号转换,不等于已经占用
Waiting 持续存在 否 等待 AllStop 与行人许可
Crossing 已承诺 是 Gate 必须关闭
Clearing 已承诺 是 Gate 必须继续关闭
Completed 否 否 仅可在合法车相下重新开放

Demo 通过 ShouldOpenVehicleGate 统一执行这条合同。步前 Gate 交给本步交互协调器,步后 Gate 决定红色危险源是否推进。行人若在本步进入 Crossing,步后检查会立即关闭 Gate,危险车辆在同一步停止;最后一个 Clearing 身份离开后,Gate 仍在 AllStop 期间保持关闭,直到下一次 VehicleGreen 且冲突区无占用时才恢复。

图 8:VehicleGreen 与无 Crossing/Clearing 占用共同决定车辆 Gate
P10 Demo 在 AllStop 阶段关闭车辆 Gate
Demo 实机 3|信号进入 AllStop 后,HUD 明确显示 GATE STOP;等待者仍在入口,车辆危险源停在停止线外。
P10 Demo 中行人进入 Crossing 的同一固定步关闭车辆 Gate
Demo 实机 4|洋红色行人进入 Crossing 后,步后 Gate 检查在同一固定步阻止车辆推进。车辆不会等到下一帧或下一次信号刷新才停车。

红色车辆危险源只有在 Gate 开放时推进,位置超过 X=1200 后回到 -1200。行人提交过街需求后,路口调度器通过车辆绿灯、AllStop、行人阶段和清空期控制许可。Crossing/Clearing 尚未结束时,车辆不会因为信号计时到期直接恢复。

Demo 危险源是匀速方块,不是 P11 的车辆运动系统。它提供 VehicleId、位置、速度和 260 cm 安全半径,用于验证行人是否能读取移动危险并产生让行或停止。车辆包络、制动和真实碰撞不在 P10 范围内。

强制 Full 保护关键交互

Requested、Waiting、Crossing 和 Clearing 身份被加入 ForceFullAgentIds。LOD Manager 将它们标为 Forced Full,使其每步进入运动更新;表现仍读取 Applied Tier,因此升格到 Near 可能受转换预算延后。过街关键状态不会因为摄像机远离而降到 8 步节拍。

Demo 把 Full 和 Simulated 容量都设为 12,但 Forced Full 不受 MaxFullAgents 拒绝:管理器先把 Forced 身份排到前面,并允许它们越过 Full 容量上限;其余身份再竞争剩余层级。ForcedFullCount 会记录强制数量,转换预算仍可能让 Applied Tier 延后。该设计优先保证关键交互,却没有独立的强制上限;极端拥挤时 Full 数量可以超过 12,当前验收只覆盖默认规模。

当前 Force Full 还把高频运动更新与 Near 表现意图绑定在一起。生产实现应拆分 ForceInteractionUpdate、ForceMotionUpdate 和 ForcePresentationNear,并让 Crossing/Clearing 的安全优先级高于 Requested/Waiting。等待队列需要可靠更新,不等于所有等待者都必须占用近景表现资源。

图 9:当前 Force Full 耦合与生产环境中的交互、运动、表示优先级拆分

活性恢复不是正常通行路径

协调器保存每个身份的上一位置、无进展时间、上一 CrossingAlpha 和恢复次数。HUD 暴露 Waiting Window Refresh、Forced Crosswalk Clear、Stalled Committed 和 Maximum No Progress Seconds。它们用于发现进入承诺状态后长期无进展的行人,并在异常情况下刷新窗口或清理占用。

这些计数应在正常循环中保持受控。强制清空是防止永久死锁的保护分支,不应被当作常规完成方式;若它持续增长,需要检查队列槽、车辆 Gate、目标同步或局部避让,而不是提高超时时间掩盖问题。

队列槽同时决定纵向排队与横向过街线

行人第一次绑定 Crosswalk 时,会根据当前位置选择更近的一侧作为 Entry,另一侧作为 Exit。斑马线宽度再被划分为 1–5 条可用横向线;QueueSlotIndex 对线数取模得到横向 Lane Index,整除结果形成 Queue Row。

横向线决定行人在斑马线内的侧向偏移,Queue Row 则让后来的行人在等待点后方每 75 cm 排开。最终保存的不是一个抽象序号,而是:

EntryPoint + LateralOffset
ExitPoint  + LateralOffset
WaitingPoint = EntryPoint - CrossingDirection × QueueDistance

同一个状态机由此支持双向进入和多排等待。它仍是规则化队列,不处理不同体型、逆行超越或自由站位;相较于共享等待点,这种结构能够验证群组、信号和冲突占用之间的关系。

Requested 与 Waiting 的差别很窄,但不能删除。

Agent 首次到达入口且尚未获得行人绿灯时,从 Approach 进入 Requested;后续仍未获准则进入 Waiting 并累计等待时间。Requested 表示需求刚刚形成,可供调度器在该步建立 Pedestrian Demand;Waiting 表示请求已经存在,身份继续停在分配的队列槽。

两者在表现上都可能使用橙色,在统计中 WaitingCount 也包含 Requested,但保存与调试仍保留独立枚举。如果把 Requested 直接并入 Waiting,便难以判断需求是否重复提交、调度器是否漏掉第一步请求。

Crossing 一旦承诺,就不能被灯色强制拉回

状态机把 Crossing 与 Clearing 视为已承诺过街。即使信号阶段变化,只要 Agent 仍在分配的过街线或保有 Crossing Commitment,就继续朝出口推进。到达 95% 后进入 Clearing,到达出口后进入 Completed。

灯色只决定是否允许新行人进入,不能把已经在路中的行人移回路边。车辆 Gate 读取实际 Crossing/Clearing 占用,并持续关闭到最后一个已承诺身份离开。

Completed 只表示一次 Crosswalk 事务结束,不是 PingPong 路线的永久终态。Agent 离开当前过街入口范围后,状态会回到 Approach;反向路线再次接近斑马线时重新进入 Requested/Waiting。CompletedCrossingCount 累计已完成次数,当前状态仍可为下一次过街重新初始化。

P10 与 P9 调度快照仍有缺口

P10 保存逐人的 Requested、Waiting、Crossing 等状态,却没有保存 P9 式的完整 Intersection Scheduling。恢复后信号回到默认配置,行人状态则回到保存点。如果读档发生在 Crossing 中段,强制占用检查通常能阻止车辆立即开放,但信号相位、请求序号和等待时长不一定与原时刻一致。

生产存档需要把 Crosswalk Agent State 与路口调度快照作为一个事务保存,并在恢复前验证:每个 Requested/Waiting Agent 都存在对应需求;每个 Crossing/Clearing Agent 都登记在正确 Conflict Zone;同区没有活动车辆预约。P10 当前只完成了逐人运动侧,文章必须保留这一缺口。


四、群组、避让与预算:局部关系不能退化为全体两两计算

群组保存 Leader 与 Formation Slot

Demo 将 25% 的行人编入群组,每组最多 3 人。群组运行时选择 Leader,为成员分配 Formation Slot,并报告 GroupCount、MemberCount 与 RegroupingGroupCount。组员接近路口时共享过街阶段,避免 Leader 已通过而成员仍被车辆 Gate 切断。

局部拥挤或路线转折会让成员暂时分离;离开冲突区后进入 Regrouping,成员重新靠近编队槽。群组约束是运动贡献,不直接覆盖 AgentId、路线或过街所有权。成员即使临时分开,仍保留自己的 Motion Frame 和 Crosswalk State。

图 10:群组在接近、共同过街与离开后重组三个阶段中保留 Leader 和 Formation Slot
P10 Demo 中群组同步过街并在离开冲突区后重新编队
Demo 实机 5|群组成员共享过街阶段;局部避让造成短暂分离后,Regrouping 将成员拉回各自 Formation Slot,而 AgentId 与个人路线状态保持不变。

避让以空间候选和有限作业处理

局部避让不对 N 个行人执行完整 N² 比较。协调器使用空间索引筛选邻近候选,并通过 Scratch State 复用临时数组;每步只处理预算允许的 Agent,其余计入 Deferred。结果报告调整人数、处理/延期作业和本步最小间距。

车辆危险是另一类贡献。行人根据危险源位置、速度和安全半径计算 Danger/Yield/Waiting;群组、路线方向、静态约束和车辆反应最终汇入 Locomotion。动画或盒体材质不直接查询附近车辆。

100/300/1000 压力验收给出了采样节流与局部避让预算的直接证据。Demo 以 Near 层调用交互协调器,因此本步局部避让上限为 128 个 Agent;运行时通过 NextLocalAvoidanceAgentIndex 轮转下一批起点。

逻辑身份 运动更新 运动跳过 避让处理 避让延期 Observable Frame
100 28 72 100 0 100
300 52 248 128 172 300
1000 140 860 128 872 1000

运动更新数显著低于总身份数,并符合固定 Full/Simulated 容量与 Stub 八步采样的预期;它仍会随总人数近似线性增长,斜率约由低频层的采样间隔决定,不是与人口规模无关的常量上限。局部避让处理数在本夹具中受 128 上限约束,但空间索引维护、Frame 输出和其他逐人工作仍随人数增长。

表中数字来自 32 个 0.05 秒逻辑步后的确定性采样,不是目标平台性能预算。轮转游标提供了稳定人群中的后续处理机会,但当前压力行没有证明身份持续增删或高密度迁移时不存在长期饥饿。

Crosswalk Query 同样分为处理和延期。

斑马线先构建 1000 cm 网格空间索引。每个 Agent 不需要扫描全部 Crosswalk;查询只访问附近单元,并受每步最大处理数约束。HUD 分别显示 Processed 和 Deferred,避免“没有找到斑马线”和“本步尚未查询”混成一种失败。

当前验收地图只有一个斑马线,空间索引的扩展价值主要由压力测试验证。城市级多路口还需检查单元密度、跨区索引更新和流送边界。

图 11:空间网格筛选邻近候选,并以轮转游标限制本步避让处理量

运动贡献的合成顺序

交互阶段不能让路线、群组和避让分别覆盖速度。更稳定的做法是先生成期望方向,再由各类约束贡献修正:

图 12:路线、群组、行人分离、车辆危险与过街约束按序合成最终速度

Crosswalk Commitment 的优先级高于普通群组回位:已经进入冲突区的成员不能为了回到 Leader 身边而横向离开斑马线。车辆危险可以让未承诺行人等待,却不能将已在路中的行人冻结在车道中央。群组修正与避让也必须受 65 cm 路线走廊限制,防止多个小贡献叠加后把人推到机动车道。

当前原型采用规则式修正,不是速度障碍、RVO 或人群连续介质模型。优先级和输出均可观察,因此替换局部算法时无需改变 AgentId、过街所有权和 Motion Frame。

预算延期必须保留上次可靠状态。

避让作业本步未获得预算时,系统不能把该身份速度清零,也不能使用未初始化结果。它继续沿上一次可靠路线状态运动,或由更高优先级的 Crosswalk/Vehicle Hazard 规则约束;同时将作业计入 Deferred,下一步重新获得处理机会。

压力结果中 300 人延期 172 个避让作业、1000 人延期 872 个,并不意味着这些身份从可观察帧中消失。所有 Agent 仍输出 Frame,区别是本步是否计算了新的局部邻居响应。因此,逻辑身份可以保持完整,昂贵作业仍受预算约束。

空间索引本身也需要预算审计。

网格查询减少了候选数,但网格过大时单元内仍可能堆积大量人;网格过小时,移动身份频繁跨格又会增加维护成本。P10 固定使用 1000 cm 单元,只证明当前夹具和压力行可重复。

城市化后应记录每格人数分布、查询访问格数、候选命中率、更新索引成本和极端聚集区的最大候选量。仅有 Processed/Deferred 还不足以评价空间结构是否适合车站、演出和疏散场景。


五、Motion Frame、表现分层与恢复

每个身份都产生统一可观察帧

交互与运动协调器在步尾生成 Motion Frame,包含 AgentId、Location、Velocity、Speed、Facing Direction、Route Target、行为状态、Group 信息、Crosswalk State、动画参数和 Presentation Tier。表现层再由 Location 与 Facing Direction 构造 Transform。ObservableFrameCount 必须等于逻辑身份数,即使某些 Stub 本步没有运动更新。

Motion Frame 是表现消费者在该固定步中的权威输入,也是由 SoA 与交互协调器派生的只读发布快照。AgentId、路线、位置和速度的持久所有权仍在底层状态;Frame 不是 SaveGame 的唯一事实源,也不作为下一步运动的反向输入。当前 ISM 盒体按状态分到 Moving、Waiting、Crossing 三组,并以大小显示 Full/Simulated/Stub。未来 Skeletal Mesh、AnimInstance 或批量代理都应消费同一 Frame,不从组件 Transform 反写 SoA。

当前 FCyberPedestrianObservableFrame 没有携带 SimulationFrameIndex、SourceAgentFixedStepCount 或 bMotionUpdatedThisStep。因此 ObservableFrameCount == AgentCount 只证明每个身份都有可消费输出,不证明每个身份本步都重新计算了位置、避让或过街结果。生产表现若需要判断数据新鲜度,应在 Frame 合同中补充对应字段。

图 13:SoA 与协调器拥有运动事实,Motion Frame 作为表现层唯一只读输入

当前稿不能宣称动画已经完成。项目已有 Speed、FacingYaw、DesiredYaw、TurnDelta、Direction、Moving、Turning 等绑定包路径,但 P10 验收使用临时盒体;生产骨骼、动画片段、步相、Foot IK 和群体材质尚未接入。

LOD 包含距离滞回、容量和转换预算

默认策略为 Full 进入 25 米、退出 32 米;Simulated 进入 55 米、退出 65 米。不同进入/退出距离形成滞回,减少观察者在边界抖动时反复升降。Demo 将 Full/Simulated 容量各限制为 12,并继承每步最多提升 12、降级 24 的转换预算。

Requested Tier 由距离与 Force Full 得出,Capacity Target 决定同层竞争,Applied Tier 还受转换预算影响。Motion Frame 使用 Applied Tier。HUD 中 Deferred LOD Transition 表示已经请求但本步未完成的转换,不是遗漏身份。

这里的 Presentation Tier 是表示意图,不是 Actor 租约。当前 Demo 适配器直接把它映射为盒体大小;生产环境中最终使用 Skeletal Mesh Actor、ISM、批量代理或不创建可见表示,仍应由 P7 的表示预算和资源管理器决定。

P10 Demo 中 Full、Simulated 与 Stub 三种运动更新层级同屏
Demo 实机 6|三种盒体尺寸对应 Full、Simulated 与 Stub。HUD 同时显示各层数量、Updated/Skipped 和 36 个 Motion Frame,说明分层改变更新成本,不拆分逻辑人口。

快照恢复身份、路线、逐人过街状态与运动调度帧

保存快照版本为 1,记录 FixedStepCount、SoA Scheduler Frame、LOD Scheduler Frame、活性恢复统计、交通危险源,以及每个 Agent 的:

  • AgentId、Position、Velocity、CurrentTargetIndex、PingPongDirection;
  • AgentFixedStepCount 与活性监测字段;
  • LOD Tier;
  • 完整 Crosswalk State、入口/出口/等待点、Alpha、等待时间、排队槽和完成次数。

加载会按快照人数重新 Configure Demo,建立相同的确定性 AgentId 与路线,再逐个按 ID 应用状态。随后恢复两个 Scheduler Frame,使散列调度节拍继续;Navigation PreviousPositions 被同步为恢复位置,避免下一步把加载位移误判为巨大速度。

当前快照没有保存 IntersectionScheduling 本身。Configure 会重建默认信号状态,之后只恢复行人 Crosswalk State 和车辆危险源。验收证明默认夹具中的路线、过街、LOD 和继续运动可以恢复,不能扩张为“路口信号相位与所有公平队列完整恢复”。若保存发生在复杂信号中段,行人状态与新建信号可能暂时不一致,生产存档必须把路口调度快照一并纳入。

完整路口恢复应先预检全量快照,再恢复 P9 的 Intersection Scheduling 和 Conflict Zone 所有权,随后恢复逐人 Crosswalk State,并校验 Requested/Waiting 对应有效 Demand、Crossing/Clearing 对应有效冲突区占用,最后恢复运动调度帧并生成新的 Motion Frame。当前独立夹具没有实现这条跨系统恢复顺序。

图 14:P10 会话快照范围与 100、300、1000 人压力采样边界
P10 Demo 保存包含行人运动与车辆危险源的会话检查点
Demo 实机 7|检查点记录固定步、36 个 Agent 的路线与过街状态、调度帧、LOD 和车辆危险源;临时盒体实例不进入快照。
P10 Demo 恢复行人运动检查点并继续固定步运行
Demo 实机 8|运行到不同状态后恢复检查点,AgentId、位置、路线目标、队列槽、CrossingAlpha、LOD 与危险源回到保存值,随后继续固定步推进。

加载同样先重配当前对象再逐项写入,不是原子事务。无效 AgentId 会在部分配置完成后失败;生产实现需要临时状态和全量预检。

Motion Frame 需要稳定表达动作意图。

表现层真正需要的不是 SoA 的全部内部列,而是一份已经归一化的动作输入。当前 Frame 中的速度、朝向、目标方向、移动/转身状态、群组和过街阶段,可以进一步映射为 AnimInstance 参数:

Motion Frame 动画消费 不应反向修改
Speed / Velocity Idle、Walk、Jog 与播放速率 SoA 速度
Facing / Desired Yaw 转身、方向混合、上半身偏转 路线目标
Crosswalk State 等待、观察、快步离开等状态标签 信号与冲突占用
Group Role Leader/Follower 姿态与回头行为 Formation Slot
Presentation Tier / Representation Intent 骨骼更新率、材质或实例路径 AgentId 与路线

后续动画系统可以把连续速度量化为步态,也可以用 Motion Matching 选择片段;但最终 Root Motion 必须与逻辑位移建立明确协议。若动画根运动成为权威,Stub 或无骨骼表示无法继续相同路线;若完全忽略根运动,脚步又可能滑动。P10 只提供逻辑 Frame,二者的对齐属于表现落地阶段。

分层表示不等于分层身份

Full、Simulated、Stub 只改变更新频率和表现成本。三层共享同一个 AgentId、路线目标、Crosswalk State 和 Group Membership。升格时不生成“另一名行人”,降格时也不删除身份。

P10 的层级需要与 P7 的人口表示租约对齐:P7 决定哪些稳定人口身份需要 Full Actor、简化实例或调试 Marker;P10 为这些身份提供 Motion Frame。两套层级可以映射,但 P10 不应自行 Spawn 新人口。集成后由人口编排器选择身份集合,运动系统更新位置,表示管理器消费 Applied Tier。

恢复后重建表现,不保存盒体。

临时盒体包含 ISM 实例索引、组件引用和颜色分组,这些都是可重建缓存。保存它们会把快照绑定到当前组件顺序;只要某组实例数量变化,旧索引就可能指向另一个 Agent。

恢复流程先按 AgentId 写回逻辑状态,再遍历 Motion Frame 重建 Moving、Waiting、Crossing 等实例组。盒体可以变化,身份映射不能变化。未来换成 Skeletal Mesh Actor 池时也应采用同一原则:保存逻辑状态与必要动画相位,Actor 指针、组件句柄和实例下标由当前世界重新分配。


六、验收与能力边界

人工走查

  1. 打开 PopulationMotion_Demo,以高位视角同时看到两侧人行道、斑马线和红色车辆;
  2. 记录 Requested、Waiting、Crossing、Clearing 到 Completed 的完整周期;
  3. 确认车辆 Gate 在 AllStop、PedestrianCrossing 和 Crossing/Clearing 占用期间关闭,最后一个行人离开后仍等待下一次 VehicleGreen 才开放;
  4. 观察三人组接近、同步过街、分离和 Regrouping;
  5. 移动摄像机,确认盒体大小与 Full/Simulated/Stub 数量渐进变化;
  6. 对照 Motion Updated/Skipped 与查询 Processed/Deferred,确认总 Frame 数始终等于 Agent 数;
  7. 保存后继续到不同阶段,再恢复并观察路线、队列槽与层级继续。
演示视频|46 秒行人运动走查,依次覆盖请求与等待、AllStop、行人过街、同一步车辆停车、冲突区清空、VehicleGreen 重开、群组与分层更新,以及 Checkpoint Save/Restore。实测车辆在重开后由 X=-500 前进到 X=200。

自动化证据

PopulationMotionDemoRuntime 覆盖空间图、固定步路线、过街六状态、群组、车辆危险、LOD、Motion Frame 和恢复后继续,并验证三种信号阶段的 Gate 真值表、本步进入 Crossing 后危险车辆立即停止,以及 Crossing/Clearing → AllStop → VehicleGreen 的恢复顺序。表现质量与地图行验证盒体状态、GameMode、HUD、道路和斑马线接线。PopulationMotionPressure100_300_1000 要求三种规模均保持 AgentId 唯一、Observable Frame 完整、更新节拍和延期预算确定。

P10 初始冻结 tag 为 article4-pedestrian-motion-demo-v1,验收地图 blob 为 ce7c580e82c2ed61c047e03e0b4715fa632dc85f;车辆 Gate 初次收口提交为 0f11f742,出口清空与车辆恢复修复提交为 aa29e247。最终验收结果为 reopenObserved=1/1、crossingEntry=1/1、vehicleOverlap=0、stopLineViolations=0。ClientEditor 编译、CyberPlayableSliceSmoke、PopulationMotionDemoRuntime、PopulationMotionPressure100_300_1000 和 CyberValidationAll 均通过;ValidationAll 为 0 错误、4 条既有预期警告,不能写成“零警告”。

P10 已验证:

  • AgentId、路线和运动状态可以脱离完整 Character 存在;
  • 项目自有人行空间能够描述 Sidewalk、Crosswalk Connector 与 Portal;
  • SoA 按固定步和 LOD 掩码推进,所有身份仍输出 Motion Frame;
  • 过街状态已与车辆 Gate、信号和冲突占用连接,AllStop 与 PedestrianCrossing 均禁止车辆推进;
  • 群组拥有 Leader、Formation Slot、同步过街和 Regrouping;
  • Crosswalk Query 与 Local Avoidance 具有处理/延期预算;
  • 路线、逐人过街状态、层级和调度帧可在会话快照后继续。

它尚未证明城市级通用寻路、复杂地形胶囊碰撞、生产动画、Foot IK、真实角色避让观感、跨区人行空间流送或完整路口调度持久化。P10 的盒体只用于暴露运行时合同。

接入正式角色前还需要哪些验收

正式角色会引入当前盒体没有暴露的问题:胶囊宽度与斑马线槽宽是否一致;转身半径会不会让角色越出路线走廊;不同骨架步幅是否与逻辑速度匹配;Foot IK 是否在路缘改变身体高度;群组成员的动画避让是否产生新的视觉碰撞。

因此下一阶段不能只执行“把 Cube 换成 Skeletal Mesh”。至少应增加:

  • 固定相机下的逻辑轨迹与骨盆轨迹叠加图;
  • 不同步幅角色在相同 Agent 速度下的脚滑测量;
  • 斑马线入口、路缘上下和转向处的胶囊扫掠失败率;
  • Full 与 Simulated 切换时动画相位和朝向误差;
  • Group Regrouping 中的最小视觉间距;
  • 100/300/1000 身份下骨骼更新、动画求值和实例绘制的独立耗时;
  • 读档后首个动画帧是否与恢复 Motion Frame 对齐。

这些验收沿用 P10 的运动合同。若正式角色接入后,路线、过街或身份开始依赖 Actor Tick,则表现层已经越过职责边界,不应据此废弃逻辑原型。

与 P7 和 P9 的数据接缝

P7 提供人口身份、区域、日程和表示租约;P9 提供信号与 Conflict Zone 所有权;P10 负责把这些输入转换为连续位姿:

图 15:P7 提供人口意图,P9 提供路口许可,P10 维护连续位姿并向表现发布 Motion Frame

当前独立地图用确定性 AgentId 和固定路线代替了 P7 的区域日程输入,用内置调度夹具代替 P9 的完整快照。这使 P10 可以独立验收,同时也明确了集成工作:不能把夹具路径当作最终社区日程,不能让两套信号状态同时成为权威。


结语:表现可以替换,Motion Frame 必须保持为唯一表现输入

P10 的交付物不是一组走路动画,而是一条可预算、可恢复的运动数据链。SoA 保存身份与路线,LOD 生成更新掩码,交互协调器处理过街、群组和危险,Motion Frame 把本步结果发布给表现。Frame 是表现的唯一输入,不取代底层状态所有权。完整角色是否存在,不改变 AgentId 和过街所有权。

下一篇 P11 将处理车辆执行层:P8 决定车辆何时进入道路,P9 决定它能否获得下一段空间,P11 则验证车身包络、跟车制动、停止线、碰撞损伤和修复在固定步中的连续执行。


AI 协作复盘

  • AI 参与内容:检索原工程行人运动证据、UE5 人行空间、SoA、过街、群组、LOD、查询预算、Motion Frame、快照与压力测试。
  • 主要修正:将旧四阶段过街口径更新为 Approach/Requested/Waiting/Crossing/Clearing/Completed;指出 Leaving 是 Clearing 别名;区分逻辑更新节拍与速度等价;确认当前快照未保存完整 Intersection Scheduling;车辆 Gate 收紧为 VehicleGreen 且无冲突占用,并补充同一步停止与清空后重开验收。
  • 人工判断边界:没有把盒体表现写成生产动画,也没有把单一斑马线空间索引扩张为城市寻路完成。
  • 核验方式:正文对应 CyberPopulationMotionDemoActor、SoA/Locomotion、PedestrianSpace、Crosswalk、Group、MotionLOD、Observable Runtime、压力行与 Article 4 验收文档。
  • 遗留风险:Stub 跳步的时间补偿需复核,Forced Full 超额策略未覆盖极端人群,快照恢复不是原子事务,信号与公平队列未完整持久化。

《《城市生命力》P10|UE5 行人运动:固定步、过街状态与分层更新》有1条评论

发表评论

了解 AI Native Game Development 的更多信息

立即订阅以继续阅读并访问完整档案。

继续阅读