234 lines
14 KiB
Markdown
234 lines
14 KiB
Markdown
|
|
# 交互系统技术参考(条件 / 消耗 / 解锁 / UI 提示 / 指向高亮)
|
|||
|
|
|
|||
|
|
本文档用于汇总当前工程内“交互系统”的落地实现与推荐配置方式,便于后续扩展与排错。
|
|||
|
|
|
|||
|
|
## 1. 总览
|
|||
|
|
|
|||
|
|
系统目标:
|
|||
|
|
- 交互行为(开门/机关/拾取等)继续由 `IInteractable.Interact()` 负责
|
|||
|
|
- “能不能交互 / 失败原因 / 成功后消耗 / 成功后解锁 / 解锁后外观变化”由可配置的组件承担
|
|||
|
|
- 玩家侧在调用 `Interact()` 前统一做条件校验,并把失败原因用于 UI/日志
|
|||
|
|
- 玩家指向可交互对象时,提供 UI 提示与外轮廓高亮反馈
|
|||
|
|
|
|||
|
|
核心入口:
|
|||
|
|
- 玩家按键交互调用链:[PlayerController.PerformInteract](file:///e:/Unity/Unity%20Program/Test_2022/Assets/_Project/Scripts/Player/PlayerController.cs#L367-L459)
|
|||
|
|
|
|||
|
|
## 2. 交互调用链(时序)
|
|||
|
|
|
|||
|
|
1) 玩家 Raycast 命中交互层物体
|
|||
|
|
2) 收集命中 collider 与父物体上的 `IInteractable`(可多个)
|
|||
|
|
3) 在调用 `Interact()` 前,获取父级所有 `InteractConditionBehaviour` 并逐一 `CanInteract(interactor, out failReason)`
|
|||
|
|
- 任一失败:中止,不调用 `Interact()`,可把 `failReason` 展示到 UI
|
|||
|
|
4) 全部通过:依次调用每个 `IInteractable.Interact()`
|
|||
|
|
5) 交互完成后:逐一回调 `OnInteractSucceeded(interactor)`(用于消耗、解锁、触发联动)
|
|||
|
|
|
|||
|
|
条件基类与接口:
|
|||
|
|
- [IInteractCondition.cs](file:///e:/Unity/Unity%20Program/Test_2022/Assets/_Project/Scripts/Interaction/Conditions/IInteractCondition.cs)
|
|||
|
|
- [InteractConditionBehaviour.cs](file:///e:/Unity/Unity%20Program/Test_2022/Assets/_Project/Scripts/Interaction/Conditions/InteractConditionBehaviour.cs)
|
|||
|
|
|
|||
|
|
## 3. 条件 / 消耗 / 解锁(组件清单)
|
|||
|
|
|
|||
|
|
### 3.1 道具需求 + 可选消耗 + 可选一次解锁
|
|||
|
|
|
|||
|
|
组件:[RequireItemsInteractCondition](file:///e:/Unity/Unity%20Program/Test_2022/Assets/_Project/Scripts/Interaction/Conditions/RequireItemsInteractCondition.cs)
|
|||
|
|
|
|||
|
|
用途:
|
|||
|
|
- 单道具门禁(配置 1 条 requirement)
|
|||
|
|
- 多道具 AND(RequireAll)
|
|||
|
|
- 多道具 OR(RequireAny)
|
|||
|
|
- 成功后消耗(consumeOnSuccess)
|
|||
|
|
- 成功后一次解锁(unlockAfterSuccess)
|
|||
|
|
|
|||
|
|
关键语义:
|
|||
|
|
- `unlockAfterSuccess` + `unlocked`(或绑定 `unlockState`)为 true 时,会直接放行,不再校验/不再消耗
|
|||
|
|
- 常见排错点:测试时如果勾了 `Unlocked`,即使没有物品也会通过
|
|||
|
|
- `RequireAny` + `consumeOnSuccess`:只消耗本次“触发通过”的那一条需求
|
|||
|
|
|
|||
|
|
### 3.2 解锁状态源(用于后续 UI/渲染读取、以及外观切换)
|
|||
|
|
|
|||
|
|
组件:[InteractUnlockState](file:///e:/Unity/Unity%20Program/Test_2022/Assets/_Project/Scripts/Interaction/InteractUnlockState.cs)
|
|||
|
|
|
|||
|
|
用途:
|
|||
|
|
- 保存“是否已解锁”的状态(`IsUnlocked`)
|
|||
|
|
- 解锁/上锁时自动激活/隐藏目标物体
|
|||
|
|
- `activateWhenUnlocked`:解锁时显示(例:发电机上的电池模型)
|
|||
|
|
- `deactivateWhenUnlocked`:解锁时隐藏(例:门锁模型)
|
|||
|
|
- 可选作为“门禁条件”参与交互校验
|
|||
|
|
- 勾选 `blockInteractionWhenLocked` 后:当 `unlocked=false` 会阻止交互,并返回 `lockedFailReason`
|
|||
|
|
- 默认不勾选:仅作为状态源与外观切换,不影响交互(兼容旧场景配置)
|
|||
|
|
- 可选对外广播状态事件(便于联动 UI/音效/剧情)
|
|||
|
|
- `onUnlocked` / `onLocked` / `onValueChanged(bool)`
|
|||
|
|
|
|||
|
|
### 3.3 未解锁则禁止交互(门禁)
|
|||
|
|
|
|||
|
|
组件:[RequireUnlockedInteractCondition](file:///e:/Unity/Unity%20Program/Test_2022/Assets/_Project/Scripts/Interaction/Conditions/RequireUnlockedInteractCondition.cs)
|
|||
|
|
|
|||
|
|
用途:
|
|||
|
|
- 给某个开关/门/机关设置“必须已解锁才允许交互”
|
|||
|
|
- `unlockState` 可不填,组件会向父级查找 `InteractUnlockState`
|
|||
|
|
- 如果你希望“只要挂了 InteractUnlockState 且未解锁就不能互动”,也可以直接在 `InteractUnlockState` 上勾选 `blockInteractionWhenLocked`(无需再挂本条件组件)
|
|||
|
|
|
|||
|
|
### 3.4 交互成功后解锁(联动)
|
|||
|
|
|
|||
|
|
组件:[UnlockStatesOnInteractSucceeded](file:///e:/Unity/Unity%20Program/Test_2022/Assets/_Project/Scripts/Interaction/Conditions/UnlockStatesOnInteractSucceeded.cs)
|
|||
|
|
|
|||
|
|
用途:
|
|||
|
|
- 在某个交互成功后,解锁自身或其它目标(例如发电机解锁另一个开关)
|
|||
|
|
|
|||
|
|
## 4. 常用配置范式(推荐挂载位置:交互根节点)
|
|||
|
|
|
|||
|
|
推荐挂载位置:
|
|||
|
|
- 条件/解锁相关组件挂在“门/机关根节点”(通常是 collider 的父级或同级)
|
|||
|
|
- 玩家侧用 `GetComponentsInParent<InteractConditionBehaviour>()`,挂在父级更稳妥
|
|||
|
|
|
|||
|
|
### 4.1 无条件交互
|
|||
|
|
|
|||
|
|
- 只挂交互行为组件(Door/Lever/Toggle* 等),不挂任何 `InteractConditionBehaviour`
|
|||
|
|
|
|||
|
|
### 4.2 有锁的门:满足条件后隐藏锁,并永久解锁
|
|||
|
|
|
|||
|
|
门根节点:
|
|||
|
|
- `DoorController`(或其它 IInteractable)
|
|||
|
|
- `InteractUnlockState`
|
|||
|
|
- `deactivateWhenUnlocked` 填门锁子物体
|
|||
|
|
- `RequireItemsInteractCondition`
|
|||
|
|
- 配置钥匙需求
|
|||
|
|
- `consumeOnSuccess`(需要消耗钥匙则勾)
|
|||
|
|
- `unlockAfterSuccess` 勾选
|
|||
|
|
- `unlockState` 指向同门上的 `InteractUnlockState`
|
|||
|
|
|
|||
|
|
### 4.3 发电机放电池:消耗电池、显示电池模型、解锁另一个开关
|
|||
|
|
|
|||
|
|
发电机根节点:
|
|||
|
|
- `RequireItemsInteractCondition`(配置电池,勾选消耗 + 解锁)
|
|||
|
|
- `InteractUnlockState`
|
|||
|
|
- `activateWhenUnlocked` 填“隐藏的电池模型”
|
|||
|
|
- `UnlockStatesOnInteractSucceeded`
|
|||
|
|
- `targetStates` 填“另一个开关的 InteractUnlockState”
|
|||
|
|
|
|||
|
|
被解锁的开关根节点:
|
|||
|
|
- `InteractUnlockState`(初始未解锁)
|
|||
|
|
- `RequireUnlockedInteractCondition`(未解锁时禁止交互)
|
|||
|
|
|
|||
|
|
### 4.4 发电机首次启动:触发一次性旁白/对话(存档持久化)
|
|||
|
|
|
|||
|
|
目标:
|
|||
|
|
- 发电机第一次“启动/通电”时触发一段旁白或其它事件
|
|||
|
|
- 该事件只触发一次(跨存档/跨重进场景也不会再触发)
|
|||
|
|
|
|||
|
|
推荐做法(不改玩家交互入口,完全 Inspector 配置):
|
|||
|
|
1) 让发电机“启动开关”使用 `ToggleMoveInteractable` 或 `ToggleRotateInteractable`(它们有 `onTurnedOn` / `onTurnedOff` 事件)
|
|||
|
|
2) 在发电机根节点(或同级管理对象)挂 `OneShotFlagEvent`
|
|||
|
|
- `flagId` 建议命名:`Generator_<UniqueName>_FirstOn`
|
|||
|
|
- `autoSaveAfterTrigger` 建议勾选(触发后立刻写入存档)
|
|||
|
|
3) 把开关脚本的 `onTurnedOn` 指向 `OneShotFlagEvent.Trigger`
|
|||
|
|
4) 在 `OneShotFlagEvent.onFirstTriggered` 上挂你要触发的事件:
|
|||
|
|
- 播放旁白:使用 `NarrationPlayByIdAction`(配置 `sequenceId`),或直接调用 `NarrationSystem.PlayById`
|
|||
|
|
- 触发其它联动:任意 `UnityEvent` 目标方法都可以
|
|||
|
|
|
|||
|
|
运行依赖(自动注入):
|
|||
|
|
- `SettingsBootstrap` 会在运行时创建 `SaveManager` 与 `PersistentFlagsService`,用于保存一次性 flags
|
|||
|
|
- [SettingsBootstrap.cs](file:///e:/Unity/Unity%20Program/Test_2022/Assets/_Project/Scripts/Core/SettingsSystem/SettingsBootstrap.cs)
|
|||
|
|
- [PersistentFlagsService.cs](file:///e:/Unity/Unity%20Program/Test_2022/Assets/_Project/Scripts/Core/SaveSystem/PersistentFlagsService.cs)
|
|||
|
|
- [OneShotFlagEvent.cs](file:///e:/Unity/Unity%20Program/Test_2022/Assets/_Project/Scripts/Core/Triggers/OneShotFlagEvent.cs)
|
|||
|
|
|
|||
|
|
## 5. UI:指向提示(Reach Feed Notification)
|
|||
|
|
|
|||
|
|
脚本落点:[PlayerController.cs](file:///e:/Unity/Unity%20Program/Test_2022/Assets/_Project/Scripts/Player/PlayerController.cs)
|
|||
|
|
|
|||
|
|
用途:
|
|||
|
|
- 玩家指向可交互对象时:
|
|||
|
|
- 条件通过:显示“可互动”通知
|
|||
|
|
- 条件失败:显示“不可互动”通知(可选显示 failReason)
|
|||
|
|
|
|||
|
|
Reach 参考:
|
|||
|
|
- [ReachUI_Technical_Reference.md](file:///e:/Unity/Unity%20Program/Test_2022/Assets/_Project/Docs/ReachUI_Technical_Reference.md)
|
|||
|
|
|
|||
|
|
如何加速弹出动画(FeedNotification):
|
|||
|
|
- 推荐改 Animator Controller 状态 Speed:`Assets/ThirdParty/UI/Reach - Complete Sci-Fi UI/Animations/HUD/FeedNotification.controller`
|
|||
|
|
- 或缩短动画片段时长:`FeedNotification_In.anim / FeedNotification_Out.anim`
|
|||
|
|
- 或运行时提高 Animator.speed(PlayerController 已提供字段)
|
|||
|
|
|
|||
|
|
## 6. 视觉:指向高亮(URP 外轮廓)
|
|||
|
|
|
|||
|
|
实现方式:
|
|||
|
|
- 指向时给目标 Renderer 追加一个“外轮廓材质”,并用材质属性控制颜色/宽度
|
|||
|
|
|
|||
|
|
组件:
|
|||
|
|
- 目标物体:[InteractOutlineTarget](file:///e:/Unity/Unity%20Program/Test_2022/Assets/_Project/Scripts/Interaction/InteractOutlineTarget.cs)
|
|||
|
|
- 玩家驱动:[PlayerAimOutlineHighlighter](file:///e:/Unity/Unity%20Program/Test_2022/Assets/_Project/Scripts/Player/PlayerAimOutlineHighlighter.cs)
|
|||
|
|
|
|||
|
|
Shader:
|
|||
|
|
- [InteractOutline.shader](file:///e:/Unity/Unity%20Program/Test_2022/Assets/_Project/Shaders/InteractOutline.shader)(`Project/InteractOutline`)
|
|||
|
|
|
|||
|
|
颜色规则(默认实现):
|
|||
|
|
- 条件全部通过:绿(可自定义)
|
|||
|
|
- 条件失败且属于“未解锁门禁”:橙(可自定义)
|
|||
|
|
- 其它失败:使用 blockedColor(可自定义)
|
|||
|
|
|
|||
|
|
## 7. 物理状态控制 (防掉落/穿模)
|
|||
|
|
|
|||
|
|
组件:[ItemPhysicsState](file:///e:/Unity/Unity%20Program/Test_2022/Assets/_Project/Scripts/Interaction/ItemPhysicsState.cs)
|
|||
|
|
|
|||
|
|
用途:
|
|||
|
|
- **解决场景初始放置的物品穿模问题**:当互动物品(如药瓶、道具)放置在只有一个大 `BoxCollider` 的架子或柜子内部时,直接启用刚体会导致物品在场景一开始就被物理引擎“挤出”或弹飞。
|
|||
|
|
- **配置方法**:将该组件挂载在拥有 `Rigidbody` 的互动物体上,并勾选 `startKinematic = true`。
|
|||
|
|
- **机制**:
|
|||
|
|
- 场景加载(`Awake`)时,该物体会自动锁定为 `isKinematic = true`,不受重力和碰撞弹出的影响。
|
|||
|
|
- 当玩家拾取该物体存入背包,后续再次丢弃(Drop)到场景时,丢弃逻辑会自动调用 `ReleaseKinematic()` 将其恢复为完全受重力影响的物理状态,玩家此时踢到它或将其扔向墙壁都会有正常的物理反馈。
|
|||
|
|
- **注意**:默认状态下 `startKinematic = false`,此时不产生任何影响。这是一项附加配置选项,不会干扰原有交互校验逻辑链。
|
|||
|
|
|
|||
|
|
## 8. 物品使用事件触发器 (监听左键使用)
|
|||
|
|
|
|||
|
|
组件:[ItemUseEventTrigger](file:///e:/Unity/Unity%20Program/Test_2022/Assets/_Project/Scripts/Interaction/ItemUseEventTrigger.cs)
|
|||
|
|
|
|||
|
|
用途:
|
|||
|
|
- **监听特定物品在背包/手中被“使用”**:当玩家在场景中任意位置,选中特定物品并按下“使用”键(通常为左键)时,触发场景中的指定事件(例如:首次吃下某种药丸后触发旁白、首次阅读特定文件后开启某扇门)。
|
|||
|
|
|
|||
|
|
配置方法:
|
|||
|
|
1. 在场景中创建一个空物体(或在任意管理节点上)。
|
|||
|
|
2. 挂载 `ItemUseEventTrigger` 组件。
|
|||
|
|
3. `Target Item`:指定你要监听的物品数据(`ItemData`)。
|
|||
|
|
4. `Trigger Only Once`:勾选则表示仅在第一次使用该物品时触发(默认开启)。
|
|||
|
|
5. `On Target Item Used`:配置你需要触发的 `UnityEvent`(如播放旁白、启用对象等)。
|
|||
|
|
|
|||
|
|
实现机制:
|
|||
|
|
- 在 `ItemUsageSystem` 核心脚本中新增了 `OnItemUsedGlobal` 静态全局事件。每次有物品被成功使用时会广播该事件。
|
|||
|
|
- `ItemUseEventTrigger` 在 `OnEnable` 时自动订阅,纯旁路监听,**完全不影响任何现有的物品消耗、装备或UI显示逻辑**。
|
|||
|
|
|
|||
|
|
## 9. 交互计数器系统 (配合 Interact 累加并触发事件)
|
|||
|
|
|
|||
|
|
组件:
|
|||
|
|
- 场景系统:[InteractCountThresholdSystem](file:///e:/Unity/Unity%20Program/Test_2022/Assets/_Project/Scripts/Interaction/InteractCountThresholdSystem.cs)
|
|||
|
|
- 累加器(挂在被交互对象上):[InteractCountAccumulator](file:///e:/Unity/Unity%20Program/Test_2022/Assets/_Project/Scripts/Interaction/Conditions/InteractCountAccumulator.cs)
|
|||
|
|
|
|||
|
|
用途:
|
|||
|
|
- 解决“交互对象会在 Interact 后被销毁/替换(例如拾取物 Destroy 自身)导致后挂脚本失效”的场景。
|
|||
|
|
- 让 **被交互对象** 在一次“交互成功”时向 **场景中的系统对象** 累加计数;当计数达到阈值后,由系统对象触发一组 `UnityEvent`。
|
|||
|
|
|
|||
|
|
配置方法:
|
|||
|
|
1. 在场景中创建一个空物体(或在任意管理节点上)挂 `InteractCountThresholdSystem`。
|
|||
|
|
2. 设置 `Threshold` 为你希望触发事件所需的累计次数,并在 `On Threshold Reached` 中配置事件列表。
|
|||
|
|
3. 在你想要计数的“被交互对象根节点”(通常是 collider 的父级或同级)挂 `InteractCountAccumulator`。
|
|||
|
|
4. `Target System` 指向第 1 步的系统对象;`Add Amount` 设置每次成功交互累加多少(默认 1)。
|
|||
|
|
|
|||
|
|
触发语义:
|
|||
|
|
- `InteractCountAccumulator` 走的是 `InteractConditionBehaviour.OnInteractSucceeded` 回调,只会在交互条件全部通过且 `Interact()` 已执行后触发;因此适合拾取类物体在销毁前完成计数与触发。
|
|||
|
|
- `InteractCountAccumulator` 只有在“从未绑定过 Target System”的情况下才会自动查找场景中的 `InteractCountThresholdSystem`;如果运行过程中 Target System 被销毁导致引用变空,则不会再随意改投到其它阈值系统上。
|
|||
|
|
|
|||
|
|
## 10. 排错清单
|
|||
|
|
|
|||
|
|
- “没有物品也能交互成功”
|
|||
|
|
- 检查 `RequireItemsInteractCondition` 是否勾选了 `unlockAfterSuccess` 且 `unlocked`(或 `unlockState.unlocked`)为 true
|
|||
|
|
- 检查 requirement 是否为空(为空会被视为无需资格直接放行)
|
|||
|
|
- “提示/高亮不出现”
|
|||
|
|
- 检查交互层(interactLayer)与射线范围(interactRange)
|
|||
|
|
- 检查目标是否存在 `IInteractable`(本体或父物体)
|
|||
|
|
- 外轮廓:目标是否挂了 `InteractOutlineTarget`,玩家是否挂了 `PlayerAimOutlineHighlighter` 且绑定了 outlineMaterial
|
|||
|
|
|
|||
|
|
## 11. 相关文档索引
|
|||
|
|
|
|||
|
|
- 条件交互与消耗配置细节:[Interaction_条件交互与消耗型交互.md](file:///e:/Unity/Unity%20Program/Test_2022/Assets/_Project/Docs/Interaction_%E6%9D%A1%E4%BB%B6%E4%BA%A4%E4%BA%92%E4%B8%8E%E6%B6%88%E8%80%97%E5%9E%8B%E4%BA%A4%E4%BA%92.md)
|
|||
|
|
- 互动条件校验方案(设计思路):[Interaction_互动条件校验方案.md](file:///e:/Unity/Unity%20Program/Test_2022/Assets/_Project/Docs/Interaction_%E4%BA%92%E5%8A%A8%E6%9D%A1%E4%BB%B6%E6%A0%A1%E9%AA%8C%E6%96%B9%E6%A1%88.md)
|
|||
|
|
- Reach UI 速查(含通知加速):[ReachUI_Technical_Reference.md](file:///e:/Unity/Unity%20Program/Test_2022/Assets/_Project/Docs/ReachUI_Technical_Reference.md)
|