190 lines
7.5 KiB
Markdown
190 lines
7.5 KiB
Markdown
|
|
# 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:
|
|||
|
|
- 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 时就塞进任务导致误判
|
|||
|
|
|