主题
任务系统(Quest System)
任务系统是 WasiCore 游戏框架中用于管理任务(Quest)和章节(Chapter)的核心系统。系统采用服务端权威架构,所有任务状态变更均在服务端执行,客户端通过 UI 接口接收通知。
目录
系统概述
命名空间:TriggerEncapsulation.QuestSystem;数编类型位于 .Data,任务状态和目标类型枚举位于 .Data.Enum。
本文签名已对照当前双端 API 和运行时源码核对。完整声明见 服务端 API 和 客户端 API。任务、目标、章节链接分别使用 IGameLink<GameDataQuest>、IGameLink<GameDataQuestObjective>、IGameLink<GameDataQuestChapter>。
| 特性 | 说明 |
|---|---|
| 运行端 | 服务端修改权威状态,双端提供查询,客户端实现 UI |
| 注册方式 | 内置 QuestSystem : IGameClass 在数编初始化时初始化管理器和双端事件监听 |
| 事件驱动 | RegisterEventListeners 自动将击杀、物品拾取等游戏事件关联到任务目标 |
| 客户端通知 | 通过 IQuestUI / IChapterUI 接口推送 UI 更新 |
常规项目由内置 QuestSystem 完成初始化:服务端调用 QuestManager.RegisterEventListeners() 和 ChapterManager.Initialize();客户端调用 Quest.InitializeClientEventListeners() 和 ChapterManager.InitializeClient()。业务代码只需初始化玩家的章节数据、接入任务操作和绑定 UI,避免再次注册同一组监听器。手动管理生命周期时,才使用对应的初始化和清理 API。
核心类
QuestManager(任务管理器)
QuestManager 是静态类,提供任务生命周期管理的全部入口。
初始化与清理
| 方法 | 签名 | 说明 |
|---|---|---|
Initialize | void Initialize() | 初始化任务管理器;常规流程由内置系统调用 |
Clear | void Clear() | 清除所有玩家的任务和可接受任务记录 |
ClearPlayer | void ClearPlayer(Player player) | 清除指定玩家的任务和可接受任务记录 |
任务创建与获取
| 方法 | 签名 | 说明 |
|---|---|---|
CreateQuest | Quest? CreateQuest(Player player, IGameLink<GameDataQuest> questLink) | 创建实例,不自动接受;数据无效时返回 null |
GetQuest | Quest? GetQuest(Player player, IGameLink<GameDataQuest> questLink) | 获取指定任务,不存在时返回 null |
GetAllQuests | IEnumerable<Quest> GetAllQuests(Player player) | 获取全部任务 |
GetActiveQuests | IEnumerable<Quest> GetActiveQuests(Player player) | 获取 InProgress 和 Completed 任务 |
GetInProgressQuests | IEnumerable<Quest> GetInProgressQuests(Player player) | 获取进行中的任务 |
GetCompletedQuests | IEnumerable<Quest> GetCompletedQuests(Player player) | 获取目标达成、等待提交的任务 |
GetTrackedQuests | IEnumerable<Quest> GetTrackedQuests(Player player) | 获取正在追踪的任务 |
RemoveQuest | void RemoveQuest(Player player, Quest quest) | 从管理器中移除实例记录 |
任务操作
| 方法 | 签名 | 说明 |
|---|---|---|
AcceptQuest | CmdResult AcceptQuest(Player player, IGameLink<GameDataQuest> questLink) | 创建并接受任务 |
TurnInQuestAsync | ValueTask<CmdResult> TurnInQuestAsync(Player player, IGameLink<GameDataQuest> questLink) | 提交任务并发奖 |
AbandonQuest | CmdResult AbandonQuest(Player player, IGameLink<GameDataQuest> questLink) | 放弃任务 |
这三个入口在双端均有声明,但接受、提交、放弃必须由服务端执行;客户端不能通过它们修改服务端任务。检查返回值的 IsSuccess / Error,异步方法先 await 再检查结果。
AcceptQuest 会先创建任务实例,再验证前置条件。接受失败后,GetQuest 仍可能返回 Inactive 实例;实例存在不代表已接取。前置任务提交后可以再次接受这个实例。交互逻辑应检查 CanAccept、CanTurnIn 和操作结果,不能无条件把 Failed / Abandoned 当成可重新接取。
任务可用性
| 方法 | 签名 | 说明 |
|---|---|---|
NotifyQuestAvailable | void NotifyQuestAvailable(Player player, IGameLink<GameDataQuest> questLink) | 标记任务可接受 |
IsQuestAvailable | bool IsQuestAvailable(Player player, IGameLink<GameDataQuest> questLink) | 检查可接受标记或前置任务 |
GetAvailableQuests | IEnumerable<IGameLink<GameDataQuest>> GetAvailableQuests(Player player) | 枚举通过 NotifyQuestAvailable 标记的任务链接 |
GetAvailableQuests 枚举显式标记的任务,不扫描所有任务定义。成功接取后会移除对应标记,接取失败则保留。NotifyQuestAvailable 不创建任务实例;可接受标记与 IsQuestAvailable 都不能替代 AcceptQuest 的实际接受验证。
事件注册与目标通知
| 方法 | 签名 | 说明 |
|---|---|---|
RegisterEventListeners | void RegisterEventListeners() | 注册击杀、物品变化监听;常规流程由内置系统调用 |
UnregisterEventListeners | void UnregisterEventListeners() | 注销上述监听 |
CheckAllTimeouts | void CheckAllTimeouts() | 检查并处理所有进行中任务的超时 |
NotifyTalkToNpc | void NotifyTalkToNpc(Player player, IGameLink<GameDataUnit> npcUnit) | 完成匹配该 NPC 类型的对话目标 |
NotifyCustomObjectiveProgress | void NotifyCustomObjectiveProgress(Player player, string customId, int delta = 1) | 按 CustomId 增加目标进度 |
NotifyCustomObjectiveComplete | void NotifyCustomObjectiveComplete(Player player, string customId) | 按 CustomId 完成目标 |
本组 API 仅服务端可用。两种自定义目标通知会遍历玩家所有进行中的任务,更新其中匹配 GameDataQuestObjectiveCustom.CustomId 的目标。只更新一个任务时,先 GetQuest,再调用该实例的 UpdateObjectiveProgress / CompleteObjective。delta 是增量;设置绝对值应使用 QuestObjective.SetProgress。
Quest(任务)
Quest 类表示一个任务实例,包含任务的运行时状态。
属性
| 属性 | 类型 | 说明 |
|---|---|---|
Link | IGameLink<GameDataQuest> | 任务数编表链接 |
Cache | GameDataQuest | 任务数编表缓存 |
Player | Player | 所属玩家 |
Status | QuestStatus | 当前状态 |
AcceptedTime | DateTime? | 接受时间;未接受时为 null |
CompletedTime | DateTime? | 完成时间 |
IsTracked | bool | 是否被追踪 |
AcceptCount | int | 接受次数(可重复任务) |
Objectives | IReadOnlyList<QuestObjective> | 只读任务目标列表 |
RequiredObjectives | IEnumerable<QuestObjective> | 必要目标 |
OptionalObjectives | IEnumerable<QuestObjective> | 可选目标 |
AllRequiredObjectivesCompleted | bool | 是否所有必要目标均已完成 |
TotalProgress | float | 总体进度(0.0 ~ 1.0) |
IsActive | bool | 是否活跃(InProgress 或 Completed) |
CanAccept | bool | 是否可接受 |
CanTurnIn | bool | 是否可提交 |
CanAbandon | bool | 是否可放弃 |
IsExpired | bool | 是否已过期 |
RemainingTime | TimeSpan? | 剩余时间(限时任务) |
方法
| 方法 | 签名 | 说明 |
|---|---|---|
GetObjective | QuestObjective? GetObjective(int index) | 双端:获取目标,不存在时返回 null |
GetObjectiveByCustomId | QuestObjective? GetObjectiveByCustomId(string customId) | 双端:获取匹配 CustomId 的目标 |
Accept | CmdResult Accept() | SERVER:接受任务 |
TurnInAsync | ValueTask<CmdResult> TurnInAsync() | SERVER:提交任务 |
Abandon | CmdResult Abandon() | SERVER:放弃任务 |
Fail | CmdResult Fail(string? reason = null) | SERVER:标记失败,可提供原因 |
SetTracked | void SetTracked(bool tracked) | SERVER:设置追踪状态 |
UpdateObjectiveProgress | bool UpdateObjectiveProgress(int objectiveIndex, int delta = 1) | SERVER:按索引增加进度 |
UpdateObjectiveProgress | bool UpdateObjectiveProgress(string customId, int delta = 1) | SERVER:按 CustomId 增加进度 |
CompleteObjective | void CompleteObjective(int objectiveIndex) | SERVER:按索引完成目标 |
CompleteObjective | void CompleteObjective(string customId) | SERVER:按 CustomId 完成目标 |
CheckTimeout | bool CheckTimeout() | SERVER:超时且仍进行中时标记失败,返回是否处理超时 |
QuestObjective(任务目标)
QuestObjective 表示任务内的一个具体目标。
属性
| 属性 | 类型 | 说明 |
|---|---|---|
Link | IGameLink<GameDataQuestObjective> | 目标数编表链接 |
Cache | GameDataQuestObjective | 目标数编表缓存 |
Quest | Quest | 所属任务 |
Index | int | 目标在任务中的索引 |
CurrentProgress | int | 当前进度 |
TargetCount | int | 目标数量 |
IsCompleted | bool | 是否已完成 |
IsOptional | bool | 是否为可选目标 |
IsHidden | bool | 是否隐藏(不在 UI 中显示) |
ObjectiveType | QuestObjectiveType | 目标类型(击杀、收集、对话、自定义等) |
ProgressPercentage | float | 进度百分比(0.0 ~ 1.0) |
方法
| 方法 | 签名 | 说明 |
|---|---|---|
GetDescription | string GetDescription() | 双端:获取格式化描述 |
UpdateProgress | bool UpdateProgress(int delta) | SERVER:增加进度,返回进度是否变化 |
SetProgress | void SetProgress(int progress) | SERVER:设置绝对进度值 |
Complete | void Complete() | SERVER:直接完成目标 |
Reset | void Reset() | SERVER:重置进度 |
目标类型与收集交付
| 类型 | 进度来源 | 适用场景 |
|---|---|---|
GameDataQuestObjectiveKill | 自动监听单位死亡,按目标、击杀来源和位置限制匹配 | 击杀任务;同一死亡事件不要再手动加进度 |
GameDataQuestObjectiveTalkTo | 业务调用 NotifyTalkToNpc(player, npcLink) | NPC 对话完成 |
GameDataQuestObjectiveCollect | 自动监听物品入栏、移出和堆叠变化 | 标准物品栏中的材料收集与交付 |
GameDataQuestObjectiveCustom | 业务主动更新指定 CustomId 或目标索引 | 比赛结果、探索条件、项目自行管理的虚拟材料等 |
收集目标的计数与扣物
收集目标统计玩家所属单位的物品栏,支持多个载体和背包。可堆叠物品按 Stack 计数,不可堆叠物品按实例计数;地面物品不计入。TargetItems 非空时,匹配列表中任意物品并合计数量;未提供物品列表时,可用 ItemCategory 匹配物品分类的枚举名称,包括项目扩展分类。
TrackCurrentInventory = true:接任务时读取已有材料,之后按当前持有量更新。丢弃、转移或消耗材料会降低进度,必要目标不再满足时,任务由Completed回到InProgress。同一次交换或堆叠合并产生的变化合并到下一游戏 Tick 更新,避免显示操作中间态。TrackCurrentInventory = false:只累计接取后新入栏的材料和背包中的堆叠增长,移出不会减少进度。同一玩家背包内的移动、交换和堆叠合并不重复增加进度。ConsumeOnComplete = true:提交时重新检查材料,并在提交效果和奖励之前扣除所需数量。多个收集目标共享材料时统一分配,不能重复消费同一份材料;材料不足返回失败,不进行部分扣除。未完成的可选收集目标不扣物。ConsumeOnComplete = false:提交不扣物。
普通物品收集可以直接使用 Collect,自定义掉落代码只需通过正常的物品栏接口把物品交给玩家。使用独立于物品栏的项目存储时,可以选择 Custom,并由项目负责计数、提交验证与扣除。不要因为进度暂未变化,就以 CompleteObjective 强制完成作为兜底;先检查任务状态、CustomId、更新范围和实际材料数量。
下面的完整定义示例在项目的数编初始化阶段调用 Define;材料和奖励物品链接由项目提供。奖励对象必须加入任务的 Rewards,仅定义奖励不会发放。GameDataRewardItem.Amount 是 FuncUInt 委托,使用 static _ => 1u 表示发放一个物品实例。
csharp
using GameCore.ItemSystem.Data;
using GameCore.RewardSystem.Data;
using GameData;
using GameData.Interface;
using TriggerEncapsulation.QuestSystem.Data;
public static class CollectQuestExample
{
private static readonly GameLink<GameDataQuestObjective, GameDataQuestObjectiveCollect> ObjectiveLink =
new("guide_collect_material"u8);
private static readonly GameLink<GameDataReward, GameDataRewardItem> RewardLink =
new("guide_collect_reward"u8);
public static readonly GameLink<GameDataQuest, GameDataQuest> QuestLink =
new("guide_collect_quest"u8);
public static void Define(IGameLink<GameDataItem> material, IGameLink<GameDataItem> rewardItem)
{
_ = new GameDataQuestObjectiveCollect(ObjectiveLink)
{
Description = new("收集材料 {0}/{1}"),
TargetItems = [material],
TargetCount = 3,
TrackCurrentInventory = true,
ConsumeOnComplete = true,
};
_ = new GameDataRewardItem(RewardLink)
{
Item = rewardItem,
Amount = static _ => 1u,
};
_ = new GameDataQuest(QuestLink)
{
Name = "guide_collect_quest",
DisplayName = new("药商的委托"),
Objectives = [ObjectiveLink],
Rewards = [RewardLink],
AutoTrack = true,
};
}
}任务 UI 的 QuestInfo.Name 优先取 DisplayName,未设置时回退到任务链接的友好名称。LocalizedString 支持本地化查询,找不到资源时显示原始 key;项目可按自己的本地化方案提供翻译。
章节系统
章节系统在任务之上提供了更高层级的进度管理,一个章节包含若干任务。
ChapterManager(章节管理器)
ChapterManager 是静态类,管理所有章节进度。
| 方法 | 签名 | 说明 |
|---|---|---|
GetChapterProgress | ChapterProgress? GetChapterProgress(Player player, IGameLink<GameDataQuestChapter> chapterLink) | 双端:查询进度,不存在时返回 null |
GetOrCreateChapterProgress | ChapterProgress GetOrCreateChapterProgress(Player player, IGameLink<GameDataQuestChapter> chapterLink) | 双端:获取或创建进度 |
GetAllChapterProgress | IEnumerable<ChapterProgress> GetAllChapterProgress(Player player) | 双端:查询全部进度 |
GetUnlockedChapters | IEnumerable<ChapterProgress> GetUnlockedChapters(Player player) | 双端:查询已解锁章节 |
GetInProgressChapters | IEnumerable<ChapterProgress> GetInProgressChapters(Player player) | 双端:查询进行中章节 |
GetCompletedChapters | IEnumerable<ChapterProgress> GetCompletedChapters(Player player) | 双端:查询已完成章节 |
IsChapterUnlocked | bool IsChapterUnlocked(Player player, IGameLink<GameDataQuestChapter> chapterLink) | 双端:查询是否已解锁 |
IsChapterCompleted | bool IsChapterCompleted(Player player, IGameLink<GameDataQuestChapter> chapterLink) | 双端:查询是否已完成 |
InitializePlayerChapters | void InitializePlayerChapters(Player player, IEnumerable<IGameLink<GameDataQuestChapter>> allChapters) | 双端有声明;权威玩家数据应在服务端初始化 |
ClearPlayerData | void ClearPlayerData(Player player) | 双端:清除玩家章节记录 |
ClearAll | void ClearAll() | 双端:清除全部章节记录 |
Initialize / Shutdown | void Initialize() / void Shutdown() | SERVER:注册/注销章节事件监听 |
UnlockChapter | CmdResult UnlockChapter(Player player, IGameLink<GameDataQuestChapter> chapterLink) | SERVER:解锁章节 |
StartChapter | CmdResult StartChapter(Player player, IGameLink<GameDataQuestChapter> chapterLink) | SERVER:开始章节 |
ClaimChapterRewardsAsync | ValueTask<CmdResult> ClaimChapterRewardsAsync(Player player, IGameLink<GameDataQuestChapter> chapterLink) | SERVER:领取章节奖励 |
客户端还提供以下入口:
| 签名 | 说明 |
|---|---|
void BindUI(IChapterUI ui) / void UnbindUI() | 绑定/解绑章节 UI |
void InitializeClient() / void ShutdownClient() | 注册/注销客户端章节监听,常规初始化由内置系统完成 |
ChapterProgress? GetLocalPlayerChapterProgress(IGameLink<GameDataQuestChapter> chapterLink) | 获取本地玩家指定章节 |
IEnumerable<ChapterProgress> GetLocalPlayerAllChapterProgress() | 获取本地玩家全部章节 |
IEnumerable<ChapterProgress> GetLocalPlayerUnlockedChapters() | 获取本地玩家已解锁章节 |
IEnumerable<ChapterProgress> GetLocalPlayerInProgressChapters() | 获取本地玩家进行中章节 |
IEnumerable<ChapterProgress> GetLocalPlayerCompletedChapters() | 获取本地玩家已完成章节 |
章节完成计数在任务提交(TurnedIn)时更新。仅达成目标(Completed)尚未计入已提交任务数。
ChapterProgress(章节进度)
| 属性 | 类型 | 说明 |
|---|---|---|
Link | IGameLink<GameDataQuestChapter> | 章节数编表链接 |
Cache | GameDataQuestChapter | 章节数编表缓存 |
Status | ChapterStatus | 当前状态 |
CompletedQuestCount | int | 已完成的任务数量 |
TotalQuestCount | int | 总任务数量 |
Progress | float | 完成进度(0.0 ~ 1.0) |
IsCompleted | bool | 是否已完成 |
CanClaimRewards | bool | 是否可以领取奖励 |
UnlockedTime | DateTime? | 解锁时间 |
CompletedTime | DateTime? | 完成时间 |
ChapterStatus 枚举
| 枚举值 | 说明 |
|---|---|
Locked | 未解锁 |
Unlocked | 已解锁但未开始 |
InProgress | 进行中 |
Completed | 已完成(可领奖) |
RewardsClaimed | 奖励已领取 |
客户端 UI 接口
客户端通过 UI 接口接收任务和章节事件通知。任务 UI 使用 QuestUI.Register,章节 UI 使用 ChapterManager.BindUI,两者需要分别绑定。
IQuestUI
| 回调方法 | 说明 |
|---|---|
void OnQuestAccepted(QuestInfo info) | 任务被接受 |
void OnQuestCompleted(QuestInfo info) | 必要目标达成 |
void OnQuestTurnedIn(QuestInfo info) | 任务已提交 |
void OnQuestFailed(QuestInfo info, string? reason) | 任务失败及失败原因 |
void OnQuestAbandoned(QuestInfo info) | 任务被放弃 |
void OnObjectiveProgress(QuestInfo questInfo, QuestObjectiveInfo objectiveInfo) | 目标进度更新 |
void OnObjectiveCompleted(QuestInfo questInfo, QuestObjectiveInfo objectiveInfo) | 目标完成 |
void OnTrackingChanged(QuestInfo info, bool isTracked) | 追踪状态变化 |
void ShowQuestDetail(QuestInfo info) | 显示任务详情 |
void HideQuestDetail() | 隐藏任务详情 |
void RefreshQuestList(IEnumerable<QuestInfo> quests) | 刷新任务列表 |
void RefreshTracker(IEnumerable<QuestInfo> trackedQuests) | 刷新追踪面板 |
以上 12 个方法均需实现。回调参数是展示数据,任务名称使用 info.Name,目标进度使用 objectiveInfo.CurrentProgress / TargetCount。QuestInfo.Link 可用于查找任务;QuestInfo 本身不提供 Cache 或任务操作方法。
QuestInfo 包含 Link、Name、Description、Category、Status、IsTracked、TotalProgress、Objectives、RemainingTime、RequiredLevel、CanTurnIn、CanAbandon。QuestObjectiveInfo 包含 Index、ObjectiveType、Description、CurrentProgress、TargetCount、IsCompleted、IsOptional、IsHidden、ProgressPercentage。
IChapterUI
| 回调方法 | 说明 |
|---|---|
void OnChapterUnlocked(IGameLink<GameDataQuestChapter> chapter, Player player) | 章节解锁 |
void OnChapterProgressUpdated(IGameLink<GameDataQuestChapter> chapter, Player player, int completedQuests, int totalQuests) | 章节进度更新 |
void OnChapterCompleted(IGameLink<GameDataQuestChapter> chapter, Player player) | 章节完成 |
void OnChapterRewardsClaimed(IGameLink<GameDataQuestChapter> chapter, Player player) | 章节奖励已领取 |
void RefreshChapterList(IEnumerable<ChapterProgress> chapters) | 刷新章节列表 |
void ShowChapterDetails(ChapterProgress chapter) | 显示章节详情 |
void HideChapterDetails() | 隐藏章节详情 |
void ShowChapterCompleteCelebration(ChapterProgress chapter, IEnumerable<string>? rewards = null) | 显示完成庆祝界面 |
IChapterUI 提供默认空实现,可按需覆盖。前四个事件回调接收章节链接和玩家;需要完整进度时,通过 ChapterManager.GetChapterProgress(player, chapter) 查询。后四个方法供项目按自己的 UI 流程调用。
QuestUI
QuestUI 是 UI 注册的静态入口。
| 方法 | 签名 | 说明 |
|---|---|---|
Register | void Register(IQuestUI ui) | 注册任务 UI 回调处理器 |
Unregister | void Unregister() | 取消注册 |
CreateInfo | QuestInfo CreateInfo(Quest quest) | 从任务实例生成展示数据,目标列表排除隐藏目标 |
CreateObjectiveInfo | QuestObjectiveInfo CreateObjectiveInfo(QuestObjective objective) | 生成目标展示数据 |
QuestUI.Current 的类型是 IQuestUI?。这些辅助 API 在双端有声明,UI 绑定和展示操作应放在客户端。客户端还可调用 Quest.RefreshQuestListUI()、Quest.ShowQuestDetailUI(Quest quest)、Quest.HideQuestDetailUI()。
任务状态流转
Accept()
Inactive ──────────────────> InProgress
│
┌───────────────┼───────────────┐
│ │ │
Fail() 必要目标完成 Abandon()
│ │ │
v v v
Failed Completed Inactive
│
TurnInAsync()
│
v
TurnedIn| 状态 | 说明 |
|---|---|
Inactive | 未接受或已放弃 |
InProgress | 进行中 |
Completed | 必要目标达成,等待提交(可选目标不影响提交) |
TurnedIn | 已提交,任务完结 |
Failed | 任务失败(超时或手动标记) |
使用示例
以下示例分别是完整的 C# 类,包含所需 using 和运行端条件。把入口接到项目的玩家加入、交互和离开流程中,传入项目已有的数编链接。
1. 初始化玩家章节与任务记录
内置系统完成管理器和事件监听初始化后,业务代码初始化玩家数据。allChapters 是章节链接集合;项目应从自己的数编定义提供该集合。
csharp
#if SERVER
using System.Collections.Generic;
using GameCore.PlayerAndUsers;
using GameData;
using TriggerEncapsulation.QuestSystem;
using TriggerEncapsulation.QuestSystem.Data;
public static class ServerQuestSetup
{
public static void OnPlayerJoin(
Player player,
IEnumerable<IGameLink<GameDataQuestChapter>> allChapters,
IGameLink<GameDataQuest> tutorialQuest)
{
ChapterManager.InitializePlayerChapters(player, allChapters);
QuestManager.NotifyQuestAvailable(player, tutorialQuest);
}
public static void OnPlayerLeave(Player player)
{
QuestManager.ClearPlayer(player);
ChapterManager.ClearPlayerData(player);
}
}
#endif这两个清理入口移除管理器中的玩家记录。是否在离开时清理,应结合项目的重连和存档策略决定。
2. 接受、更新目标、提交和领奖
csharp
#if SERVER
using System.Threading.Tasks;
using GameCore.BaseType;
using GameCore.EntitySystem.Data;
using GameCore.PlayerAndUsers;
using GameData;
using TriggerEncapsulation.QuestSystem;
using TriggerEncapsulation.QuestSystem.Data;
public static class ServerQuestFlow
{
public static CmdResult Accept(
Player player, IGameLink<GameDataQuest> questLink)
{
var result = QuestManager.AcceptQuest(player, questLink);
if (!result.IsSuccess) return result;
QuestManager.GetQuest(player, questLink)?.SetTracked(true);
return result;
}
// 更新所有进行中任务中匹配 CustomId 的自定义目标。
public static void AddSharedCustomProgress(Player player, string customId)
{
QuestManager.NotifyCustomObjectiveProgress(player, customId, delta: 1);
}
// 只更新指定任务,objectiveIndex 是从 0 开始的目标索引。
public static bool AddSpecificProgress(
Player player, IGameLink<GameDataQuest> questLink, int objectiveIndex)
{
var quest = QuestManager.GetQuest(player, questLink);
if (quest is null || !quest.IsActive) return false;
return quest.UpdateObjectiveProgress(objectiveIndex, delta: 1);
}
// progress 是绝对值,与上面的 delta 区别开。
public static void SetSpecificProgress(
Player player, IGameLink<GameDataQuest> questLink,
int objectiveIndex, int progress)
{
var quest = QuestManager.GetQuest(player, questLink);
if (quest is null || !quest.IsActive) return;
quest.GetObjective(objectiveIndex)?.SetProgress(progress);
}
public static void CompleteSharedCustomObjective(Player player, string customId)
{
QuestManager.NotifyCustomObjectiveComplete(player, customId);
}
public static void OnNpcInteraction(
Player player, IGameLink<GameDataUnit> npcUnit)
{
// 在项目所需的交互或对话完成后通知。
QuestManager.NotifyTalkToNpc(player, npcUnit);
}
public static async ValueTask<CmdResult> TurnIn(
Player player, IGameLink<GameDataQuest> questLink)
{
var result = await QuestManager.TurnInQuestAsync(player, questLink);
if (!result.IsSuccess) return result;
// 此处才执行项目自己的提交成功反馈。
return result;
}
public static CmdResult StartChapter(
Player player, IGameLink<GameDataQuestChapter> chapterLink)
{
// StartChapter 会在章节 Locked 时先尝试解锁,并返回验证结果。
return ChapterManager.StartChapter(player, chapterLink);
}
public static async ValueTask<CmdResult> ClaimChapterRewards(
Player player, IGameLink<GameDataQuestChapter> chapterLink)
{
return await ChapterManager.ClaimChapterRewardsAsync(player, chapterLink);
}
}
#endif调用者检查返回的 CmdResult.IsSuccess,失败原因可从 Error 获取。自定义目标的 customId 必须与数编中的 GameDataQuestObjectiveCustom.CustomId 一致。使用自动击杀目标时,不要在同一个死亡事件中再手动增加该目标的进度。
3. 客户端完整 UI 接口骨架
下面实现全部任务 UI 方法,并覆盖章节 UI 方法。将空方法体替换为项目自己的 GameUI 更新;展示名称使用 QuestInfo.Name。章节 UI 必须单独调用 ChapterManager.BindUI。
csharp
#if CLIENT
using System;
using System.Collections.Generic;
using GameCore.PlayerAndUsers;
using GameData;
using TriggerEncapsulation.QuestSystem;
using TriggerEncapsulation.QuestSystem.Data;
public sealed class ClientQuestUI : IQuestUI, IChapterUI, IDisposable
{
public void Initialize()
{
QuestUI.Register(this);
ChapterManager.BindUI(this);
Quest.RefreshQuestListUI();
RefreshChapterList(ChapterManager.GetLocalPlayerAllChapterProgress());
}
public void OnQuestAccepted(QuestInfo info) { /* 显示 info.Name */ }
public void OnQuestCompleted(QuestInfo info) { /* 提示可以提交 */ }
public void OnQuestTurnedIn(QuestInfo info) { /* 更新列表 */ }
public void OnQuestFailed(QuestInfo info, string? reason) { /* 显示原因 */ }
public void OnQuestAbandoned(QuestInfo info) { /* 更新列表 */ }
public void OnObjectiveProgress(QuestInfo questInfo, QuestObjectiveInfo objectiveInfo)
{ /* 显示 objectiveInfo.CurrentProgress / TargetCount */ }
public void OnObjectiveCompleted(QuestInfo questInfo, QuestObjectiveInfo objectiveInfo)
{ /* 播放完成反馈 */ }
public void OnTrackingChanged(QuestInfo info, bool isTracked) { /* 更新追踪状态 */ }
public void ShowQuestDetail(QuestInfo info) { /* 显示详情 */ }
public void HideQuestDetail() { /* 隐藏详情 */ }
public void RefreshQuestList(IEnumerable<QuestInfo> quests) { /* 更新任务列表 */ }
public void RefreshTracker(IEnumerable<QuestInfo> trackedQuests) { /* 更新追踪面板 */ }
public void OnChapterUnlocked(IGameLink<GameDataQuestChapter> chapter, Player player)
{ /* 显示解锁反馈 */ }
public void OnChapterProgressUpdated(
IGameLink<GameDataQuestChapter> chapter, Player player,
int completedQuests, int totalQuests)
{ /* 显示 completedQuests / totalQuests */ }
public void OnChapterCompleted(IGameLink<GameDataQuestChapter> chapter, Player player)
{ /* 显示完成反馈 */ }
public void OnChapterRewardsClaimed(IGameLink<GameDataQuestChapter> chapter, Player player)
{ /* 显示奖励反馈 */ }
public void RefreshChapterList(IEnumerable<ChapterProgress> chapters) { /* 更新章节列表 */ }
public void ShowChapterDetails(ChapterProgress chapter) { /* 显示章节详情 */ }
public void HideChapterDetails() { /* 隐藏章节详情 */ }
public void ShowChapterCompleteCelebration(
ChapterProgress chapter, IEnumerable<string>? rewards = null)
{ /* 显示章节完成界面 */ }
public void Dispose()
{
QuestUI.Unregister();
ChapterManager.UnbindUI();
}
}
#endif同一时刻只有一个任务 UI 和一个章节 UI 绑定。界面不再使用时解除绑定;若替换了 UI,实现方应安排好旧界面的退出与新界面的绑定顺序。
API 速查表
QuestManager(静态方法)
| 分类 | 方法 |
|---|---|
| 初始化 | Initialize, Clear, ClearPlayer |
| 创建/获取 | CreateQuest, GetQuest, GetAllQuests, GetActiveQuests, GetInProgressQuests, GetCompletedQuests, GetTrackedQuests, RemoveQuest |
| 操作 | AcceptQuest, TurnInQuestAsync, AbandonQuest |
| 可用性 | NotifyQuestAvailable, IsQuestAvailable, GetAvailableQuests |
| 事件/目标(SERVER) | RegisterEventListeners, UnregisterEventListeners, CheckAllTimeouts, NotifyTalkToNpc, NotifyCustomObjectiveProgress, NotifyCustomObjectiveComplete |
Quest 实例
| 分类 | 成员 |
|---|---|
| 标识 | Link, Cache, Player |
| 状态 | Status, IsActive, CanAccept, CanTurnIn, CanAbandon, IsExpired, RemainingTime |
| 进度 | Objectives, RequiredObjectives, OptionalObjectives, AllRequiredObjectivesCompleted, TotalProgress, AcceptCount |
| 时间 | AcceptedTime, CompletedTime |
| 查询(双端) | GetObjective, GetObjectiveByCustomId |
| 操作(SERVER) | Accept, TurnInAsync, Abandon, Fail, SetTracked, UpdateObjectiveProgress, CompleteObjective, CheckTimeout |
| UI 辅助(CLIENT) | RefreshQuestListUI, ShowQuestDetailUI, HideQuestDetailUI |
QuestObjective 实例
| 分类 | 成员 |
|---|---|
| 标识 | Link, Cache, Quest, Index, ObjectiveType |
| 进度 | CurrentProgress, TargetCount, IsCompleted, ProgressPercentage |
| 标志 | IsOptional, IsHidden |
| 查询(双端) | GetDescription |
| 操作(SERVER) | UpdateProgress, SetProgress, Complete, Reset |
ChapterManager(静态方法)
| 方法 | 说明 |
|---|---|
GetChapterProgress | 获取章节进度 |
GetOrCreateChapterProgress | 获取或创建章节进度 |
GetAllChapterProgress | 获取全部章节进度 |
GetUnlockedChapters, GetInProgressChapters, GetCompletedChapters | 按状态查询章节 |
IsChapterUnlocked, IsChapterCompleted | 查询章节状态 |
UnlockChapter | 解锁章节 |
StartChapter | 开始章节 |
ClaimChapterRewardsAsync | 领取奖励(异步) |
InitializePlayerChapters | 初始化玩家章节 |
ClearPlayerData, ClearAll | 清除章节记录 |
BindUI, UnbindUI | CLIENT:绑定/解绑章节 UI |
ChapterProgress 实例
| 属性 | 说明 |
|---|---|
Link, Cache | 数编表引用 |
Status | Locked / Unlocked / InProgress / Completed / RewardsClaimed |
CompletedQuestCount, TotalQuestCount | 任务完成计数 |
Progress | 完成百分比 |
IsCompleted, CanClaimRewards | 状态查询 |
UnlockedTime, CompletedTime | 时间记录 |
注意事项与最佳实践
运行端约束
| 操作 | 运行端 | 说明 |
|---|---|---|
| QuestManager / ChapterManager 查询及记录管理 | 双端 | 查询当前端的任务/章节记录;权威数据在服务端管理 |
| QuestManager.AcceptQuest / TurnInQuestAsync / AbandonQuest | 双端有声明,业务放 SERVER | 客户端无法通过这些入口完成权威操作 |
| Quest 实例操作、目标进度修改、QuestManager 事件/目标通知 | SERVER | 使用 #if SERVER 保护代码 |
| ChapterManager.UnlockChapter / StartChapter / ClaimChapterRewardsAsync | SERVER | 章节解锁、开始与领奖 |
| IQuestUI / IChapterUI 回调 | CLIENT | UI 通知在客户端接收 |
| QuestUI.Register / Unregister,ChapterManager.BindUI / UnbindUI | CLIENT 的 UI 流程 | 两种 UI 分别绑定与解绑;BindUI / UnbindUI 仅 CLIENT 有声明 |
推荐做法
- 使用内置初始化 -- 常规项目由
QuestSystem初始化管理器和监听器,业务代码不要重复注册。 - 使用追踪功能 -- 通过
SetTracked(true)让玩家能在 HUD 中看到当前聚焦的任务进度。 - 检查操作结果 -- 接受、提交、放弃及章节操作返回
CmdResult;异步提交和领奖返回ValueTask<CmdResult>,先await,再检查IsSuccess。 - 按项目生命周期管理记录 -- 确认无需保留重连状态后,使用
QuestManager.ClearPlayer(player)和ChapterManager.ClearPlayerData(player)清除记录;这些接口不负责存档。 - 章节与任务解耦 -- 章节作为任务的上层分组,不要在任务逻辑中直接操作章节状态,而是通过
ChapterManager统一管理。 - 对话类目标使用 NotifyTalkToNpc -- 当玩家与 NPC 交互时调用此方法,系统会自动匹配并更新相关任务的对话目标。
- 明确自定义目标范围 --
NotifyCustomObjectiveProgress按 CustomId 遍历所有进行中任务;指定任务使用quest.UpdateObjectiveProgress。 - 主动检查限时任务 -- 项目在服务端的周期逻辑中调用
QuestManager.CheckAllTimeouts(),或检查单个任务的CheckTimeout();设置 TimeLimit 本身不会安排周期检查。
常见错误
| 错误 | 说明 |
|---|---|
在客户端调用 QuestManager.AcceptQuest 完成接任务 | 客户端有声明,但无法执行服务端权威的接受操作 |
| 手动重复初始化所有管理器 | 初始化已具备重复调用保护;常规项目只需使用内置初始化并绑定 UI |
把 NotifyCustomObjectiveProgress 写成任务链接 + 索引 | 实际参数是 Player, string customId, int delta = 1;索引操作在 Quest 实例上 |
将 delta 当作绝对值 | delta 会累加;设置绝对值使用 QuestObjective.SetProgress |
| 将 IQuestUI 参数写成 Quest / QuestObjective | 实际使用 QuestInfo / QuestObjectiveInfo,且失败回调需要 reason 参数 |
| 只实现任务 UI 的状态回调 | IQuestUI 的追踪、列表和详情方法也必须实现 |
只调用 QuestUI.Register | 章节 UI 还需 ChapterManager.BindUI,退出时分别解绑 |
| 忽略异步方法的结果 | 必须 await 并检查 CmdResult,不能无条件显示成功 |
用 GetQuest != null 判断接取成功 | 接取失败也可能留下 Inactive 实例,应检查 CmdResult.IsSuccess |
只定义奖励,未填写 Rewards | 奖励必须通过任务的 Rewards 列表关联 |
| 收集材料被多个目标重复使用 | 提交会统一检查消费需求;不足时返回失败,进度达标不代表材料足够同时交付所有目标 |