Files
Echo/Assets/_Project/Docs/Interaction_互动条件校验方案.md
2026-07-08 20:42:51 +08:00

8.0 KiB
Raw Permalink Blame History

互动条件校验方案(仅方案,不写代码)

更多“条件/消耗/解锁/UI 提示/指向高亮”的统一说明见:

现状梳理(基于已存在脚本)

  • 玩家侧:PlayerController.PerformInteract() 通过 Raycast 命中物体后,查找 IInteractable(本体或父物体)并直接调用 Interact()
    • 目前已接入“条件组件”前置判断:在调用 Interact() 前,查找父级上的 InteractConditionBehaviour 并逐一校验;失败时会输出失败原因日志。
  • 物体侧:DoorControllerLeverControllerToggleRotateInteractable 都实现了 IInteractable,其 Interact() 只负责执行动作(切换状态 + DOTween 动画/旋转),并各自提供了 GetInteractPrompt()(但玩家侧目前未使用)。
  • 道具侧:HandController 负责“手持外观”;ItemUsageSystem 负责“使用道具输入 -> 触发特定 UI/消耗逻辑”;两者与“场景交互门/拉杆”无直接条件绑定。

结论:当前架构是“玩家统一触发 -> 物体自行执行”,适合无条件互动;一旦加入“需要特定道具/状态才能互动”,就需要在 Interact() 调用前增加一层“条件判定”。

补充:你新增的 ToggleRotateInteractable 对架构的影响

  • 这个脚本本质上把“门/拉杆这类‘开关式旋转交互’”抽象成了一个通用组件:
    • 支持多目标(RotationTarget[])、轴选择、Off/On 两套欧拉角、初始化自动检测、Tween 配置、以及 UnityEventOn/Off/StateChanged)。
    • 这意味着“行为层”已经开始走向通用化/组件化,比在每个 Door/Lever 单独写逻辑更利于复用。
  • 对“需要特定道具才能互动”的需求来说,这反而更适合采用“独立条件组件”的方案:条件不应该写进 ToggleRotateInteractable,否则它就不再通用。

注意:IInteractable 目前只定义了 Interact()

  • 你现在的 IInteractable 并不包含 GetInteractPrompt()Door/Lever/ToggleRotateInteractable 只是各自额外提供了这个方法)。
  • 因此玩家侧如果想统一显示交互提示/失败原因,建议走“可选接口/可选组件”的方式,例如:
    • 提示文本走一个独立的接口(如 IInteractPrompt)或提示组件;
    • 条件失败原因由“条件组件”提供;
    • 玩家侧优先查这些可选能力,查不到就降级为默认提示。

推荐方向:新增“条件校验脚本/组件”,并让玩家侧在调用前统一校验(推荐)

核心思路

保持现有 DoorController / LeverController / ToggleRotateInteractable 的职责单一:只做“行为与动画”,不要把“钥匙/任务状态/场景权限”等规则硬塞进每个 Controller。

新增一个专门用于校验互动条件的脚本(组件化挂载在可交互物体上或其父物体上),由玩家的交互入口(PlayerController.PerformInteract())在调用 Interact() 前统一检查:

  • 通过:照常调用 IInteractable.Interact()
  • 不通过:不调用 Interact(),并输出一个统一的“失败提示”(后续可接 UI 提示、音效、日志)。

为什么推荐“新增组件”,而不是直接改现有 Controller

  • 可复用:同一套条件组件可以给门、箱子、机关、NPC 对话等复用,而不是每个交互脚本都写一遍“检查背包是否有钥匙”。
  • 可配置:Inspector 上直接配置“需要的道具ID/数量/是否消耗/需要的剧情标记”等,设计迭代成本更低。
  • 可扩展:未来加入“多个条件组合(AND/OR)”“一次性权限”“任务阶段限制”等,只需要扩展条件组件,不必改每个交互物体的行为脚本。
  • 更符合分层:玩家侧是统一入口、物体侧是具体行为、条件侧是规则/权限。

你需要“动哪些地方”

  1. 新增脚本(建议新增到 Interaction 或新建 Interaction/Conditions 目录)
    • 作为一个“可选组件”:只有需要条件的物体才挂它。
  2. 轻改 PlayerController.PerformInteract()
    • interactable.Interact() 之前:尝试在命中物体/父物体上查找“条件组件”,存在则执行校验,不存在则默认允许互动。
  3. (可选,后续做 UI 时再做)GetInteractPrompt() 也纳入玩家侧
    • 由于 IInteractable 不包含 prompt,建议用“可选接口/可选组件”的方式整合提示文本。

本项目落地实现(当前仓库)

  • 条件组件脚本路径:
    • Assets/_Project/Scripts/Interaction/Conditions/RequireItemsInteractCondition.cs(多道具 OR/AND
    • Assets/_Project/Scripts/Interaction/Conditions/RequireUnlockedInteractCondition.cs(基于解锁状态的门禁)
    • Assets/_Project/Scripts/Interaction/Conditions/UnlockStatesOnInteractSucceeded.cs(交互成功后解锁目标)
    • Assets/_Project/Scripts/Interaction/InteractUnlockState.cs(解锁状态源,可驱动外观显示/隐藏;可选参与门禁校验并抛出状态事件)
  • 玩家交互入口已接入条件校验:Assets/_Project/Scripts/Player/PlayerController.cs
  • 使用方式(Inspector 配置):
    • 在需要“道具限制”的可交互物体上(或其父物体上)挂载 RequireItemsInteractCondition
    • 若只需要单道具:mode=RequireAll + requirements 仅配置 1 条(requiredItemIdItemData.id
    • 如需“用掉钥匙/道具”,勾选 consumeOnSuccess
    • 如需“一次解锁后永久放行(本次运行内)”,勾选 unlockAfterSuccess
      • 解锁后不会再消耗道具,也不会再校验背包数量
      • 可选绑定 unlockState 指向 InteractUnlockState,将解锁状态暴露给后续 UI/渲染系统
    • (可选)设置 failReason 作为失败提示文本

补充文档

  • 条件交互与消耗型交互的配置与语义说明见:Assets/_Project/Docs/Interaction_条件交互与消耗型交互.md

条件组件建议的能力范围(不写代码,只定职责)

最小可用版本(MVP):

  • 配置“需要某个物品(按 id)与数量”
  • 返回“是否允许互动”
  • 返回“失败原因文本”(例如“需要钥匙”“需要撬棍”)

进阶版本(可逐步加入):

  • 是否消耗道具(成功互动后扣除数量)
  • 多条件组合(需要 A 与 B / 需要 A 或 B)
  • 关联剧情/关卡状态(如某个 Flag/QuestState
  • 互动成功后触发事件(UnityEvent),用于开门同时触发音效/任务推进等

备选方案 1:直接扩展 IInteractable(不推荐作为第一步)

做法:把 IInteractable 升级为 CanInteract(Interactor) / TryInteract(Interactor) 这种形式,让每个交互脚本自己判断并返回失败原因。

缺点:

  • 需要修改所有实现 IInteractable 的脚本,未来新增交互也必须实现这些判断。
  • 条件逻辑会散落在各个 Controller 中,不利于复用与统一配置。

适用场景:项目规模很小、交互类型很少,并且每个交互的条件都高度定制且不可复用。

备选方案 2:新增“InteractionSystem(玩家侧系统)”作为中枢(可选)

做法:把 Raycast、提示、条件判断、调用 Interact 等集中到一个 InteractionSystem(挂在玩家上),PlayerController 只负责移动/视角。

优点:玩家控制与交互解耦更清晰;但这属于结构重构,适合你准备做完整交互 UI 与复杂逻辑时再做。

最终建议(可执行结论)

  • 结论:建议新增一个“互动条件校验组件脚本”,并在 PlayerController 的交互入口处统一调用它;现有行为脚本只保留行为,不要塞入道具校验。
  • 落地策略:先做“需要某个物品ID才能互动 + 失败提示”,跑通全链路;再逐步扩展为“消耗、组合条件、剧情状态、UI 提示”。