分层半开放 · 触发与门控
Layered Semi-Open · Triggers & Gates我们是分层半开放(layered semi-open):每一层(L0–L7)是一个可自由探索 / 战斗 / 搜刮的半开放沙盒,不是线性走廊。但每层都有几个关键触发点(critical-path triggers)串起这一层的主线——击杀 / 拾取 / 交互 / 到达某些东西才推进下一步,最终解锁下潜到下一层。世界状态记录"队伍这层的进度"。
触发 = 主动推进 Triggers (push)
完成动作即推进:击杀 X / 拾取 Y / 交互 Z / 到达某点 → 写世界状态。这是比"门控"更普遍的推进方式。求值器(§04)已支持 7 种触发条件,ACSObjectiveTriggerVolume 就是"到达型"触发。
门控 = 被动阻挡 Gates (block)
条件没满足就挡着:锁着的门、未解锁路线的电梯。门控读世界状态决定放不放行(§05)。触发 / 门控是一体两面:一个触发点(击杀 boss)写标签 → 一道门控(下潜电梯)读标签放行。
脊柱 The Spine
触发写、门控读,中间隔着世界状态(WorldProgress,共享 + 存档)。装置彼此不直接连线,全靠脊柱间接耦合(§01 / §03)。加载存档回到原处、合作玩家同步同一进度。
主线 vs 开放内容 Critical Path vs Side
每层主线 = 那几个关键触发(必做才下潜);其余半开放空间填充支线 / 搜刮 / 痕迹叙事(SD_48 每层三类空间 + 支线小案)——可探可不探、丰富而不卡进度。
世界状态 = 进度脊柱
WorldProgress — The BackboneACSGameState.WorldProgress 是 服务器权威 · server-authoritative 的复制结构,存着整个 Site 的共享进度。它不按玩家分(钥匙卡这类私有物在背包里);后加入的玩家从共享世界存档读取,全队收敛到同一状态。
| 字段 | 存什么 | 谁读它当门控 |
|---|---|---|
WorldStateTags | 世界标签(如 Power.SectorB.On / Site.FirstDescent) | 门、目标可见性 / 武装、叙事节拍 |
CompletedObjectiveIds / ObjectiveStates | 已完成 / 各步状态 | 门、任务面板、后续目标的前置 |
UnlockedRouteIds | 已解锁的主路线(如 Route_S0_L0_MainElevator) | 电梯层级门禁、门 |
ObjectiveEventIds | 已应用的进度事件Id(幂等去重 + 可追踪) | 求值器(防重复折叠奖励) |
ArchivedSampleIds / AvailableAIMProtocolIds / UnlockedRecipeIds | 样本归档 / AIM 协议 / 配方解锁 | 归档 / 制作 / S0 整备系统 |
S0RequisitionCredits / S0SharedMaterialStock | S0 共享物资 / 信用点 | 整备 / 经济 |
ApplyObjectiveEvent(折叠"完成目标 + 写标签 + 解锁路线 / 配方"为一笔幂等事件)/ CompleteObjective / AddWorldStateTag / RemoveWorldStateTag(断电)/ UnlockRoute。读取:HasWorldStateTag / IsObjectiveCompleted / IsRouteUnlocked。任何改动经 OnWorldProgressChanged 委托广播 → UI / 门 / 叙事即时刷新。一次下潜的流程
First-Descent Loop — Worked Example用 Demo 的第一次下潜循环把门控串起来:每一步玩家做的事,对应一次世界状态读 / 写。蓝=读门控 / 橙=写进度。
门控落在哪 Where Gates Sit
最小循环是 L0 修电梯 → 上行 S0 → 回 L0 备料 → 下潜 L1 → 撤回 S0。电梯是两处关键门控:上行段解锁 Route_S0_L0_MainElevator 后放行(到顶写 Site.ReachedS0);下潜 L1 段读 Access.L1 才放行。回 L0 的备料是「二选一门」:L0_GetKeycard(拿 Keycard_L1)或 L0_GatherMaterials(清剿 EG_L0_Hostiles)任一完成即写 Access.L1,另一条自动归档(求值器 OR 组 L1Access)。可控制台验:CompleteObjective L0_GetKeycard → Access.L1 置位 → 下潜电梯放行;只做 L0_GatherMaterials 同样开。
前置门控现已运行时生效 Prereq Gates Now Live
任务的前置世界标签(工具里蓝色「解锁线」写入的)现由求值器 UCSObjectiveDirectorSubsystem::IsArmed 真正读取:前置没满足,该任务的目标不武装(事件不计数)。故 MQ_L0_Prep 在没拿到 Site.S0_Briefed 前推不动、MQ_Containment_Filtration 在没 Access.L1 前推不动——不再只靠电梯物理挡。武器库门仍可配刷卡(Keycard_*),同挂一根世界状态脊柱。
🔶 循环 A/B/E + OR门 + 前置门控 已建待 PIE ⬜ 污染 C / 收容导演 D / HUD 接真 F + 关卡接线(Vol_S0_Hub/Keycard_L1拾取/EG_L0_Hostiles/电梯标签) · 详见 第一次下潜 · 关卡设计
门控装置一览
Gating Devices所有"挡路 / 推进"的装置,按它读世界状态(当门)还是写世界状态(推进)归类。形态不同,全挂同一根脊柱。
| 装置 | 类 | 读什么(当门控) | 写什么(推进) |
|---|---|---|---|
| 门 | ACSDoorActor 新 | 解锁条件:标签 / 目标 / 路线 / 钥匙卡 | 首次开门:WorldTagOnFirstOpen / ObjectiveOnFirstOpen |
| 总闸 / 拉杆 | ACSWorldStateSwitch 新 | (可选)需物品(保险丝 / 电池) | AddWorldStateTag(通电)/ RemoveWorldStateTag(断电) |
| 电梯 | ACSStreamingElevatorStation | RequiredRouteId + RequiredWorldStateTags(按层) | CompleteTravel → Site.FirstDescent / Site.ReturnedToS0 |
| 目标触发体 | ACSObjectiveTriggerVolume | — | 进入 → NotifyReachVolume + 盖复活检查点 |
| 任务目标 | UCSObjectiveDirectorSubsystem | 可见性 RevealWorldStateTags / 武装 PrereqWorldStateTags | 条件达成 → ApplyObjectiveEvent 折叠奖励 |
| 钥匙卡 / 物品 | UCSInventoryComponent(玩家私有) | 门的 Keycard 条件 | 拾取 → NotifyCollect |
| 容器 / NPC | ACSLootContainer / ACSNarrativeNPC | — | 首搜 Site.FirstSearch / 谈完 SetTagsOnEnd |
状态怎么前进(写)
Advancing State — The Write Side世界状态前进的主干 = 玩法事件 → 任务求值器 → 折叠奖励;另有几条"直接写"的旁路(开关 / 门首开 / 电梯 / 容器 / 对话)。全在服务端、零 RPC。
主干:任务求值器 Quest-C
UCSObjectiveDirectorSubsystem(World 子系统,仅权威、无复制)按目标的 ConditionType 收玩法事件、达阈值即 ApplyObjectiveEvent 自动完成并折叠奖励(写标签 / 解锁路线 / 配方)。七种条件:
| ConditionType | ConditionKey | 谁喂事件 |
|---|---|---|
Interact | 交互物的 Obj.<Tag> | 交互 → InteractOnServer 扫 actor tag → NotifyInteract |
CollectItem | 物品 ItemId | 拾取 → AddItem → NotifyCollect |
KillCount | 遭遇组 EncounterGroupId | 敌人 HandleDeath → NotifyKill |
ReachVolume | 触发体 VolumeId | ACSObjectiveTriggerVolume → NotifyReachVolume |
RepairCount / HoldTimer | 收容 EncounterId | 收容导演 → NotifyRepairProgress / NotifyHoldComplete |
WorldStateTag | 世界标签 | 标签置位且本目标已武装即完成(被动重算) |
Manual | — | 控制台 / GM / 脚本(向后兼容缺省) |
旁路:直接写标签的装置 Direct Writers
- 总闸
ACSWorldStateSwitch→AddWorldStateTag(Power.X.On)(通电)。 - 门首开
ACSDoorActor→WorldTagOnFirstOpen/ObjectiveOnFirstOpen(开门驱动剧情)。 - 电梯
CompleteTravel→Site.FirstDescent/Site.ReturnedToS0。 - 容器首搜 →
Site.FirstSearch;NPC 谈完 → 会话表SetTagsOnEnd。
ApplyObjectiveEvent 按 EventId 去重(ObjectiveEventIds.Contains → 已应用直接返回),所以重复触发 / 多客户端不会把奖励折叠两次。状态怎么挡路(读)
Gating on State — The Read Side门控读世界状态决定放不放行。读取在服务端裁决,但因为世界状态复制到所有客户端、玩家背包对 owner 复制,客户端本机也能算出"开 / 锁 + 缺什么",提示无需等服务器回包。
门:四种条件 Door Conditions
ACSDoorActor.UnlockRequirements 一串 FCSDoorRequirement(WorldStateTag / ObjectiveComplete / RouteUnlocked / Keycard),全满足才开。详见下方门小节。
电梯:按层门禁 Elevator
CanAccessLayer 读目标层的 RequiredRouteId + RequiredWorldStateTags;不满足返失败原因("route X required")。与门同一套世界状态读法。
目标:可见 / 武装 Objective Gating
Hidden 类目标在 RevealWorldStateTags 任一置位时才在任务面板现身;目标按 PrereqWorldStateTags 武装后才参与自动完成——即任务链本身也被世界状态门控。
反馈:锁灯 + 提示 Feedback
门锁灯红 / 绿(共享门控),按 E 锁着时把具体原因推给玩家("需要红卡 / 先通电")。"按了没反应、也不知道原因"是门控最让玩家受挫的体验,刻意避免。
门 · ACSDoorActor 细节 Door Detail
玩家按 E → UCSInteractionComponent 找焦点 → 客户端 ServerInteract RPC → 服务器 Interact_Implementation:开着→关;关着→CanOpenNow 求值,满足则开(扣一次性卡 + 破封 + 首开副作用),否则推送原因。复制态 bIsOpen / bIsUnlocked(OnRep_DoorVisual 驱动门扇摆动 + 锁灯)。
- 锁存
bLatchUnlock(默认 true=破封后永久通行;false=每次重验,配掉电门 / 每次刷卡)。自动关bAutoClose+AutoCloseDelay。 - 钥匙卡
bConsumeOnUse=开门即从背包RemoveItem(一次性钥匙)。 - 即时刷新:门订阅
OnWorldProgressChanged→ 通电 / 解锁那一刻锁灯 + 提示立即更新(门不自开,仍需按 E)。 - 默认网格取门交互包
SM_DoorFrame/SM_MetalDoor/SM_CardLock(编辑器可换);开关默认SM_Lever。
类 · 数据 · 接线
Classes · Data · Wiring脊柱 Spine
ACSGameState(WorldProgress + 写 / 读 API + OnWorldProgressChanged)。新增 RemoveWorldStateTag(断电 / 反复开关)。
求值器 Director
UCSObjectiveDirectorSubsystem(Quest-C,七条件 + Notify* + 折叠奖励)。静态定义 Quests.csv / Objectives.csv → UCSItemDataSubsystem。
门控装置 Gates
ACSDoorActor / ACSWorldStateSwitch(新,World/)· ACSStreamingElevatorStation · ACSObjectiveTriggerVolume · ACSLootContainer · ACSNarrativeNPC,各实现 ICSInteractable。
接线约定 Wiring
编辑器给交互物加 Obj.<Key> actor tag 即挂目标;门 / 开关 / 触发体的世界状态字段在细节面板填,或写进 Scripts/CreateL0Blockout.py 摆放时设。命名沿用 Power.* / Site.* / Obj.*。
行业对照
Convention — How Progression Gating Is Normally Done"关卡推进门控正常怎么做"——业界主流就是一套世界状态 / 旗标系统 + 读它的门控装置,正是本系统的形态。条件实现的轻重分四档:
| 档 | 做法 | 谁这么做 / 评价 |
|---|---|---|
| ① | 每门 / 每关硬编码 bool、关卡蓝图连线 | 入门,不扩展、难存档、跨流送断线 |
| ② | 世界状态 / 旗标系统 + 数据驱动门控(门读条件列表、装置写标签) | ← 我们这档。沉浸式模拟 / 类银河城 / 生存的主流(Source I/O、各类任务旗标系统)。可存档、多对多、解耦 |
| ③ | ②的键用 GameplayTag + 标签查询(FGameplayTagQuery) | UE5 官方推荐写法,Lyra 走这条;带可视化查询编辑器 |
| ④ | GAS:推进 / 门控做成能力 + GameplayEffect 标签 | 大型项目;对本作过度设计 |
术语表
Glossary| 术语 | English | 含义 |
|---|---|---|
| 关卡流程 | level flow / progression | 玩家从入口往深处推进的路径,被一串门控分段 |
| 门控 | gate | 挡路装置;读世界状态决定放不放行(门 / 电梯 / 目标 / 卡) |
| 世界状态 / 进度 | world state / WorldProgress | 共享 + 复制 + 存档的"队伍进度记忆",门控的脊柱 |
| 进度事件 | objective event | ApplyObjectiveEvent 一笔:折叠"完成目标 + 写标签 + 解锁",按 EventId 幂等 |
| 任务求值器 | objective director | 按条件类型收玩法事件、达阈值自动完成目标的权威子系统(Quest-C) |
| 解锁条件 | unlock requirement | 门的 FCSDoorRequirement;标签 / 目标 / 路线 / 钥匙卡,全满足才开 |
| 破封锁存 | latch unlock | 门满足条件开过一次后永久通行 |
| 路线 | route | UnlockedRouteIds 项;电梯层级门禁读它(如 Route_S0_L0_MainElevator) |
| 世界进度委托 | OnWorldProgressChanged | 世界状态变化广播;门 / UI / 叙事订阅它即时刷新(非每帧轮询) |
| 服务器权威 | server-authoritative | 推进 / 门控真值只在服务器改,客户端发请求 + 收复制结果 |
状态图例 & 调参
Status Legend & Tuning调参入口 Tuning Entry Points
- 任务链 / 目标条件 / 奖励(可视化):用 任务流程节点编辑器 拖节点连线 → 导出增量补丁 →
python Scripts\ApplyQuestGraph.py QuestPatch.jsonupsert 进Quests.csv/Objectives.csv→python Scripts\GenerateGameData.py。也可直接手改两张 CSV。给交互物加Obj.<Key>tag 即挂 Interact 目标。 - 门:
ACSDoorActor实例的UnlockRequirements/bLatchUnlock/bAutoClose/WorldTagOnFirstOpen。 - 开关 / 总闸:
ACSWorldStateSwitch的WorldStateTag/bOneShot/RequiredItemId。 - 电梯门禁:
LayerDefinitions的RequiredRouteId/RequiredWorldStateTags。 - 触发体 / 检查点:
ACSObjectiveTriggerVolume的VolumeId/bStampCheckpoint。 - 关卡里批量摆门 / 开关:写进
Scripts/CreateL0Blockout.py(见 L0 灰盒生成)。
Core/CSGameState · World/CSObjectiveDirectorSubsystem · CSDoorActor · CSWorldStateSwitch · CSStreamingElevatorStation · CSObjectiveTriggerVolume 现状,单元状态以 DevelopmentUnits.md 为准。类对类地图见 UE_CodeArchitecture.md · World。Demo 循环见 第一次下潜;任务系统见 系统设计总览。