任务系统的设计思路

September 5, 2025 · View on GitHub

  • 任务接口代码通用,实现业务逻辑和任务模块的解耦
  • 同时我们还希望有灵活的配置,对同一个事件,配置文件里通过排列组合来实现不同的任务,而不是增加代码

任务涉及到的点

  • 任务的基础配置(如任务名,等级,奖励等常规配置,不在本文讨论的范围
  • 条件接口,如用于判断任务是否可以接了
  • 事件匹配接口,事件分发后,判断事件是否和任务配置参数匹配,匹配后,增加进度值
  • 进度接口,进度值达到目标值后,任务完成

以战斗相关的任务来举例

// 战斗事件的proto定义
message EventFight {
  int64 PlayerId = 1;
  bool IsPvp = 2;
  bool IsWin = 3;
  int32 RoomType = 4; // 房间类型(1:新手模式(PVE) 2:普通匹配 3:天梯匹配 4:机器人对抗(PVE) 5:自定义房间)
  int32 RoomLevel = 5; // 房间等级
  int32 Score = 6; // 战斗得分
}
// 分发战斗事件的代码大概是这样:
eventFight := &pb.EventFight{
    PlayerId:  player.GetId(),
    IsPvp:     isPvp,
    IsWin:     isWin,
    RoomType:  roomType,
    RoomLevel: roomLevel,
    Score:     score,
    }
player.FireEvent(eventFight)

假设我们有如下任务需求:

  • 任务1: 打5场战斗
  • 任务2: 打5场天梯匹配的战斗
  • 任务3: 赢5场指定条件的PVP(房间类型是匹配模式(2或3),房间等级>3,得分在[100,200]之间)

在不增加代码的情况下,如何仅仅通过配置就能实现如上4种不同的任务? 先不考虑具体的配置格式,我们先设想一下大概的配置是什么样的

  • 任务1: 打5场战斗

    只要匹配EventFight事件,进度就+1

    配置: Event=EventFight

  • 任务2: 打5场天梯匹配的战斗

    需要匹配EventFight事件,且其中的RoomType字段的值是3,进度就+1

    配置: Event=EventFight RoomType=3

  • 任务3: 赢5场指定条件的PVP(房间类型是匹配模式(2或3),房间等级>3,得分在[100,200]之间)

    需要匹配EventFight事件,且其中的且IsPvp和IsWin字段的值都为true,而且RoomType字段的值是2或3,RoomLevel字段的值>3,Score字段的值在[100,200]之间,进度就+1

    配置: Event=EventFight RoomType=2或3 RoomLevel>3 100<=Score<=200

我们来逐个分析怎么实现

  • 任务1: 打5场战斗

    任务配置里有个事件名的配置项就可以,分发事件时,检查事件名匹配,进度就+1,很容易实现

  • 任务2: 打5场天梯匹配的战斗

    在任务1的基础上,再加个字段名和字段值的配置项,通过反射来比较事件字段值和配置项是否匹配,就能实现,

    在这里通过反射实现了通用的接口,也就是代码里分发不同的事件,配置文件里配置对应的字段名和字段值就可以,

    实现代码详见gserver里的DefaultProgressUpdater函数

  • 任务3: 赢5场指定条件的PVP(房间类型是匹配模式(2或3),房间等级>3,得分在[100,200]之间)

    在任务2的实现中,只能实现字段名和字段值的完全匹配,还不能支持RoomType是2或3,RoomLevel>3,Score在某个区间内的复杂匹配机制

    为了实现复杂比较,我们把任务2中用到的字段值扩展为一个操作符+值,且值可以是一个数组

    如RoomType是2或3 操作符是= 但是值是个数组[]int{2,3}

    RoomLevel>3 操作符是> 值是3

    100<=Score<=200 操作符是[] 值是[100,200]

    在任务2的基础上,我们在匹配事件字段值的时候,增加了操作符的比较,从而支持复杂的比较接口,可以这里理解,任务2里使用的都是=操作符,

    实现代码详见gserver里的DefaultProgressUpdater函数

单元测试用例

  • player_test.go里的TestCondition: 演示了比较操作符的基础功能
  • player_test.go里的TestQuest: 演示了分发事件和任务进度更新的功能

总结

  • 我们以几个具有代表性的任务为例,描述了任务接口逐步改进的设计过程,具体实现直接看源码吧,同时gserver里还做了一些额外的工作
  • 任务配置支持复杂需求之后,配置格式势必也复杂了一点,gserver做了一些简化,降低了配置难度,详见cfg/options.go,cfg/template_cfg.go
  • 这部分实现代码可以认为是通用的,因为里面基本没有涉及到具体的业务逻辑