Friend and Group Leaderboards
#
Nakama Leaderboards give players a competitive activity to engage around. As a game grows, massive leaderboards can feel static and more challenging for players to climb the ranks. You can address this with:
Using the leaderboard list API you can pass in a list of user IDs to create a custom leaderboard view.
The following code samples show you how to get leaderboard records for a group’s members or user’s friends.
Creating the leaderboard
#
Create a leaderboard on the server that resets every Monday at 00:00.
Server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| func InitModule(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, initializer runtime.Initializer) error {
// Create a weekly leaderboard
id := "weekly_leaderboard"
authoritative := false
sortOrder := "desc"
operator := "incr"
resetSchedule := "0 0 * * 1"
metadata := make(map[string]interface{})
if err := nk.LeaderboardCreate(ctx, id, authoritative, sortOrder, operator, resetSchedule, metadata); err != nil {
logger.Error("error creating leaderboard")
return err
}
}
|
Server
1
2
3
4
5
6
7
8
9
10
| let InitModule: nkruntime.InitModule = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, initializer: nkruntime.Initializer) {
const id = 'weekly_leaderboard';
const authoritative = false;
const sortOrder = nkruntime.SortOrder.DESCENDING;
const operator = nkruntime.Operator.INCREMENTAL;
const resetSchedule = '0 0 * * 1';
const metadata = {};
nk.leaderboardCreate(id, authoritative, sortOrder, operator, resetSchedule, metadata);
}
|
Code snippet for this language Lua has not been found. Please choose another language to show equivalent examples.
Getting a custom view of the leaderboard
#
Define the payload structure for the RPC:
Server
1
2
3
4
5
6
| type leaderboardRecord struct {
Username string `json:"username"`
UserId string `json:"userId"`
Score int `json:"score"`
Rank int `json:"rank"`
}
|
Server
1
2
3
4
5
6
| interface LeaderboardRecord {
username: string,
userId: string,
score: number,
rank: number
}
|
Code snippet for this language Lua has not been found. Please choose another language to show equivalent examples.
Create a helper function that will take an array of user IDs and return an array of those records along with the relative Rank value based on user scores.
Server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| func getLeaderboardForUsers(leaderboardId string, userIds []string, ctx context.Context, logger runtime.Logger, nk runtime.NakamaModule) ([]leaderboardRecord, error) {
// Get all leaderboard records for user Ids
_, records, _, _, err := nk.LeaderboardRecordsList(ctx, leaderboardId, userIds, 0, "", 0)
if err != nil {
return nil, err
}
// Create result slice and add a rank value
results := []leaderboardRecord{}
for i, record := range records {
r := leaderboardRecord{
Username: record.Username.Value,
UserId: record.OwnerId,
Score: int(record.Score),
Rank: len(records) - i,
}
results = append(results, r)
}
return results, nil
}
|
Server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| const getLeaderboardForUsers = function (leaderboardId: string, userIds: string[], logger: nkruntime.Logger, nk: nkruntime.Nakama): LeaderboardRecord[] {
const recordsList = nk.leaderboardRecordsList(leaderboardId, userIds);
const results: LeaderboardRecord[] = [];
recordsList.records.forEach(function (leaderboardRecord, i) {
results.push({
username: leaderboardRecord.username,
userId: leaderboardRecord.ownerId,
score: leaderboardRecord.score,
rank: recordsList.records.length - i
});
});
return results;
}
|
Code snippet for this language Lua has not been found. Please choose another language to show equivalent examples.
Getting the group leaderboard view
#
Now that you have a general function to get a leaderboard for a list of users, create a function to get a leaderboard for a group.
Define a payload structure for the RPC:
Server
1
2
3
4
| type groupLeaderboardRecordsPayload struct {
GroupId string `json:"groupId"`
LeaderboardId string `json:"leaderboardId"`
}
|
Server
1
2
3
4
| interface groupLeaderboardRecordsPayload {
groupId: string,
leaderboardId: string
}
|
Code snippet for this language Lua has not been found. Please choose another language to show equivalent examples.
Create a function that will get a slice of members from a group and then get the leaderboard for them:
Server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
| func getGroupLeaderboardRecords(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
// Unmarshal the payload
data := &groupLeaderboardRecordsPayload{}
if err := json.Unmarshal([]byte(payload), data); err != nil {
logger.Error("error unmarshaling payload")
return "", err
}
// Get leaderboard
leaderboards, err := nk.LeaderboardsGetId(ctx, []string{data.LeaderboardId})
if err != nil {
logger.Error("error getting leaderboards")
return "", err
}
if len(leaderboards) == 0 {
errorMessage := fmt.Sprintf("error finding leaderboard: %s", data.LeaderboardId)
logger.Error(errorMessage)
return "", errors.New(errorMessage)
}
// Get group members
members, _, err := nk.GroupUsersList(ctx, data.GroupId, 100, nil, "")
if err != nil {
logger.Error("error getting group members")
return "", err
}
// Get a slice of memberIds
memberIds := []string{}
for _, member := range members {
memberIds = append(memberIds, member.User.Id)
}
// Get all leaderboard records for users
results, err := getLeaderboardForUsers(leaderboards[0].Id, memberIds, ctx, logger, nk)
if err != nil {
logger.Error("error getting leaderboard records")
return "", err
}
// Return the leaderboard records to the user
bytes, err := json.Marshal(results)
if err != nil {
logger.Error("error marshaling result")
return "", err
}
return string(bytes), nil
}
|
Server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| const getGroupLeaderboardRecords: nkruntime.RpcFunction = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, payload: string): string | void {
// Parse the payload
const data = <groupLeaderboardRecordsPayload> JSON.parse(payload);
if (!data) {
throw new Error('Error parsing payload');
}
// Get leaderboard
const leaderboards = nk.leaderboardsGetId([data.leaderboardId]);
if (leaderboards.length == 0) {
throw new Error(`Error finding leaderboard: ${data.leaderboardId}`);
}
// Get group members
const members = nk.groupUsersList(data.groupId, 100);
// Get a slice of memberIds
const memberIds: string[] = [];
members.groupUsers.forEach(function (groupUser) {
memberIds.push(groupUser.user.userId);
});
// Get all leaderboard records for users
const results = getLeaderboardForUsers(leaderboards[0].id, memberIds, logger, nk);
// Return the leaderboard records to the user
return JSON.stringify(results);
};
|
Code snippet for this language Lua has not been found. Please choose another language to show equivalent examples.
Register the function and expose it as a remote procedure that can be called from the client:
Server
1
2
3
4
5
6
7
8
9
10
11
| func InitModule(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, initializer runtime.Initializer) error {
// ...
// Register RPC to retrieve a custom view over a leaderboard based on group members
if err := initializer.RegisterRpc("getGroupLeaderboardRecords", getGroupLeaderboardRecords); err != nil {
logger.Error(`error registering "getGroupLeaderboardRecords" rpc`)
return err
}
return nil
}
|
Server
1
2
3
4
5
6
| let InitModule: nkruntime.InitModule = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, initializer: nkruntime.Initializer) {
// ...
// Register RPC to retrieve a custom view over a leaderboard based on group members
initializer.registerRpc('getGroupLeaderboardRecords', getGroupLeaderboardRecords);
}
|
Code snippet for this language Lua has not been found. Please choose another language to show equivalent examples.
Getting the friend leaderboard view
#
Similarly, create a function to get a leaderboard for a user’s friends.
Define a payload structure for the RPC:
Server
1
2
3
| type friendsLeaderboardRecordsPayload struct {
LeaderboardId string `json:"leaderboardId"`
}
|
Server
1
2
3
| interface friendsLeaderboardReocrdsPayload {
leaderboardId: string
}
|
Code snippet for this language Lua has not been found. Please choose another language to show equivalent examples.
Create a function that will get a slice of a user’s friends and then get the leaderboard for them:
Server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
| func getFriendLeaderboardRecords(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
// Unmarshal the payload
data := &friendsLeaderboardRecordsPayload{}
if err := json.Unmarshal([]byte(payload), data); err != nil {
logger.Error("error unmarshaling payload")
return "", err
}
// Get leaderboard
leaderboards, err := nk.LeaderboardsGetId(ctx, []string{data.LeaderboardId})
if err != nil {
logger.Error("error getting leaderboards")
return "", err
}
if len(leaderboards) == 0 {
errorMessage := fmt.Sprintf("error finding leaderboard: %s", data.LeaderboardId)
logger.Error(errorMessage)
return "", errors.New(errorMessage)
}
// Get user id from context
userId, ok := ctx.Value(runtime.RUNTIME_CTX_USER_ID).(string)
if !ok {
errorMessage := fmt.Sprintf("error getting user id from context")
logger.Error(errorMessage)
return "", errors.New(errorMessage)
}
// Get friends (where state is 0 - mutual friends)
state := 0
friends, _, err := nk.FriendsList(ctx, userId, 100, &state, "")
if err != nil {
logger.Error("error getting friends")
return "", err
}
// Get a slice of memberIds
friendIds := []string{}
for _, member := range friends {
friendIds = append(friendIds, member.User.Id)
}
// Get all leaderboard records for users
results, err := getLeaderboardForUsers(leaderboards[0].Id, friendIds, ctx, logger, nk)
if err != nil {
logger.Error("error getting leaderboard records")
return "", err
}
// Return the leaderboard records to the user
bytes, err := json.Marshal(results)
if err != nil {
logger.Error("error marshaling result")
return "", err
}
return string(bytes), nil
}
|
Server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| const getFriendLeaderboardRecords: nkruntime.RpcFunction = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, payload: string): string | void {
// Parse the payload
const data = <friendsLeaderboardReocrdsPayload> JSON.parse(payload);
if (!data) {
throw new Error('Error parsing payload');
}
// Get leaderboard
const leaderboards = nk.leaderboardsGetId([data.leaderboardId]);
if (leaderboards.length == 0) {
throw new Error(`Error finding leaderboard: ${data.leaderboardId}`);
}
// Get friends (where state is 0 - mutual friends)
const friendsList = nk.friendsList(ctx.userId, 100, 0);
// Get a slice of friendIds
const friendIds: string[] = [];
friendsList.friends.forEach(function (friend) {
friendIds.push(friend.user.userId);
});
// Get all leaderboard records for users
const results = getLeaderboardForUsers(leaderboards[0].id, friendIds, logger, nk);
// Return the leaderboard records to the user
return JSON.stringify(results);
};
|
Code snippet for this language Lua has not been found. Please choose another language to show equivalent examples.
Register the function and expose it as a remote procedure that can be called from the client:
Server
1
2
3
4
5
6
7
8
9
10
11
| func InitModule(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, initializer runtime.Initializer) error {
// ...
// Register RPC to retrieve a custom view over a leaderboard based on friends
if err := initializer.RegisterRpc("getFriendLeaderboardRecords", getFriendLeaderboardRecords); err != nil {
logger.Error(`error registering "getFriendLeaderboardRecords" rpc`)
return err
}
return nil
}
|
Server
1
2
3
4
5
6
| let InitModule: nkruntime.InitModule = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, initializer: nkruntime.Initializer) {
// ...
// Register RPC to retrieve a custom view over a leaderboard based on friends
initializer.registerRpc('getFriendLeaderboardRecords', getFriendLeaderboardRecords);
}
|
Code snippet for this language Lua has not been found. Please choose another language to show equivalent examples.