抽奖系统开发文档
抽奖系统服务端+客户端实现
抽奖系统开发文档
一、数据结构
配置文件
GachaPool.csv
| 字段 | 类型 | 说明 |
|------|------|------|
| entryId | int | 条目ID(唯一) |
| poolId | int | 奖池ID(1=游戏宝箱,2=钻石奖池,3=金币奖池,4=里程碑奖励) |
| poolName | string | 奖池名称 |
| costItem | int | 消耗 itemId#count,如 1#10 = 消耗 10 宝石 |
| rewardId | int | 奖励ID(对应 Reward.csv) |
| weight | float | 权重值 |
Reward.csv | 字段 | 说明 | |------|------| | rewardId | 奖励ID | | itemType | 0=金币,1=碎片,2=钻石 | | quality | 品质(0=白,1=蓝,2=紫,3=金) | | value | 价值 | | qtyMin/qtyMax | 数量范围 | | fragmentIds | 碎片ID列表(合成武器用) |
Item.csv | 字段 | 说明 | |------|------| | id | 物品ID(0=金币,1=宝石,1000+=碎片,2000+=武器) |
奖池消耗
| poolId | 名称 | 消耗 |
|---|---|---|
| 1 | 游戏宝箱 | 1#10(10 宝石) |
| 2 | 钻石奖池 | 1#100(100 宝石) |
| 3 | 金币奖池 | 0#1000(1000 金币) |
| 4 | 里程碑奖励 | 1#30(30 宝石) |
二、奖池算法
概率展开 + 洗牌 + 顺序抽取(不放回)
初始化(每个 poolId 独立):
1. 读取 GachaPool.csv 中该 poolId 的所有 entryId
2. 按 weight 展开为 100 个奖励(weight/总和 × 100)
3. Fisher-Yates 打乱
4. 存入 List<Reward>(环形队列)
抽取(每次请求):
1. 从 List 头部 pop 一个
2. 若 List 为空,重新执行初始化
3. 返回 rewardId
十连:
- 循环 count 次,每次 pop 一个
- 所有奖励一次性返回
三、Proto 消息
// 抽奖请求
message C2S_LotteryRequest {
int32 pool_id = 1; // 奖池ID
int32 count = 2; // 次数(1=单抽,10=十连)
}
// 抽奖结果
message S2C_LotteryResult {
int32 code = 1; // 0=成功,其他=错误码
string msg = 2;
repeated LotteryReward rewards = 3;
}
message LotteryReward {
int32 reward_id = 1;
int32 item_type = 2; // 0=gold, 1=fragment, 2=diamond
int32 item_id = 3; // 物品ID(参照 Reward.csv)
int32 count = 4; // 实际数量
bool is_new = 5; // 是否新获得(背囊中原本没有)
}
// 物品变化同步(服务端主动推送)
message S2C_InventoryChanged {
int32 currency_delta = 1; // 金币变化量(可为负)
int32 gems_delta = 2; // 宝石变化量(可为负)
map<int32, int32> items_delta = 3; // 物品变化量 itemId -> delta
}
四、错误码
| code | 说明 |
|---|---|
| 0 | 成功 |
| 101 | 奖池不存在 |
| 102 | 余额不足 |
| 103 | 次数非法(需 1 或 10) |
五、完整流程
客户端 服务端
| |
|--- C2S_LotteryRequest ------>|
| (pool_id=1, count=10) |
| |
| | 1. 读取 GachaPool.csv
| | 2. 校验余额(costItem)
| | 3. 扣费(currency/gems)
| | 4. 从奖池 pop 10 个 rewardId
| | 5. 查 Reward.csv 得实际物品
| | 6. 更新数据库(发奖)
| |
|<-- S2C_LotteryResult --------|
| (code=0, rewards=[...]) |
| |
|<-- S2C_InventoryChanged ----|
| (currency_delta=-100, |
| gems_delta=-10, |
| items_delta={1000:+30}) |
六、服务端实现
新增文件
LotteryService.cs
Draw(int poolId, int count)— 抽奖主逻辑InitPool(int poolId)— 初始化奖池(概率展开+洗牌)ExpandTo100(poolId)— 权重展开为 100 个DrawOnce(int poolId)— 单次抽取
LotteryController.cs
- WebSocket 端点处理
C2S_LotteryRequest
数据库改动
无需新增表。奖池配置走 CSV,物品走现有 character_inventory 表的 currency/gems/items。
七、客户端实现
WebSocketService.cs
- 接收
S2C_LotteryResult→ 显示抽奖结果动画/弹窗 - 接收
S2C_InventoryChanged→ 更新 InventoryService
InventoryService.cs
- 适配
items_delta(增量更新) - 适配
equipped_weapon_id
八、已完成清单
- [x] 更新 GachaPool.csv
- [x] 更新 Ninjiafall1Proto.proto(添加 LotteryRequest/Result/InventoryChanged)
- [x] 重新编译 protobuf(build.sh)
- [x] 服务端:LotteryService.cs — 概率展开 + Fisher-Yates 洗牌 + 顺序抽取不放回
- [x] 服务端:LotteryController.cs(WebSocket)— C2S_LotteryRequest → S2C_LotteryResult + S2C_InventoryChanged
- [x] 客户端:WebSocketService.cs — OnLotteryResult 事件 + RequestLottery(poolId, count)
- [x] 客户端:InventoryService.cs — UpdateByDelta 支持 items_delta 增量更新
- [ ] 测试:单抽/十连/余额不足(待联调)
所有内容仅供学习与交流,转载须标明链接。未经同意,禁止作为商业用途,有特殊需求请联系站长。
