# TaskSystem - 触发器与流程节点方案 本文档描述“任务的添加/删除/状态切换”在项目中的推荐落地方式:通过独立脚本组件(触发器/动作节点)把 TaskSystem 接入任意玩法事件、流程节点或 Timeline/动画/对话节点,而不是把逻辑写死在某个互动脚本里。 适用范围: - 任务不一定来自交互(门、拉杆等),也可能来自剧情节点、进入区域、时间到、对话选项、成就/教程步骤等。 - 希望策划/关卡在 Inspector 中配置“触发条件 → 任务动作”,尽量减少硬编码与跨模块耦合。 相关基础系统: - `Assets/_Project/Scripts/Core/TaskSystem/TaskService.cs` - `Assets/_Project/Scripts/Core/TaskSystem/TaskData.cs` - `Assets/_Project/Docs/TaskSystem_Technical_Reference.md` ## 1. 总体思路 将任务系统的“动作”拆成可复用的独立组件: - 动作基类(`GameAction`):定义统一的 `public override void Invoke()` 接口。 - 动作类(Task Actions):继承自 `GameAction`,只做一件事,调用 `TaskService` 的某个 API。 - AddTaskAction - RemoveTaskAction - SetTaskStatusAction - 触发类(Task Triggers):监听某个来源事件,满足条件时调用动作(通过 UnityEvent 绑定 `Action.Invoke()`)。 - TriggerVolume (进入区域触发) - InteractFailedTrigger (交互失败触发) 这样可以保证: - 任务系统逻辑(添加/移除/状态规则)仍然集中在 `TaskService`,只通过 API 进入 - 具体玩法/流程只负责“何时触发”,不负责“如何维护任务” - 同一套任务动作能被任何系统复用(UnityEvent、Timeline、Trigger、脚本回调都能接) ## 2. 推荐的“任务动作”组件清单(未来要做的脚本) 以下是建议新增的脚本(都应放在 `_Project/Scripts/Core/TaskSystem/Actions` 或 `_Project/Scripts/UI/Tasks/Actions`,按你们组织习惯即可),每个脚本尽量只有一个公开入口方法,便于被 UnityEvent 调用。 ### 2.1 AddTaskAction 用途:向任务系统添加一个任务(可选超时秒数)。 Inspector 槽位: - `TaskService taskService`(槽位引用;不写死单例) - `TaskData task`(要添加的任务定义) - `float timeoutSeconds = -1`(<0 表示不超时) - `bool addOrUpdate = true`(推荐默认 true:避免重复添加失败) - `bool resetStatusToTodo = false`(可选:用于“重复触发时重置为待做”) 公开方法(示例概念): - `public void Invoke()` 内部应调用: - `taskService.AddTask(task, timeoutSeconds)` 或 - `taskService.AddOrUpdateTask(task, timeoutSeconds, resetStatusToTodo)` ### 2.2 RemoveTaskAction 用途:按任务 ID 移除任务(建议用 TaskData 引用获取 id,避免手写字符串)。 Inspector 槽位: - `TaskService taskService` - `TaskData task` 公开方法: - `public void Invoke()` 内部应调用: - `taskService.RemoveTask(task.id)` ### 2.3 SetTaskStatusAction 用途:把某个任务置为 Completed / Expired / Todo。 Inspector 槽位: - `TaskService taskService` - `TaskData task` - `TaskStatus status` 公开方法: - `public void Invoke()` 内部应调用: - `taskService.SetTaskStatus(task.id, status)` ## 3. 推荐的“触发器”组件清单(未来要做的脚本) 触发器组件的核心原则: - 只监听事件 + 过滤条件 + 调用一个或多个动作 - 尽量支持 UnityEvent:让关卡/流程可以在 Inspector 里做绑定 - 必须提供“防重复触发”的选项(例如只触发一次、冷却时间、按任务是否已存在等) ### 3.1 OnStartTrigger / OnEnableTrigger 用途:场景开始/对象启用时自动添加任务或设置状态(替代临时的 TaskBootstrap)。 建议用法: - 教程任务:开场添加“打开背包”“查看平板”等 - 剧情开场:进入房间后添加“寻找钥匙” 防重复建议: - `onlyOnce`(触发一次后禁用自身) - `requireTaskNotPresent`(任务已存在则不触发) ### 3.2 InteractFailedTrigger(交互失败触发) 用途:当玩家“按下交互键且条件不满足”时触发任务添加。 关键注意点: - 不要挂在“瞄准提示”的高频检测上,否则会重复触发 - 必须挂在“按键交互尝试”的失败分支上(目前失败分支在 PlayerController 里) 推荐做法: - PlayerController 暴露一个“交互失败事件”(UnityEvent 或 C# event) - 参数至少应包含:目标 collider / 交互者 / failReason - InteractFailedTrigger 订阅该事件,并做过滤: - 只关心指定门/指定根物体(hit 是否在该物体层级里) - 可选:只对某类条件失败生效(例如 RequireUnlockedInteractCondition) ### 3.3 UnlockStateChangedTrigger(解锁状态变化触发) 用途:当 `InteractUnlockState` 变为 unlocked 时: - 自动完成某个任务(例如“寻找钥匙”完成) - 或移除过期任务 当前工程里 `InteractUnlockState` 没有事件回调,只有 `SetUnlocked/Unlock/Lock` 改字段并 Apply 视觉。 推荐改造方向(未来做): - 在 `InteractUnlockState` 增加 `event Action Changed` 或 `UnityEvent onChanged` - 或者新增一个 Watcher 组件轮询 `IsUnlocked`(成本更低但不够优雅) ### 3.4 TriggerVolume / TimelineSignal / DialogueNode 用途:将任务动作接到“流程节点”上: - 进入触发区:添加任务 - Timeline Signal:切任务状态 - 对话节点:选择某个选项后添加/移除任务 建议统一入口: - 这些系统最终都调用 `AddTaskAction/SetTaskStatusAction/RemoveTaskAction` 的 `Invoke()` ## 4. 配置规范(强烈建议遵守) ### 4.1 不写死引用 所有动作脚本都应该用 Inspector 槽位引用: - `TaskService`:拖场景里的 TaskService(或者由上层管理注入) - `TaskData`:拖 ScriptableObject 资产 不要在脚本里用: - 查找路径 `transform.Find("...")` - `GameObject.Find(...)` - 用字符串写 taskId(除非迫不得已) ### 4.2 去重策略(默认建议 AddOrUpdate) 任务添加最常见的坑是“重复触发导致重复添加/报错/刷 UI”。 推荐默认策略: - 触发器 → 调用 `AddOrUpdateTask(taskData, ...)` - 如确实需要“只添加一次”:在触发器侧增加 `onlyOnce` 或 `requireTaskNotPresent` ### 4.3 任务生命周期归属 建议统一归属: - 任务的超时判定:由 `TaskService`(已实现) - 任务的自动移除:由 `TaskService` 的 autoRemove 配置(已实现,可关) - 任务的产生与完成:由“触发器 + 动作”拼装(关卡/流程可配置) ## 5. 示例:门未解锁 → 交互失败 → 添加“寻找钥匙”任务 目标: - 门上挂了 RequireUnlockedInteractCondition,未解锁时交互失败 - 失败后添加任务 `task_find_key` 推荐配置: - Scene: - TaskSystem(TaskService) - DoorRoot - InteractUnlockState(unlocked=false) - RequireUnlockedInteractCondition(failReason=“尚未解锁”) - InteractFailedTrigger - targetFailReason=“尚未解锁”(或留空) - onInteractFailedEvent -> 拖入下方的 AddTaskAction,选择 `GameAction.Invoke()` - AddTaskAction - taskService -> TaskSystem(TaskService) - task -> task_find_key(TaskData) ## 6. 临时测试与正式接入的区别 - `TaskBootstrap`:仅用于快速在场景启动时塞入测试任务(方便 UI 验证) - 正式玩法:应该由“触发器 + 动作”驱动任务变化,避免 Start 时就塞进任务导致误判