Skip to content

任务系统(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 是静态类,提供任务生命周期管理的全部入口。

初始化与清理 ​

方法签名说明
Initializevoid Initialize()初始化任务管理器;常规流程由内置系统调用
Clearvoid Clear()清除所有玩家的任务和可接受任务记录
ClearPlayervoid ClearPlayer(Player player)清除指定玩家的任务和可接受任务记录

任务创建与获取 ​

方法签名说明
CreateQuestQuest? CreateQuest(Player player, IGameLink<GameDataQuest> questLink)创建实例,不自动接受;数据无效时返回 null
GetQuestQuest? GetQuest(Player player, IGameLink<GameDataQuest> questLink)获取指定任务,不存在时返回 null
GetAllQuestsIEnumerable<Quest> GetAllQuests(Player player)获取全部任务
GetActiveQuestsIEnumerable<Quest> GetActiveQuests(Player player)获取 InProgress 和 Completed 任务
GetInProgressQuestsIEnumerable<Quest> GetInProgressQuests(Player player)获取进行中的任务
GetCompletedQuestsIEnumerable<Quest> GetCompletedQuests(Player player)获取目标达成、等待提交的任务
GetTrackedQuestsIEnumerable<Quest> GetTrackedQuests(Player player)获取正在追踪的任务
RemoveQuestvoid RemoveQuest(Player player, Quest quest)从管理器中移除实例记录

任务操作 ​

方法签名说明
AcceptQuestCmdResult AcceptQuest(Player player, IGameLink<GameDataQuest> questLink)创建并接受任务
TurnInQuestAsyncValueTask<CmdResult> TurnInQuestAsync(Player player, IGameLink<GameDataQuest> questLink)提交任务并发奖
AbandonQuestCmdResult AbandonQuest(Player player, IGameLink<GameDataQuest> questLink)放弃任务

这三个入口在双端均有声明,但接受、提交、放弃必须由服务端执行;客户端不能通过它们修改服务端任务。检查返回值的 IsSuccess / Error,异步方法先 await 再检查结果。

AcceptQuest 会先创建任务实例,再验证前置条件。接受失败后,GetQuest 仍可能返回 Inactive 实例;实例存在不代表已接取。前置任务提交后可以再次接受这个实例。交互逻辑应检查 CanAccept、CanTurnIn 和操作结果,不能无条件把 Failed / Abandoned 当成可重新接取。

任务可用性 ​

方法签名说明
NotifyQuestAvailablevoid NotifyQuestAvailable(Player player, IGameLink<GameDataQuest> questLink)标记任务可接受
IsQuestAvailablebool IsQuestAvailable(Player player, IGameLink<GameDataQuest> questLink)检查可接受标记或前置任务
GetAvailableQuestsIEnumerable<IGameLink<GameDataQuest>> GetAvailableQuests(Player player)枚举通过 NotifyQuestAvailable 标记的任务链接

GetAvailableQuests 枚举显式标记的任务,不扫描所有任务定义。成功接取后会移除对应标记,接取失败则保留。NotifyQuestAvailable 不创建任务实例;可接受标记与 IsQuestAvailable 都不能替代 AcceptQuest 的实际接受验证。

事件注册与目标通知 ​

方法签名说明
RegisterEventListenersvoid RegisterEventListeners()注册击杀、物品变化监听;常规流程由内置系统调用
UnregisterEventListenersvoid UnregisterEventListeners()注销上述监听
CheckAllTimeoutsvoid CheckAllTimeouts()检查并处理所有进行中任务的超时
NotifyTalkToNpcvoid NotifyTalkToNpc(Player player, IGameLink<GameDataUnit> npcUnit)完成匹配该 NPC 类型的对话目标
NotifyCustomObjectiveProgressvoid NotifyCustomObjectiveProgress(Player player, string customId, int delta = 1)按 CustomId 增加目标进度
NotifyCustomObjectiveCompletevoid NotifyCustomObjectiveComplete(Player player, string customId)按 CustomId 完成目标

本组 API 仅服务端可用。两种自定义目标通知会遍历玩家所有进行中的任务,更新其中匹配 GameDataQuestObjectiveCustom.CustomId 的目标。只更新一个任务时,先 GetQuest,再调用该实例的 UpdateObjectiveProgress / CompleteObjective。delta 是增量;设置绝对值应使用 QuestObjective.SetProgress。


Quest(任务) ​

Quest 类表示一个任务实例,包含任务的运行时状态。

属性 ​

属性类型说明
LinkIGameLink<GameDataQuest>任务数编表链接
CacheGameDataQuest任务数编表缓存
PlayerPlayer所属玩家
StatusQuestStatus当前状态
AcceptedTimeDateTime?接受时间;未接受时为 null
CompletedTimeDateTime?完成时间
IsTrackedbool是否被追踪
AcceptCountint接受次数(可重复任务)
ObjectivesIReadOnlyList<QuestObjective>只读任务目标列表
RequiredObjectivesIEnumerable<QuestObjective>必要目标
OptionalObjectivesIEnumerable<QuestObjective>可选目标
AllRequiredObjectivesCompletedbool是否所有必要目标均已完成
TotalProgressfloat总体进度(0.0 ~ 1.0)
IsActivebool是否活跃(InProgress 或 Completed)
CanAcceptbool是否可接受
CanTurnInbool是否可提交
CanAbandonbool是否可放弃
IsExpiredbool是否已过期
RemainingTimeTimeSpan?剩余时间(限时任务)

方法 ​

方法签名说明
GetObjectiveQuestObjective? GetObjective(int index)双端:获取目标,不存在时返回 null
GetObjectiveByCustomIdQuestObjective? GetObjectiveByCustomId(string customId)双端:获取匹配 CustomId 的目标
AcceptCmdResult Accept()SERVER:接受任务
TurnInAsyncValueTask<CmdResult> TurnInAsync()SERVER:提交任务
AbandonCmdResult Abandon()SERVER:放弃任务
FailCmdResult Fail(string? reason = null)SERVER:标记失败,可提供原因
SetTrackedvoid SetTracked(bool tracked)SERVER:设置追踪状态
UpdateObjectiveProgressbool UpdateObjectiveProgress(int objectiveIndex, int delta = 1)SERVER:按索引增加进度
UpdateObjectiveProgressbool UpdateObjectiveProgress(string customId, int delta = 1)SERVER:按 CustomId 增加进度
CompleteObjectivevoid CompleteObjective(int objectiveIndex)SERVER:按索引完成目标
CompleteObjectivevoid CompleteObjective(string customId)SERVER:按 CustomId 完成目标
CheckTimeoutbool CheckTimeout()SERVER:超时且仍进行中时标记失败,返回是否处理超时

QuestObjective(任务目标) ​

QuestObjective 表示任务内的一个具体目标。

属性 ​

属性类型说明
LinkIGameLink<GameDataQuestObjective>目标数编表链接
CacheGameDataQuestObjective目标数编表缓存
QuestQuest所属任务
Indexint目标在任务中的索引
CurrentProgressint当前进度
TargetCountint目标数量
IsCompletedbool是否已完成
IsOptionalbool是否为可选目标
IsHiddenbool是否隐藏(不在 UI 中显示)
ObjectiveTypeQuestObjectiveType目标类型(击杀、收集、对话、自定义等)
ProgressPercentagefloat进度百分比(0.0 ~ 1.0)

方法 ​

方法签名说明
GetDescriptionstring GetDescription()双端:获取格式化描述
UpdateProgressbool UpdateProgress(int delta)SERVER:增加进度,返回进度是否变化
SetProgressvoid SetProgress(int progress)SERVER:设置绝对进度值
Completevoid Complete()SERVER:直接完成目标
Resetvoid 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 是静态类,管理所有章节进度。

方法签名说明
GetChapterProgressChapterProgress? GetChapterProgress(Player player, IGameLink<GameDataQuestChapter> chapterLink)双端:查询进度,不存在时返回 null
GetOrCreateChapterProgressChapterProgress GetOrCreateChapterProgress(Player player, IGameLink<GameDataQuestChapter> chapterLink)双端:获取或创建进度
GetAllChapterProgressIEnumerable<ChapterProgress> GetAllChapterProgress(Player player)双端:查询全部进度
GetUnlockedChaptersIEnumerable<ChapterProgress> GetUnlockedChapters(Player player)双端:查询已解锁章节
GetInProgressChaptersIEnumerable<ChapterProgress> GetInProgressChapters(Player player)双端:查询进行中章节
GetCompletedChaptersIEnumerable<ChapterProgress> GetCompletedChapters(Player player)双端:查询已完成章节
IsChapterUnlockedbool IsChapterUnlocked(Player player, IGameLink<GameDataQuestChapter> chapterLink)双端:查询是否已解锁
IsChapterCompletedbool IsChapterCompleted(Player player, IGameLink<GameDataQuestChapter> chapterLink)双端:查询是否已完成
InitializePlayerChaptersvoid InitializePlayerChapters(Player player, IEnumerable<IGameLink<GameDataQuestChapter>> allChapters)双端有声明;权威玩家数据应在服务端初始化
ClearPlayerDatavoid ClearPlayerData(Player player)双端:清除玩家章节记录
ClearAllvoid ClearAll()双端:清除全部章节记录
Initialize / Shutdownvoid Initialize() / void Shutdown()SERVER:注册/注销章节事件监听
UnlockChapterCmdResult UnlockChapter(Player player, IGameLink<GameDataQuestChapter> chapterLink)SERVER:解锁章节
StartChapterCmdResult StartChapter(Player player, IGameLink<GameDataQuestChapter> chapterLink)SERVER:开始章节
ClaimChapterRewardsAsyncValueTask<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(章节进度) ​

属性类型说明
LinkIGameLink<GameDataQuestChapter>章节数编表链接
CacheGameDataQuestChapter章节数编表缓存
StatusChapterStatus当前状态
CompletedQuestCountint已完成的任务数量
TotalQuestCountint总任务数量
Progressfloat完成进度(0.0 ~ 1.0)
IsCompletedbool是否已完成
CanClaimRewardsbool是否可以领取奖励
UnlockedTimeDateTime?解锁时间
CompletedTimeDateTime?完成时间

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 注册的静态入口。

方法签名说明
Registervoid Register(IQuestUI ui)注册任务 UI 回调处理器
Unregistervoid Unregister()取消注册
CreateInfoQuestInfo CreateInfo(Quest quest)从任务实例生成展示数据,目标列表排除隐藏目标
CreateObjectiveInfoQuestObjectiveInfo 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, UnbindUICLIENT:绑定/解绑章节 UI

ChapterProgress 实例 ​

属性说明
Link, Cache数编表引用
StatusLocked / 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 / ClaimChapterRewardsAsyncSERVER章节解锁、开始与领奖
IQuestUI / IChapterUI 回调CLIENTUI 通知在客户端接收
QuestUI.Register / Unregister,ChapterManager.BindUI / UnbindUICLIENT 的 UI 流程两种 UI 分别绑定与解绑;BindUI / UnbindUI 仅 CLIENT 有声明

推荐做法 ​

  1. 使用内置初始化 -- 常规项目由 QuestSystem 初始化管理器和监听器,业务代码不要重复注册。
  2. 使用追踪功能 -- 通过 SetTracked(true) 让玩家能在 HUD 中看到当前聚焦的任务进度。
  3. 检查操作结果 -- 接受、提交、放弃及章节操作返回 CmdResult;异步提交和领奖返回 ValueTask<CmdResult>,先 await,再检查 IsSuccess。
  4. 按项目生命周期管理记录 -- 确认无需保留重连状态后,使用 QuestManager.ClearPlayer(player) 和 ChapterManager.ClearPlayerData(player) 清除记录;这些接口不负责存档。
  5. 章节与任务解耦 -- 章节作为任务的上层分组,不要在任务逻辑中直接操作章节状态,而是通过 ChapterManager 统一管理。
  6. 对话类目标使用 NotifyTalkToNpc -- 当玩家与 NPC 交互时调用此方法,系统会自动匹配并更新相关任务的对话目标。
  7. 明确自定义目标范围 -- NotifyCustomObjectiveProgress 按 CustomId 遍历所有进行中任务;指定任务使用 quest.UpdateObjectiveProgress。
  8. 主动检查限时任务 -- 项目在服务端的周期逻辑中调用 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 列表关联
收集材料被多个目标重复使用提交会统一检查消费需求;不足时返回失败,进度达标不代表材料足够同时交付所有目标

相关文档 ​