任务系统
Category
system / 玩法系统。节点定位:任务把「策划写的一段玩法叙事」变成「服务端可推进、可发奖、可对账的进度对象」——定义、进度、链式前置、奖励发放四件事。数据表管线在 配置表与数据驱动管线、奖励凭证在 结算、产出与回收的数值口径在 numerical 树,本页只管进度机制本身。
Definition
任务是「带条件的进度对象」:一条任务定义给出目标(做什么、做几次)、前置(能不能接)、奖励(做完了给什么),玩家侧持有一条进度记录。它与成就同构——都是事件驱动的计数器与状态判定——区别在任务有接取与交付两个显式节点、可被放弃与重接,成就的进度只增不减、无交付动作。任务与成就共用同一套进度模型,分开写只是因为运营侧对两者的心智不同。
Problem
任务的坑按出现顺序:
- 进度与业务耦合:把「击杀 3 只狼」的判断写死在战斗代码里,加一个任务就要改一次战斗服。任务量一大,战斗代码里全是
if quest_id == 1001。 - 链式前置断链:任务 A 完成要解锁任务 B,但玩家的进度记录与前置记录分两处存,前置判定用旧快照,出现「做了 A 却接不了 B」。
- 重复发奖:交付动作重试、重连重放、崩溃恢复都可能把同一笔奖励发两次或零次——与结算同款的幂等问题。
- 进度回滚:事件重复投递(网络重发)让「击杀数」多计,或服务端重启丢了内存中的进度。
先定「谁推进进度」(事件源),再定「何时发奖」(交付提交点),最后定「前置怎么判」——顺序反了,链式任务的事后补丁最难写。
Algorithm
进度模型:定义与运行态分离
任务定义是静态数据,运行态只有一条进度记录:
quest_def: # 静态表,策划维护,热更可换
id: q-1001
objectives: [ {type: kill, key: wolf, need: 3},
{type: collect, key: pelt, need: 2} ]
prereq: [ q-1000 ]
rewards: [ {item: gem, n: 10}, {exp: 500} ]
rule_version: v3
quest_progress: # 运行态,每玩家每条任务一行
player: u1001
quest: q-1001
state: in_progress # locked / available / in_progress / completed / rewarded
counters: { kill:wolf: 1, collect:pelt: 0 }
updated_at: 2026-10-09T10:00:00Z运行态只存「计数器 + 状态」,不存文案与奖励——那些每次从定义表读。这样改文案、调奖励只动表,不动玩家数据。
进度推进:事件驱动,而非轮询
服务端把玩家行为发成统一事件(击杀、拾取、到达、对话),任务层订阅事件并匹配 (type, key) 自增计数器:
- 事件源要收敛:战斗、拾取、移动各出各的事件,但格式统一成
{player, type, key, n},任务层只认这一个格式 - 自增要幂等:事件带唯一的
event_id,进度记录里存最近处理过的 event_id 或按(quest, objective, event_id)去重,网络重发不会多计 - 计数只增不减是简化前提:需要「减少」的任务(如「保持不被发现」)用状态判定而非计数器,单独一类
链式前置:状态机,不是快照
任务状态机与前置的关系:
| 状态 | 含义 | 进入条件 |
|---|---|---|
| locked | 前置未满足 | 初始态,前置任务未 rewarded |
| available | 可接取 | 全部前置 rewarded(按运行态实时判定,非快照) |
| in_progress | 进行中 | 玩家接取 |
| completed | 目标达成待交付 | 所有 objective 计数达标 |
| rewarded | 已发奖终结 | 交付提交成功 |
前置判定必须读运行态而非缓存快照——这是「做了 A 却接不了 B」的根因。解锁在「前置任务 rewarded」的那一刻写一条「待解锁」记录,玩家上线时按记录刷 available,避免全表扫描。
奖励发放:交付即提交点
交付(交付动作那一刻)是提交点,与结算共用凭证口径:
- 交付时先写奖励凭证
(player, quest_id, rewards, rule_version),再落库——凭证先行,崩溃可重放 - 落库按
(player, quest_id)去重——重复交付直接跳过 - 一次性任务与日常任务的差别只在重置规则:日常按服务器日重置进度(重置口径见内文体力页同类问题),一次性任务 rewarded 即终态
自动交付 vs 手动交付
| 形态 | 触发 | 优点 | 代价 |
|---|---|---|---|
| 自动交付 | 目标达成即发奖 | 少一次点击,反馈即时 | 玩家看不到「完成」仪式感;连线中断时发奖时机难对齐 |
| 手动交付 | 玩家点「交付」 | 明确的完成仪式;交付前可放弃 | 需处理「完成了但没交」的悬挂态 |
| 混合 | 日常自动、主线手动 | 各取所长 | 两套路径都要测 |
Used By
- TrinityCore(开源 MMORPG 服务端套件,公开仓库与 wiki):任务定义、达成条件、奖励各自成数据表,服务端按事件推进进度,加任务不改引擎代码——这是本页「定义与运行态分离」的直接参照。具体表名以项目公开文档为准。
- WoW(商业 MMO):任务链、日常/周常任务、任务日志上限的公开设计事实,作为链式前置与重置规则的商业参照。
- FF14(商业 MMO):主线任务门槛与任务同步机制(主线进度锁副本解锁)作为「任务作为玩法闸门」的参照。
- 原神(商业手游):每日委托的每日重置与纪行任务联动,作为日常任务与运营任务两种形态并存的参照。
可参考分析:TrinityCore 任务数据表化=可参考(定义与运行态分离直接落本页进度模型);WoW 任务链与日常=可借鉴(链式前置与重置规则落状态机与一次性/日常一节);FF14 主线锁副本=可借鉴(任务作玩法闸门,落点 design 树进度设计);原神每日委托=可参考(日常重置与运营任务并存,落次数门控窗口表);事件驱动幂等=可借鉴(落进度推进一节,与结算凭证口径对齐)。
Related
关系链:进度模型 → 事件推进 → 链式前置 → 奖励发放 → 交付形态。逐段回答「为什么需要下一个」:
- 定义了进度对象,才谈得上推进——没有运行态,事件无处可落。
- 能推进了才要前置——链式任务的前置判定必须读运行态,否则断链。
- 前置顺畅了才发奖——交付是提交点,凭证先行保证不重发不漏发。
- 发奖路径定了才选交付形态——自动与手动决定「完成」到「发奖」之间有没有人。
上游:配置表与数据驱动管线(定义表怎么热更)、技能系统(任务目标的常见事件源);下游:成就与图鉴(同构的事件订阅)、结算(奖励凭证的落库口径)、掉落与概率设计(任务奖励的数值侧)。
Reference
- 本页为机制归纳;TrinityCore 的表结构以其公开仓库与 wiki 为准,不臆造表名与列名。
- 奖励凭证与幂等口径:结算 与 交易、支付与经济系统。
- 术语对照:Quest(任务)、Objective(任务目标)、Prerequisite(前置)、Turn-in(交付)、Daily Reset(每日重置)。