Files

190 lines
7.5 KiB
Markdown
Raw Permalink Normal View History

2026-07-08 19:53:15 +08:00
# 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<bool> Changed``UnityEvent<bool> 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:
- TaskSystemTaskService
- DoorRoot
- InteractUnlockStateunlocked=false
- RequireUnlockedInteractConditionfailReason=“尚未解锁”)
- InteractFailedTrigger
- targetFailReason=“尚未解锁”(或留空)
- onInteractFailedEvent -> 拖入下方的 AddTaskAction,选择 `GameAction.Invoke()`
- AddTaskAction
- taskService -> TaskSystem(TaskService)
- task -> task_find_key(TaskData)
## 6. 临时测试与正式接入的区别
- `TaskBootstrap`:仅用于快速在场景启动时塞入测试任务(方便 UI 验证)
- 正式玩法:应该由“触发器 + 动作”驱动任务变化,避免 Start 时就塞进任务导致误判