排行榜系统开发文档

排行榜系统服务端+客户端实现,含分数上报、全榜/武器榜、实时排名查询

最后编辑时间:yaohua

排行榜系统开发文档

一、需求概述

游戏结束后自动上报分数和武器,服务端记录:

  • 总榜:全局前 100 名
  • 武器榜:每把武器(2000-2015)各前 100 名

客户端可主动查询任意榜。


二、客户端现状(已确认)

分数系统 ScoreSystem

/Volumes/2TAPFS/UnityProject/NinjaFall/client/Assets/Scripts/System/ScoreSystem.cs

  • _currentScore — 本局得分(随正确输入累加,baseScore + combo加成)
  • CurrentScore 属性 — 对外暴露
  • Settle() — 游戏结束时调用,触发 RankingService.Instance.SubmitScore()(当前仅本地)

武器系统 WeaponSystem

/Volumes/2TAPFS/UnityProject/NinjaFall/client/Assets/Scripts/System/WeaponSystem.cs

  • _currentWeaponId — 当前装备武器 ID
  • CurrentWeaponId 属性 — 对外暴露
  • 初始化时从 InventoryService.Instance.CurrentWeapon 读取

结算时机

GameStateManager.csOnPlayerDead()ScoreSystem.Instance.Settle() → 状态切换 GameOver

待改动点

  • ScoreSystem.Settle() 目前只调本地 RankingService.SubmitScore(),需新增网络上报
  • 上报时机:游戏结束(GameOver)时自动触发
  • 上报内容:score = ScoreSystem.Instance.CurrentScoreweapon_id = WeaponSystem.Instance.CurrentWeaponId

三、数据结构

数据库表

character_rank_scores(单局记录)

CREATE TABLE character_rank_scores (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    character_id BIGINT NOT NULL,
    username VARCHAR(64) NOT NULL,
    weapon_id INT NOT NULL,
    score INT NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_character (character_id),
    INDEX idx_weapon_score (weapon_id, score DESC)
);

global_rank(总榜,冗余表,TOP 100)

CREATE TABLE global_rank (
    rank INT PRIMARY KEY,
    character_id BIGINT NOT NULL,
    username VARCHAR(64) NOT NULL,
    weapon_id INT NOT NULL,
    score INT NOT NULL,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

weaponrank{id}(各武器榜,TOP 100)

-- 16张表:weapon_rank_2000, weapon_rank_2001, ... weapon_rank_2015
CREATE TABLE weapon_rank_2000 (
    rank INT PRIMARY KEY,
    character_id BIGINT NOT NULL,
    username VARCHAR(64) NOT NULL,
    score INT NOT NULL,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- (其余15张表结构相同)

考虑到 SQLite(服务端未定),可用单表 + weapon_id 过滤实现,或 JSON 分表。此处用 MySQL 多表描述。


四、Proto 消息

// 上报分数(游戏结束时自动调用)
message C2S_RankSubmit {
    int32 weapon_id = 1;     // 当前武器ID(2000-2015)
    int32 score = 2;        // 本局得分(ScoreSystem.CurrentScore)
}

// 上报结果
message S2C_RankSubmitResult {
    int32 code = 1;          // 0=成功,其他=错误码
    string msg = 2;
    int32 rank_in_total = 3; // 总榜排名(-1=未上榜)
    int32 rank_in_weapon = 4; // 武器榜排名(-1=未上榜)
}

// 查询排行榜
message C2S_RankQuery {
    int32 query_type = 1;    // 0=总榜,1=武器榜
    int32 weapon_id = 2;     // query_type=1 时填武器ID(2000-2015),否则填0
    int32 offset = 3;        // 起始位置(0=第1名)
    int32 limit = 4;         // 条数(最大100)
}

// 排行榜条目
message RankEntry {
    int32 rank = 1;          // 排名(1-100)
    string username = 2;      // 用户名
    int32 score = 3;         // 分数
    int32 weapon_id = 4;      // 武器ID(总榜有,武器榜无)
}

// 查询结果
message S2C_RankQueryResult {
    int32 code = 1;           // 0=成功
    int32 query_type = 2;     // 查询类型(0=总榜,1=武器榜)
    int32 weapon_id = 3;      // 武器ID(查询武器榜时)
    repeated RankEntry entries = 4;
    int32 my_rank = 5;        // 我的排名(-1=未上榜)
    int32 my_score = 6;       // 我的分数(查询时用的那个)
}

五、错误码

code 说明
0 成功
1 角色不存在
2 分数无效(<=0)
3 武器ID无效
10 查询类型无效

六、完整流程

上报(游戏结束时)

客户端                         服务端
   |                              |
   |   GameStateManager.OnPlayerDead()
   |                              |
   |--- C2S_RankSubmit --------->|
   |    (weapon_id=2000, score=12345)
   |                              |
   |                              | 1. 验证 character_id / weapon_id / score
   |                              | 2. 写入 character_rank_scores
   |                              | 3. 更新 global_rank(重新排序,插入或忽略)
   |                              | 4. 更新 weapon_rank_{id}(重新排序,插入或忽略)
   |                              | 5. 计算 rank_in_total / rank_in_weapon
   |                              |
   |<-- S2C_RankSubmitResult ----|
   |    (code=0, rank_in_total=42, rank_in_weapon=3)
   |                              |
   |   RankingService.OnRankSubmitResult?.Invoke(result)

查询(玩家主动拉取)

客户端                         服务端
   |                              |
   |--- C2S_RankQuery ----------->|
   |    (query_type=0, offset=0, limit=20)  // 总榜前20名
   |                              |
   |<-- S2C_RankQueryResult ------|
   |    (code=0, query_type=0, entries=[...])
   |                              |
   |--- C2S_RankQuery ----------->|
   |    (query_type=1, weapon_id=2000, offset=0, limit=20)  // 武器2000榜
   |                              |
   |<-- S2C_RankQueryResult ------|
   |    (code=0, query_type=1, weapon_id=2000, entries=[...])

七、服务端实现

新增文件

server/RankService.cs

// 上报处理
public async Task<RankSubmitResult> SubmitScoreAsync(long characterId, string username, int weaponId, int score);

// 查询处理
public async Task<RankQueryResult> QueryRankAsync(long characterId, int queryType, int weaponId, int offset, int limit);

// 内部:重新计算 global_rank 和 weapon_rank_{id}
private void RebuildGlobalRank();
private void RebuildWeaponRank(int weaponId);

server/RankController.cs

  • WebSocket 处理(C2S_RankSubmit → S2C_RankSubmitResult)
  • WebSocket 处理(C2S_RankQuery → S2C_RankQueryResult)

数据库操作

上报时:

// 插入记录
INSERT INTO character_rank_scores (character_id, username, weapon_id, score) VALUES (?, ?, ?, ?)

// 更新总榜(取 TOP 100)
INSERT INTO global_rank (rank, character_id, username, weapon_id, score)
SELECT COUNT(*) + 1, ?, ?, ?, ? 
FROM global_rank WHERE score > ?
ON DUPLICATE KEY UPDATE score=VALUES(score), username=VALUES(username), weapon_id=VALUES(weapon_id), updated_at=NOW()

// 更新武器榜(取 TOP 100)
INSERT INTO weapon_rank_{id} (rank, character_id, username, score)
...

查询时:

SELECT rank, username, score FROM global_rank ORDER BY rank LIMIT ? OFFSET ?
SELECT rank, username, score FROM weapon_rank_{id} ORDER BY rank LIMIT ? OFFSET ?

八、客户端实现

改动点

1. ScoreSystem.cs — Settle() 新增网络上报

public void Settle()
{
    bool isNewRecord = RankingService.Instance.SubmitScore(
        AuthService.Instance.Username,
        _currentScore
    );

    // 新增:上报到服务器
    WebSocketService.Instance.SubmitRank(
        WeaponSystem.Instance.CurrentWeaponId,
        _currentScore
    );

    if (isNewRecord) {
        EventDispatcher.Instance.Publish(EventID.OnNewRecord, _currentScore);
    }
}

2. RankingService.cs — 预留网络回调

public event Action<RankSubmitResult> OnRankSubmitResult;

public void SubmitRankNetwork(int weaponId, int score) {
    WebSocketService.Instance.SubmitRank(weaponId, score);
}

3. WebSocketService.cs — 新增

// 上报
public void SubmitRank(int weaponId, int score) {
    var req = new C2S_RankSubmit { WeaponId = weaponId, Score = score };
    Send(MsgId.C2S_RankSubmit, req);
}

// 查询
public void QueryRank(int queryType, int weaponId, int offset, int limit) {
    var req = new C2S_RankQuery { QueryType = queryType, WeaponId = weaponId, Offset = offset, Limit = limit };
    Send(MsgId.C2S_RankQuery, req);
}

// 处理结果
case MsgId.S2C_RankSubmitResult:
    var submitResult = _decoder.DecodeS2C_RankSubmitResult(pkg.Data);
    MainThreadDispatcher.Invoke(() => RankingService.Instance.OnRankSubmitResult?.Invoke(submitResult));
    break;

case MsgId.S2C_RankQueryResult:
    var queryResult = _decoder.DecodeS2C_RankQueryResult(pkg.Data);
    MainThreadDispatcher.Invoke(() => OnRankQueryResult?.Invoke(queryResult));
    break;

事件:

public event Action<S2C_RankSubmitResult> OnRankSubmitResult;
public event Action<S2C_RankQueryResult> OnRankQueryResult;

九、注意事项

  1. 同分数处理:按 timestamp 排序,早提交的排前
  2. 客户端查榜:排行榜界面打开时拉取,不主动推送
  3. NETWORK_ENABLED=false 时:本地流程不变,网络上报跳过(GameConfig.NETWORK_ENABLED)
  4. 升级武器后:武器榜显示的是使用该武器的历史最高分,而非当前武器属性
  5. TopScore 更新:上报后若分数更高,InventoryService.Instance.TopScore 应同步更新

十、MsgId 分配建议

消息 MsgId
C2S_RankSubmit 60
S2C_RankSubmitResult 61
C2S_RankQuery 62
S2C_RankQueryResult 63

十一、已完成清单

  • [x] 更新 Ninjiafall1Proto.proto(添加 RankSubmit / RankSubmitResult / RankQuery / RankQueryResult + RankEntry)
  • [x] 重新编译 protobuf(build.sh)
  • [x] 数据库:character_rank_scores / global_rank / weaponrank* 表
  • [x] 服务端:RankService.cs — SubmitScoreAsync / QueryRankAsync,16表自动建
  • [x] 服务端:RankController.cs(WebSocket)— HandleRankSubmit / HandleRankQuery
  • [x] 服务端:WebSocketServer.cs — 注册 MsgId.C2S_RankSubmit / C2S_RankQuery
  • [x] 服务端:Program.cs — DI 注册 RankService
  • [x] 客户端:ProtobufTypes.cs — 添加消息类型
  • [x] 客户端:ProtobufCodec.cs — 编解码支持
  • [x] 客户端:WebSocketService.cs — SubmitRank + QueryRank + 事件
  • [x] 客户端:ScoreSystem.Settle() — 新增网络上报调用
  • [ ] 测试:上报成功 / 分数被刷新 / 查询总榜 / 查询武器榜(待联调)
  • 留下精彩的评论吧~(0条)

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