排行榜 #

我们设置了比赛、好友、部落和存储数据的方法。我们来将其整合到一起,显示顶尖玩家的排行榜,促进竞争。

排行榜
排行榜

设置排行榜 #

对于想要创建的每个排行榜,我们运行 leaderboardCreate。此函数有多个参数:

  • ID/名称
  • 排行榜是否为权威的布尔值(如为是,则仅有服务器可以更新排行榜)
  • 排序(SortOrder.ASCENDINGSortOrder.DESCENDING
  • 对分数求和的运算符
    • Operator.BEST:始终取提交的最好分数
    • Operator.SET:始终取提交的最新分数
    • Operator.INCR:将提交的分数添加到总分
  • 重置时间表,其为 CRON 表达式或从不重置的排行榜的 null
  • 要存储到排行榜中的元数据

对于 Pirate Panic,我们设置一个全局排行榜,显示所有玩家所有时候的最高分数:

main.ts

1
2
3
4
5
6
7
const id = "global_leaderboard";
const authoritative = false;
const metadata = {};
const scoreOperator = nkruntime.Operator.BEST;
const sortOrder = nkruntime.SortOrder.DESCENDING;
const resetSchedule = null;
nk.leaderboardCreate(id, authoritative, sortOrder, scoreOperator, resetSchedule, metadata);

选择排行榜可以接受新的记录。

向排行榜添加 #

每场比赛结束后,我们将把玩家分数记录到排行榜上。每当比赛结束时,客户端都会向服务器发送 RPC 请求以处理游戏结束行为:

match.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
interface MatchEndRequest { // Create a structure to align the payload to
    matchId : string;
    placement: MatchEndPlacement;
    time : number;
    towersDestroyed : number;
}


const rpcHandleMatchEnd: nkruntime.RpcFunction = function(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, payload: string): string {
    ... // Add gems to wallet, calculate score, etc. etc.
    let request : MatchEndRequest = JSON.parse(payload);
    let score = calculateScore(request.placement == MatchEndPlacement.Winner, request.towersDestroyed, request.time);
    nk.leaderboardRecordWrite(globalLeaderboard, ctx.userId, ctx.username, score);
    ...
}

如何计算分数取决于您和游戏机制。您可以考虑谁赢了,花了多长时间才赢了,或者他们像我们在这个例子中一样摧毁了多少座塔。所有这些信息都从客户端传递到有效载荷中。

一旦计算出分数,我们将其作为一个数字与用户 ID 和用户名一起传递到 leaderboardRecordWrite,然后排行榜引擎将进行后续处理。

此外,还可以使用 WriteLeaderboardRecordAsync 在 Unity 端达到这个目的。

列出排行榜记录 #

现在已为处理新的排行榜记录设置好服务器,让我们为玩家提供一种查看服务器上存储的内容的方式。这在客户端使用 ListLeaderboardRecordsAsync 完成:

LeaderboardsMenuUI.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
[SerializeField] private int _recordsPerPage = 100;
...
public async void ShowGlobalLeaderboards(string cursor = null)
{
    // Fetch all records from the leaderboard "global"
    IApiLeaderboardRecordList records = await _connection.Client.ListLeaderboardRecordsAsync(_connection.Session, "global", ownerIds: null, expiry: null, _recordsPerPage, cursor);

    SetLeaderboardsCursor(records, ShowGlobalLeaderboards);
    ...
}

对于上述示例,SetLeaderboardsCursor 会形如:

LeaderboardsMenuUI.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
private void SetLeaderboardsCursor(IApiLeaderboardRecordList records, Action<string> caller)
{
    if (records.PrevCursor != null) {
        _prevPageButton.interactable = true;
        _prevPageButton.onClick.RemoveAllListeners();
        _prevPageButton.onClick.AddListener(() => caller(records.PrevCursor));
    } else {
        _prevPageButton.interactable = false;
    }
    if (records.NextCursor != null) {
        _nextPageButton.interactable = true;
        _nextPageButton.onClick.RemoveAllListeners();
        _nextPageButton.onClick.AddListener(() => caller(records.NextCursor));
    } else {
        _nextPageButton.interactable = false;
    }
}

ListLeaderboardRecordsAsync 返回的 records 列表带有两个函数,PrevCursorNextCursor,每个都抓取上一页或下一页记录(如果存在这一页)。

通过将这些功能绑定到按钮,我们可以让玩家轻松浏览记录。PrevCursorNextCursor 都将遵守在原始 ListLeaderboardRecordsAsync 调用中设置的每页记录数,并将更新 records 对象,以保留下一页记录。

为实际显示这些记录,可以在 records 中的每条记录上使用 foreach 循环:

LeaderboardsMenuUI.cs

1
2
3
4
5
6
7
8
9
foreach (IApiLeaderboardRecord record in records)
{
    string username = record.Username;
    if (localId == record.OwnerId)
    {
        username += " (You)";
    }
    ...
}

每个 record 保留一个 UsernameOwnerIdScore,以及存储在可选数据库中的任何其他自定义字段。

过滤排行榜 #

我们还可以过滤出全局排行榜,看看我们的好友或部落的表现如何。

在前一部分,我们以 null 退出 ownerIds ,因为我们想要获取所有条目。现在,我们可以使用此参数仅获取具有特定所有者的条目。

例如,我们可以使用 ListFriendsAsync 获取所有好友:

1
var friends = await _connection.Client.ListFriendsAsync(_connection.Session);

然后,我们可以提取他们的所有用户 ID 并将其存储到列表中:

1
List<string> ids = friends.Friends.Select(x => x.User.Id).ToList();

最后,我们将 ids 列表传入过滤器,在 records 中将仅显示此列表中的用户:

1
IApiLeaderboardRecordList records = await _connection.Client.ListLeaderboardRecordsAsync(_connection.Session, "global", ids, null, 1, cursor);

在这里,我们从同一排行榜 (global) 获取记录,并将 ids 作为 ownerIds,并将每页记录数设置为 1

可以执行一个非常类似的过程,对排行榜进行过滤,以仅显示您的部落的成员的分数:

1
2
3
4
var users = await _connection.Client.ListGroupUsersAsync(_connection.Session, _userClan.Group.Id, null, 1, null);
IEnumerable<string> ids = users.GroupUsers.Select(x => x.User.Id);

IApiLeaderboardRecordList list = await _connection.Client.ListLeaderboardRecordsAsync(_connection.Session, "global", ids, null, 1, cursor);

下一主题 #

通知

Related Pages