任务系统最怕到处监听
任务系统开始通常很简单:击败 5 个怪,收集 3 个宝箱,通关第 2 关。工程在怪物死亡处加一句任务进度,在宝箱打开处加一句任务进度,在通关结算处加一句任务进度。几周后,日常任务、成就、活动任务、新手引导、战令任务都想监听同一批事件,代码里到处都是 quest.addProgress。
我见过一个 Phaser 闯关项目,活动任务要求“使用火焰技能击败 10 个冰怪”。最初工程在技能命中里写判断,后来又有成就要求“单局不受伤通关”,战令要求“累计释放 50 次技能”。每个系统各写一套监听,最后一次技能释放会触发四五段逻辑,Bug 也难定位。正确做法是让玩法系统只发布语义事件,任务系统自己订阅和匹配。
任务系统的边界应该是:玩法系统负责发生了什么,任务系统负责哪些事件能推进哪些任务,UI 负责展示当前可见任务。不要让每个按钮、怪物和宝箱都知道任务配置。
flowchart TD
A[玩法事件 enemy.killed/chest.opened/level.completed] --> B[事件总线]
B --> C[任务条件匹配器]
C --> D[任务进度状态]
D --> E{是否完成}
E -->|否| F[更新追踪 UI]
E -->|是| G[标记可领取]
G --> H[奖励服务校验并发放]
H --> F
任务配置要表达条件
任务配置不应该只是一段文案和一个目标数。它至少要描述任务 id、类型、条件、目标数量、奖励、前置任务、展示规则和过期时间。比如击杀任务需要 enemyTag,收集任务需要 itemId,通关任务需要 levelId,活动任务需要 eventId。
条件表达要足够清楚,但不要变成一门复杂脚本语言。大多数 Phaser 小游戏用结构化 JSON 就够了:event=enemy.killed、where.enemyTag=ice、where.skillElement=fire、target=10。匹配器读取事件 payload 判断是否推进。
配置要做校验。任务引用不存在的 enemyTag、奖励表缺失、前置任务循环、目标数为 0、活动时间已经过期,都应该在加载时发现。任务配置错误如果进了线上,玩家可能无法完成或无法领奖,影响比普通 UI 文案错误大得多。
事件要有稳定语义
任务依赖事件,所以事件命名和 payload 必须稳定。enemy.killed 应该明确包含 enemyId、enemyTag、levelId、killerId、skillId、skillElement、isBoss 等字段。不要今天发 monsterDead,明天发 enemy.kill,后天只在某种怪物里发。
事件也要区分尝试和结果。玩家点击攻击不是击杀,打开宝箱动画开始不是获得奖励,进入关卡不是通关。任务一般应该监听已确认结果,而不是表现开始。否则动画取消、网络失败或服务端否定时,任务进度会提前增加。
联网项目里,重要任务进度最好由服务端确认或至少在结算时校验。客户端可以本地显示临时进度,但最终奖励领取要防作弊。单机项目也要保证事件幂等,避免同一只怪死亡事件重复推进。
任务状态要小而完整
任务状态不需要保存全部配置,只要保存 questId、progress、state、claimed、startedAt、updatedAt 等动态字段。配置来自任务表,状态来自存档。这样任务文案和奖励调整不需要迁移大量存档。
状态机可以很简单:locked、active、completed、claimed、expired。locked 表示前置未满足,active 正在进行,completed 可领取,claimed 已领取,expired 已过期。UI 根据状态显示不同入口。不要用多个 boolean 拼状态,后期很容易出现 completed=true 但 claimed=true 又 active=true 的矛盾。
活动任务过期要谨慎。过期后未领取奖励如何处理?自动发邮件、允许补领、直接失效?这是产品规则,不是工程随手决定。任务系统要支持明确策略,并在 UI 上解释。
追踪 UI 要筛选信息
任务多了以后,不能把所有任务都放到主界面。追踪 UI 应该只显示当前最相关的几条:主线任务、新手任务、玩家手动追踪的任务、即将完成的活动任务。其他任务放在任务面板里。
追踪 UI 不应该自己计算进度。它订阅任务状态变化,显示 title、progress、target、状态和跳转入口。玩家点击任务,可以导航到关卡、打开活动页或高亮目标。导航也要由任务配置提供,不要在 UI 里写死。
任务完成提示要节制。每次进度 +1 都弹窗会打断游戏。可以在主界面小幅刷新,完成时显示轻量 toast,领奖时再展示奖励。战斗中完成任务,最好等战斗结束再弹详细面板。
奖励领取要幂等
任务奖励是玩家资产,领取必须幂等。点击领取时,先检查任务是否 completed 且未 claimed,再调用奖励服务发放,成功后标记 claimed。网络重试、快速点击、页面刷新都不应该造成重复发奖或丢奖。
奖励服务要返回明确结果:成功、已领取、任务未完成、任务过期、背包满、服务器错误。背包满时可以阻止领取,也可以发到邮件或临时箱,取决于产品规则。不要简单弹“领取失败”,玩家会不知道该怎么办。
如果客户端本地发奖,也要把发奖和标记 claimed 放在同一个事务感流程里。先发奖后写 claimed 失败,可能重复领取;先写 claimed 后发奖失败,可能丢奖励。至少要有恢复策略和日志。
一个任务匹配器示例
下面示例展示任务条件如何匹配玩法事件。真实项目可以支持更多操作符,但入口应该保持清楚。
type GameEvent = { name: string; payload: Record<string, unknown> };
type QuestCondition = { event: string; where?: Record<string, unknown>; target: number };
function matchCondition(event: GameEvent, condition: QuestCondition) {
if (event.name !== condition.event) return false;
for (const [key, expected] of Object.entries(condition.where ?? {})) {
if (event.payload[key] !== expected) return false;
}
return true;
}
这个函数很朴素,但它避免了玩法代码直接关心任务。玩法只发事件,任务系统自己匹配。后续要支持 gte、in、tag includes,也可以在匹配器里扩展。
调试任务要能手动发事件
任务系统必须有调试工具。开发版可以列出所有任务状态,手动激活任务、完成任务、重置任务、模拟事件。测试“使用火焰技能击败冰怪”时,不应该每次真的进关卡打一遍。
事件日志也很重要。某个任务不涨进度时,要能看到最近是否收到对应事件,payload 是否匹配,任务是否 active,条件为什么失败。没有这些信息,工程只能在多个玩法系统里找。
配置校验工具可以输出任务依赖图,检查前置循环和不可达任务。主线任务尤其要重视这一点。一个前置 id 写错,玩家可能卡在整个新手流程。
上线前检查清单
上线前检查:玩法事件是否稳定,任务条件是否结构化,任务状态机是否清楚,奖励领取是否幂等,活动过期策略是否明确,追踪 UI 是否不自己算进度,配置是否校验,调试工具是否能模拟事件。
还要测试重复事件、离线完成、战斗中完成、背包满领取、任务过期、旧存档打开新任务表、服务端奖励失败。任务系统一旦影响成长和活动,就不能只测成功路径。
主线、日常和成就不要混成一种任务
任务系统常见问题是把所有目标都塞进一张表。主线任务强调顺序和引导,日常任务强调刷新和活跃,成就强调长期累计,活动任务强调时间和奖励。它们可以共用条件匹配器,但生命周期不同。全部混成一种状态机,后期会出现大量特殊字段。
比较稳的方式是抽公共能力:条件、进度、奖励、状态;再给不同任务域定义策略。主线有前置链和章节,日常有刷新时间和重置,成就通常不重置,活动任务有活动窗口和补领策略。UI 也可以复用列表组件,但入口和排序不同。
这种分层能减少很多奇怪配置。比如日常任务的 progress 在每天重置,成就 progress 永久保留;活动任务过期后可能隐藏,主线任务不会过期。策略写清楚,运营配置才不会把“每日击杀 10 个怪”误配成永久成就。
任务跳转要保护玩家上下文
任务追踪 UI 常提供“前往”按钮。点击后可能进入关卡、打开商店、跳到活动页或高亮 NPC。跳转前要考虑玩家当前状态:是否在战斗中,是否有未保存编辑,是否正在结算,是否背包满。不要让任务跳转直接打断关键流程。
可以让任务配置只描述目标位置,导航服务决定何时执行。若当前不能跳转,显示原因或排队到安全点。比如战斗中点击任务,只标记战斗结束后打开任务面板;大厅中点击任务,则直接进入目标关卡。
跳转失败也要可解释。目标关卡未解锁、活动已结束、资源未下载、网络不可用,都应该给出不同提示。任务系统的目标是引导,不是把玩家送进死路。
任务数据要服务运营复盘
任务上线后,运营会问很多问题:哪一步新手流失最高,哪个日常没人做,哪个活动任务完成率异常低,奖励领取率为什么低。客户端要记录任务曝光、接受、进度变化、完成、领取和跳转。不要只在领取奖励时埋点,那样看不到玩家卡在哪里。
埋点要带任务版本和配置版本。活动任务经常调整目标数和奖励,如果数据不带版本,就无法比较调整前后效果。任务 id 也要稳定,不要每次改文案就换 id。数据口径稳定,运营才敢基于它做判断。
任务追踪 UI 的排序也可以通过数据优化。如果玩家经常点击某类任务,说明它值得更靠前;如果某个任务曝光很多但完成率低,要检查跳转是否清楚、目标是否过难或奖励是否不吸引。任务系统不是写完配置就结束,它会直接影响玩家路径。
数据复盘也能发现配置事故。某个任务完成率突然为零,可能不是玩家不做,而是事件名改了、条件写错了、目标关卡被下架了。任务系统要能把这种异常尽早暴露出来。
可以给任务配置做灰度前检查:模拟关键事件流,确认任务能从 active 走到 completed,再走到 claimed。这个检查比上线后看报表更主动。
任务还要处理跨天和跨版本。玩家在 23:59 完成日常但 00:00 才领取,应该按哪天算?活动配置更新时,旧进度是否保留?这些规则要写在策略里。否则边界时间会产生大量客服问题,尤其是日常和战令类系统。
跨天刷新最好有服务端时间和刷新批次 id。客户端显示倒计时,真正重置由批次决定。这样玩家改本地时间不会影响任务状态。
批次 id 也能帮助客服判断玩家领取的是哪一天的奖励,减少跨天争议。
如果任务奖励很重要,领取日志也要带批次 id、任务版本和奖励摘要。这样玩家反馈“昨天的奖励没收到”时,客服能直接查证,而不是让工程翻客户端日志。
结语
Phaser 任务系统不需要复杂脚本,但需要事件边界。玩法发布事实,任务匹配条件,状态机管理进度,奖励服务负责发放,UI 只展示和导航。把这些职责分开,任务类型才能不断增加而不压垮玩法代码。
任务的价值是引导玩家,不是给工程制造散落监听。只要事件、配置、状态和奖励闭环清楚,Phaser 小游戏也能承载主线、日常、活动和成就,而不是每加一个任务就改一遍战斗代码。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。