抽奖系统开发文档

抽奖系统服务端+客户端实现

最后编辑时间:yaohua

抽奖系统开发文档

一、数据结构

配置文件

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 增量更新
  • [ ] 测试:单抽/十连/余额不足(待联调)
  • 留下精彩的评论吧~(0条)

所有内容仅供学习与交流,转载须标明链接。未经同意,禁止作为商业用途,有特殊需求请联系站长。