任务系统的设计思路
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
- 这部分实现代码可以认为是通用的,因为里面基本没有涉及到具体的业务逻辑