成就/称号系统
概述
成就是把"玩得久、玩得深"变成可见荣誉的系统:达成条件 → 解锁成就 → 发放奖励与称号。核心难点有三:条件检测的触发方式(如何低成本捕捉玩家行为)、进度持久化(防丢进度)、奖励发放的幂等(一成就只发一次)。称号作为成就的展示层,承担个性化与社交展示。本文设计完整的成就/称号系统。
一、成就模型
1.1 成就定义(配置驱动)
成就配置化定义,由运营/策划在配置表中维护,代码不写死:
AchievementDef(配置表):
achId、名称、描述、图标
category(累计/一次性/隐藏)
conditionType(击败次数/充值金额/连续登录…)
conditionParam(目标值:如 100 次)
rewards(奖励列表:货币/道具/称号)
hiddenFlag(隐藏成就,达成前不展示)成就类型:
累计型:杀死 100 只怪物(进度 0 → 100)
一次性:首次通关、首充
隐藏成就:特殊玩法彩蛋,达成前不可见
阶梯成就:同一行为多档目标(10/50/100 次)1.2 玩家成就状态
PlayerAchievement:
playerId + achId
progress(当前进度)
status(0 未达成 / 1 已达成 / 2 已领奖)
completeTime
PRIMARY KEY(playerId, achId)二、条件检测
2.1 事件驱动
成就检测不轮询、靠事件:业务系统把关键行为发布为事件,成就服务订阅并更新进度。
事件总线设计:
对战结束 → PlayerBattleEndEvent(胜场/击杀/用时)
充值成功 → PlayerRechargeEvent(金额)
升级 → PlayerLevelUpEvent(等级)
登录 → PlayerLoginEvent(连续天数)
社交行为 → FriendAddEvent、GuildJoinEvent检测流程:
1. 业务发布事件
2. 成就服务监听事件(同玩家串行处理)
3. 匹配相关成就条件
4. 计算新进度并累加
5. 达 100% → 达成成就 → 发放奖励2.2 监听实现
@EventListener
public void onBattleEnd(PlayerBattleEndEvent event) {
// 找出与"战斗"相关的成就定义
// 对每个进行进度更新
}性能注意:
事件只携带最小必要字段
相关成就定义内存缓存(配置量小)
异步处理,不阻塞主业务三、进度持久化
3.1 进度存储
存储策略:
Redis Hash:ach:progress:{playerId}(热数据,实时读写)
MySQL:player_achievement 表(定期落盘 + 领取时持久化)
写入路径:
事件 → 更新 Redis 进度 → 标记脏数据
定时/下线 → 落盘 MySQL进度计算:
累计型:progress + 事件数值,上限为目标值
一次性:布尔达成,无进度
隐藏成就:达成前记录隐藏进度3.2 防丢与对账
进度是玩家的"沉没成本",必须防丢:
落盘时机:定时批量(30 秒)+ 关键节点(领奖前)
达成状态以 MySQL 为准(Redis 只是加速)
异常恢复:启动时从 MySQL 回填 Redis 缓存四、达成奖励发放
4.1 发放流程
达成即发奖,必须幂等:一个成就只发一次,崩溃重放不重复。
发放流程:
1. 校验:状态未达成 → 达成,未领奖 → 可领
2. 状态机:UNFINISHED → COMPLETED → CLAIMED
3. 原子更新状态(UPDATE ... WHERE status=OLD)
4. 发放奖励(统一发放接口,记流水)
5. 解锁对应称号(写入称号表)
6. 广播成就达成(全服/好友可见)@Transactional
public void claimAchievement(long playerId, int achId) {
// UPDATE player_achievement
// SET status = CLAIMED
// WHERE player_id = ? AND ach_id = ? AND status = COMPLETED
// 影响行数 = 1 才发奖(数据库级幂等)
// 发放奖励 + 解锁称号
}4.2 奖励投放策略
奖励设计:
货币(少量)+ 道具(实用)+ 称号(荣誉)
阶梯成就:每档都发,越高级奖励越好
隐藏成就:奖励更丰厚,鼓励探索
全成就达成:额外终极奖励(称号 + 限定道具)五、称号展示与装备
5.1 称号模型
PlayerTitle:
playerId + titleId
source(成就/活动/排行)
obtainTime、equipFlag
PRIMARY KEY(playerId, titleId)称号独立于成就存储,同一个成就可对应多个称号,活动、排行也可发称号。
5.2 装备与展示
称号展示:
装备称号 → 名称前缀(如"屠龙者 · 张三")
聊天/对战结算/个人主页展示
过期称号(限时称号)到期自动卸下
称号属性(可选):
部分称号带属性加成(战斗/社交)
同类型加成不叠加,取最高接口:
equipTitle(playerId, titleId)
unequipTitle(playerId)
getTitleList(playerId)
聊天展示:玩家基础信息里带当前称号六、实现要点
核心接口:
onEvent(PlayerEvent) // 事件入口,更新进度
getAchievementList(playerId) // 成就列表/进度
claimAchievement(playerId, achId) // 领取奖励
equipTitle(playerId, titleId) // 装备称号工程注意:
成就定义配置化,热更新
事件与成就服务解耦(MQ 或进程内总线)
进度更新同玩家串行,避免并发覆盖
成就达成推送:红点 + 弹窗 + 可选全服广播常见坑:
并发领奖重复 → UPDATE 条件状态 + 事务
进度覆盖丢失 → 单玩家串行处理
事件风暴 → 合并批量处理,控制粒度
配置改目标值 → 进度超上限时截断处理与其他系统衔接:
奖励发放 → 经济系统统一接口
成就事件源 → 玩家数据模型/行为系统
称号展示 → 社交展示(聊天、好友)
达成广播 → 聊天系统频道消息