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