排行榜指南
环境要求
- Unity
- Android
- iOS
- UE4
- Unity 2019.4 或更高版本
- iOS 11 或更高版本,Xcode 版本 14.1 或更高版本
- Android 5.0(API level 21)或更高版本
Android 5.0(API level 21)或更高版本
iOS 11 或更高版本,Xcode 版本 14.1 或更高版本
- 安装 UE 4.26 及以上版本
- iOS 12 或更高版本
- Android 5.0(API level 21)或更高版本
支持平台:Android / iOS
权限说明
- Unity
- Android
- iOS
- UE4
该模块需要如下权限:
权限 | 使用目的 | 权限申请时机 |
---|---|---|
网络权限 | 用于访问网络数据 | 用户首次使用该功能时会申请权限 |
该模块将在应用中添加如下权限:
<uses-permission android:name="android.permission.INTERNET" />
集成前准备
- 参考 准备工作 创建应用、开启内建账户、排行榜服务、绑定 API 域名;
SDK 获取
排行榜功能是数据存储 SDK 的一部分,排行榜 SDK 配置与数据存储 SDK 配置一致;
- Unity
- Android
- iOS
- UE4
SDK 可以通过 Unity Package Manager 导入或手动导入,二者任选其一。请根据项目需要选择。
方法一:使用 Unity Package Manager
在项目的 Packages/manifest.json
文件中添加以下依赖:
"dependencies":{
"com.leancloud.realtime": "https://github.com/leancloud/csharp-sdk-upm.git#realtime-2.3.0",
"com.leancloud.storage": "https://github.com/leancloud/csharp-sdk-upm.git#storage-2.3.0",
}
在 Unity 顶部菜单中选择 Window > Package Manager 可查看已经安装在项目中的包。
方法二:手动导入
在 下载页 找到 LeanCloud C# SDK 下载地址,下载
LeanCloud-SDK-Realtime-Unity.zip
。解压后的
LeanCloud-SDK-Realtime-Unity.zip
为 Plugins 文件夹,拖拽至 Unity 即可。
- 打开项目的
project/app/build.gradle
文件,添加 gradle 配置如下:
dependencies {
...
implementation 'com.taptap:lc-storage-android:8.2.24'
}
- 下载 TapSDK UE4,TapSDK-UE4-xxx.zip 解压后将
LeanCloud
文件夹 Copy 到项目的Plugins
目录中 - 重启 Unreal Editor
- 打开 编辑 > 插件 > 项目 > LeanCloud,开启
LeanCloud
模块
添加依赖
在 Project.Build.cs 中添加所需模块:
PublicDependencyModuleNames.AddRange(new string[] { "Core",
"CoreUObject",
"Engine",
"Json",
"InputCore",
"JsonUtilities",
"SlateCore",
});
if (Target.Platform == UnrealTargetPlatform.IOS || Target.Platform == UnrealTargetPlatform.Android)
{
PublicDependencyModuleNames.AddRange(
new string[]
{
"LeanCloudMobile"
}
);
}
else
{
PublicDependencyModuleNames.AddRange(
new string[]
{
"LeanCloud"
}
);
}
导入头文件
#include "LCLeaderboard.h"
SDK 初始化
内建账户 SDK 初始化完成后会同步初始化排行榜功能,因此无需重复初始化;
快速入门
创建排行榜
创建排行榜有 3 种方式:
- 在 TapTap 开发者中心的排行榜控制台创建。
- 在服务端等受信任的环境下调用 REST API 创建。
- 在服务端等受信任的环境下调用 SDK 的管理接口创建。
比如,可以在控制台创建一个名称为 world
,成员类型为「内建账户」,排序为 descending
,更新策略为 better
,重置周期为「每月」的世界排行榜。
提交成绩
假设玩家已登录(currentUser
),通过如下代码即可更新成绩:
- Unity
- Android
- iOS
- UE4
var statistic = new Dictionary<string, double>();
statistic["world"] = 20.0;
await LCLeaderboard.UpdateStatistics(currentUser, statistic);
Map<String, Double> statistic = new HashMap<>();
statistic.put("world", 20.0);
LCLeaderboard.updateStatistic(currentUser, statistic).subscribe(new Observer<LCStatisticResult>() {
@Override
public void onSubscribe(@NotNull Disposable disposable) {}
@Override
public void onNext(@NotNull LCStatisticResult jsonObject) {
// scores saved
}
@Override
public void onError(@NotNull Throwable throwable) {
// handle error
}
@Override
public void onComplete() {}
});
NSDictionary *statistic = @{
@"world" : 20.0,
};
[LCLeaderboard updateCurrentUserStatistics:statistic, callback:^(NSArray *statistics, NSError *error) {
if (statistics) {
// statistics 是更新后你的最好/最新成绩
} else if (error) {
// 处理错误
}
}];
TMap<FString, double> Statistic;
Statistic.Add("world", 20.0);
FLCLeaderboard::FStatisticsDelegate OnSuccess = FLCLeaderboard::FStatisticsDelegate::CreateLambda([=](TArray<FLCLeaderboardStatistic> Statistics) {
// statistics 是更新后你的最好/最新成绩
});
FLCError::FDelegate OnError = FLCError::FDelegate::CreateLambda([=](const FLCError& Error) {
// 处理错误
});
FLCLeaderboard::UpdateCurrentUserStatistics(Statistic, OnSuccess, OnError);
获取名次
接下来我们获取排行榜中的前十,由于之前我们只提交了一个玩家(当前玩家)的成绩,所以结果中只包含一个成绩。
- Unity
- Android
- iOS
- UE4
var leaderboard = LCLeaderboard.CreateWithoutData("world");
var rankings = await leaderboard.GetResults(limit: 10);
LCLeaderboard leaderboard = LCLeaderboard.createWithoutData("world");
leaderboard.getResults(0, 10, null, null).subscribe(new Observer<LCLeaderboardResult>() {
@Override
public void onSubscribe(@NotNull Disposable disposable) {}
@Override
public void onNext(@NotNull LCLeaderboardResult leaderboardResult) {
List<LCRanking> rankings = leaderboardResult.getResults();
// process rankings
}
@Override
public void onError(@NotNull Throwable throwable) {
// handle error
}
@Override
public void onComplete() {}
});
LCLeaderboard *leaderboard = [[LCLeaderboard alloc] initWithStatisticName:@"world"];
leaderboard.limit = 10;
[leaderboard getUserResultsWithOption:nil callback:^(NSArray *rankings, NSInteger count, NSError *error) {
// rankings 是排行榜前十的排名信息
}];
FLCLeaderboard Leaderboard("world", FLCLeaderboard::User);
Leaderboard.Limit = 10;
FLCLeaderboard::FRankingsDelegate OnSuccess = FLCLeaderboard::FRankingsDelegate::CreateLambda([=](TArray<FLCLeaderboardRanking> Rankings, int64 Count) {
// process rankings
});
FLCError::FDelegate OnError = FLCError::FDelegate::CreateLambda([=](const FLCError& Error) {
// handle error
});
Leaderboard.GetResults(OnSuccess, OnError);
看过上面这个最简单的接入排行榜的例子后,下面就详细介绍排行榜的各个接口。
成绩管理
更新用户成绩
当用户完成了一局游戏后,你可以在客户端使用 SDK 的 updateStatistic
方法更新该用户的成绩。
不过,从反作弊的角度出发,我们建议在控制台勾选「只允许使用 Master Key 更新分数」,然后在服务端更新成绩。
- Unity
- Android
- iOS
- UE4
var statistic = new Dictionary<string, double> {
{ "score", 3458.0 },
{ "kills", 28.0 }
};
await LCLeaderboard.UpdateStatistics(currentUser, statistic);
Map<String, Double> statistic = new HashMap<>();
statistic.put("score", 3458.0);
statistic.put("kills", 28.0);
LCLeaderboard.updateStatistic(currentUser, statistic).subscribe(new Observer<LCStatisticResult>() {
@Override
public void onSubscribe(@NotNull Disposable disposable) {}
@Override
public void onNext(@NotNull LCStatisticResult jsonObject) {
// saved
}
@Override
public void onError(@NotNull Throwable throwable) {
// handle error
}
@Override
public void onComplete() {}
});
NSDictionary *statistic = @{
@"score" : 3458.0,
@"kills" : 28.0,
};
[LCLeaderboard updateCurrentUserStatistics:statistic callback:^(NSArray *statistics, NSError *error) {
if (!error) {
// saved
}
}];
TMap<FString, double> Statistic;
Statistic.Add("score", 3458.0);
Statistic.Add("kills", 28.0);
FLCLeaderboard::FStatisticsDelegate OnSuccess = FLCLeaderboard::FStatisticsDelegate::CreateLambda([=](TArray<FLCLeaderboardStatistic> Statistics) {
// saved
});
FLCError::FDelegate OnError = FLCError::FDelegate::CreateLambda([=](const FLCError& Error) {
// handle error
});
FLCLeaderboard::UpdateCurrentUserStatistics(Statistic, OnSuccess, OnError);
更新用户成绩需要玩家登录,玩家只能更新自己的成绩。
你可以一次性更新多个排行榜的成绩,比如上面的示例中同时更新了得分(score
)和击杀(kills
)两个排行榜的成绩。
客户端无法更新 object 和 entity 的成绩。 如需更新其他用户、object、entity 的成绩,需要使用 REST API 或 SDK 提供的管理接口。
删除用户成绩
玩家也可以删除自己的成绩:
- Unity
- Android
- iOS
- UE4
await LCLeaderboard.DeleteStatistics(currentUser, new List<string> { "world" });
// 暂未支持
[LCLeaderboard deleteCurrentUserStatistics:@[@"world"], callback:^(BOOL succeeded, NSError * _Nullable error) {
if (succeeded) {
// 删除成功
} else if (error) {
// 处理错误
}
}];
// 暂未支持
和更新成绩一样,玩家只能删除自己的成绩。 如需删除其他用户、object、entity 的成绩,需要使用 REST API 或 SDK 提供的管理接口。
查询排行榜成员成绩
已登录用户可以通过 GetStatistics
方法查询其他用户在所有排行榜中的成绩:
- Unity
- Android
- iOS
- UE4
var otherUser = LCObject.CreateWithoutData(TDSUser.CLASS_NAME, "5c76107144d90400536fc88b");
var statistics = await LCLeaderboard.GetStatistics(otherUser);
foreach(var statistic in statistics) {
Debug.Log(statistic.Name);
Debug.Log(statistic.Value);
}
// 查询排行榜成员成绩
LCUser otherUser = null;
try {
otherUser = LCUser.createWithoutData(LCUser.class, "5c76107144d90400536fc88b");
} catch (LCException e) {
e.printStackTrace();
}
LCLeaderboard.getUserStatistics(otherUser).subscribe(new Observer<LCStatisticResult>() {
@Override
public void onSubscribe(@NotNull Disposable disposable) {}
@Override
public void onNext(@NotNull LCStatisticResult lcStatisticResult) {
List<LCStatistic> statistics = lcStatisticResult.getResults();
for (LCStatistic statistic : statistics) {
Log.d(TAG, statistic.getName());
Log.d(TAG, String.valueOf(statistic.getValue()));
}
}
@Override
public void onError(@NotNull Throwable throwable) {
// handle error
Toast.makeText(MainActivity.this, "查询失败: " + throwable.getMessage(), Toast.LENGTH_SHORT).show();
}
@Override
public void onComplete() {}
});
NSString *otherUserObjectId = @"5c76107144d90400536fc88b";
[LCLeaderboard getStatisticsWithUserId:otherUserObjectId, statisticNames:nil, callback:^(NSArray<LCLeaderboardStatistic *> * _Nullable *statistics, NSError _Nullable *error) {
if (statistics) {
for (LCLeaderboardStatistic *statistic in statistics) {
NSLog(@"排行榜名称:%@", statistic.name);
NSLog(@"成绩:%f", statistic.value);
}
} else if (error) {
// 处理错误
}
}];
FLCLeaderboard::FStatisticsDelegate OnSuccess = FLCLeaderboard::FStatisticsDelegate::CreateLambda([=](TArray<FLCLeaderboardStatistic> Statistics) {
for (FLCLeaderboardStatistic Statistic : Statistics) {
// 排行榜名称:Statistic.Name;
// 成绩:Statistic.Value;
}
});
FLCError::FDelegate OnError = FLCError::FDelegate::CreateLambda([=](const FLCError& Error) {
// 处理错误
});
FString OtherUserObjectId = "5c76107144d90400536fc88b";
FLCLeaderboard::GetStatistics(OtherUserObjectId, OnSuccess, OnError, {}, FLCLeaderboard::User);
更多的场景下,只关心用户在某个或某几个排行榜中的成绩。 查询时指定相应排行榜的名称即可:
- Unity
- Android
- iOS
- UE4
var statistics = await LCLeaderboard.GetStatistics(otherUser, new List<string> { "world" });
LCLeaderboard.getUserStatistics(otherUser, Arrays.asList("world")).subscribe(/** 略 **/);
[LCLeaderboard getStatisticsWithUserId:otherUserObjectId, statisticNames:@[@"world"], callback:^(NSArray<LCLeaderboardStatistic *> * _Nullable statistics, NSError * _Nullable error) {
// 略
}];
FLCLeaderboard::GetStatistics(OtherUserObjectId, OnSuccess, OnError, {"world"}, FLCLeaderboard::User);
类似地,可以查询 object 和 entity 的成绩。
- Unity
- Android
- iOS
- UE4
例如,假定武器排行榜是 object 排行榜:
var excalibur = LCObject.createWithoutData("Weapon", "582570f38ac247004f39c24b");
var statistics = await LCLeaderboard.GetStatistics(excalibur, new List<string> { "weapons" });
如果武器排行榜是 entity 排行榜:
var statistics = await LCLeaderboard.GetStatistics("excalibur", new List<string> { "weapons" });
例如,假定武器排行榜是 object 排行榜:
String excaliburObjectId = "582570f38ac247004f39c24b";
LCLeaderboard.getMemberStatistics("Weapon", excaliburObjectId,
Arrays.asList("weapons")).subscribe(/** 略 **/);
如果武器排行榜是 entity 排行榜:
LCLeaderboard.getMemberStatistics(LCLeaderboard.MEMBER_TYPE_ENTITY, "excalibur",
Arrays.asList("weapons")).subscribe(/** 略 **/);
顺便提下,之前提到的 getUserStatistics
方法
LCLeaderboard.getUserStatistics(otherUser, Arrays.asList("weapons")).subscribe(/** 略 **/);
等价于:
LCLeaderboard.getMemberStatistics(LCLeaderboard.LCLeaderboard.MEMBER_TYPE_USER,
otherUser.getObjectId(),
Arrays.asList("weapons")).subscribe(/** 略 **/);
例如,假定武器排行榜是 object 排行榜:
NSString *excaliburObjectId = @"582570f38ac247004f39c24b";
[LCLeaderboard getStatisticsWithObjectId:excaliburObjectId, statisticNames:@[@"weapons"],
option:nil
callback:^(NSArray *statistics, NSError *error) {
// 略
}];
如果武器排行榜是 entity 排行榜:
[LCLeaderboard getStatisticsWithEntity:@"excalibur", statisticNames:@[@"weapons"],
callback:^(NSArray<LCLeaderboardStatistic *> * _Nullable *statistics, NSError * _Nullable error) {
// 略
}];
例如,假定武器排行榜是 object 排行榜:
FString ExcaliburObjectId = "582570f38ac247004f39c24b";
FLCLeaderboard::GetStatistics(ExcaliburObjectId, OnSuccess, OnError, {}, FLCLeaderboard::Object);
如果武器排行榜是 entity 排行榜:
FLCLeaderboard::GetStatistics("excalibur", OnSuccess, OnError, {}, FLCLeaderboard::Entity);
最后,还可以查询一组成员的成绩:
- Unity
- Android
- iOS
- UE4
var otherUser = LCObject.CreateWithoutData(TDSUser.CLASS_NAME, "5c76107144d90400536fc88b");
var anotherUser = LCObject.CreateWithoutData(TDSUser.CLASS_NAME, "672a127144a90d00536f3456");
var statistics = await LCLeaderboard.GetStatistics({otherUser, anotherUser}, new List<string> { "world" });
var oneObject = LCObject.CreateWithoutData<LCObject>("abccb27133a90ddd536ffffa");
var anotherUser = LCObject.CreateWithoutData<LCObject>("672a1279345777005a2b2444");
var statistics = await LCLeaderboard.GetStatistics({oneObject, anotherObject}, new List<string> { "weapons" });
var statistics = await LCLeaderboard.GetStatistics({"Sylgr", "Leiptr"}, new List<string> { "rivers" });
// 暂未支持
NSString *otherUserObjectId = @"5c76107144d90400536fc88b";
NSString *anotherUserObjectId = @"672a127144a90d00536f3456";
[leaderboard getStatisticsWithUserIds:@[otherUserObjectId, anotherUserObjectId]
callback:^(NSArray<LCLeaderboardStatistic *> * _Nullable statistics, NSError * _Nullable error) {
// 略
}];
NSString *oneObjectId = @"abccb27133a90ddd536ffffa";
NSString *anotherObjectId = @"672a1279345777005a2b2444";
[leaderboard getStatisticsWithObjectIds:@[oneObjectId, anotherObjectId]
option:nil
callback:^(NSArray<LCLeaderboardStatistic *> * _Nullable statistics, NSError * _Nullable error) {
// 略
}];
[leaderboard getStatisticsWithEntities:@[@"Sylgr", "Leiptr"]
callback:^(NSArray<LCLeaderboardStatistic *> * _Nullable statistics, NSError * _Nullable error) {
// 略
}];
// 暂未支持
获取排行榜结果
通过 Leaderboard#getResults
方法可以获取排行榜结果。
最常见的使用场景是获取排名前 N 的用户成绩和获取当前登录用户附近的排名。
首先,我们构造一个排行榜实例:
- Unity
- Android
- iOS
- UE4
var leaderboard = LCLeaderboard.CreateWithoutData("world");
LCLeaderboard.CreateWithoutData
方法接受两个参数:
public static LCLeaderboard CreateWithoutData(string statisticName, string memberType = LCLeaderboard.USER_MEMBER_TYPE)
statisticName
为排行榜名称,这里需要传入云端已经存在的排行榜名称。上例中是world
。memberType
为排行榜成员类型,传入LCLeaderboard.USER_MEMBER_TYPE
表示成员类型为用户,传入LCLeaderboard.ENTITY_MEMBER_TYPE
表示成员类型为 entity,成员类型为 object 时请传入相应 Class 名称。上例中省略了这一参数,表示使用默认值,成员类型为用户。
LCLeaderboard leaderboard = LCLeaderboard.createWithoutData("world");
LCLeaderboard.createWithoutData
方法接受两个参数:
public static LCLeaderboard createWithoutData(String name, String memberType)
name
为排行榜名称,这里需要传入云端已经存在的排行榜名称。上例中是world
。memberType
为排行榜成员类型,传入LCLeaderboard.MEMBER_TYPE_USER
表示成员类型为用户,传入LCLeaderboard.MEMBER_TYPE_ENTITY
表示成员类型为 entity,成员类型为 object 时请传入相应 Class 名称。用户是最常用的排行榜类型,因此createWithoutData
还提供了一个单参数的重载方法,上例中就只传入了排行榜名称,此时成员类型为用户。
LCLeaderboard leaderboard = [[LCLeaderboard alloc] initWithStatisticName:@"world"];
FLCLeaderboard Leaderboard("world", FLCLeaderboard::User);
FLCLeaderboard
初始化方法接受两个参数:
Name
为排行榜名称,这里需要传入云端已经存在的排行榜名称。上例中是world
。InMemberType
为排行榜成员类型,传入FLCLeaderboard::User
表示成员类型为用户,传入FLCLeaderboard::Entity
表示成员类型为 entity,传入FLCLeaderboard::Object
表示成员类型为 object
构造排行榜实例后,调用该实例上的相应方法即可获取排名。
获取指定区间的排名
获取排行榜的 Top 10:
- Unity
- Android
- iOS
- UE4
var rankings = await leaderboard.GetResults(limit: 10);
GetResults
方法可以指定以下参数限制查询范围:
参数名 | 类型 | 说明 |
---|---|---|
aroundUser | LCUser | 获取某用户附近的排名,详见下节。 |
aroundObject | LCObject | 获取某 object 附近的排名,详见下节 |
aroundEntity | string | 获取某 entity 附近的排名,详见下节。 |
limit | number | 限制返回的结果数量,默认 10。 |
skip | number | 指定从某个位置开始获取,与 limit 一起可以实现翻页,默认 0。 |
selectKeys | string[] | 指定返回的 Ranking 中的 user 需要包含的属性,详见下文。 |
includeKeys | string[] | 指定返回的 Ranking 中的 user 需要包含的 Pointer 属性,详见下文。 |
includeStatistics | string[] | 指定返回的 Ranking 中需要包含的其他成绩,详见下文。 |
version | number | 指定返回某个历史版本的成绩。 |
返回的结果是一个排名数组(Ranking[]
),Ranking
上有如下属性:
属性 | 类型 | 说明 |
---|---|---|
Rank | int | 排名,从 0 开始 |
User | LCUser | 该成绩的用户(user 排行榜) |
Object | LCObject | 该成绩的 object(object 排行榜) |
Entity | string | 该成绩的 entity(entity 排行榜) |
Value | double | 成绩 |
IncludedStatistics | List<Statistic> | 该成员在其他排行榜的成绩 |
leaderboard.getResults(0, 10, null, null).subscribe(new Observer<LCLeaderboardResult>() {
@Override
public void onSubscribe(@NotNull Disposable disposable) {}
@Override
public void onNext(@NotNull LCLeaderboardResult leaderboardResult) {
List<LCRanking> rankings = leaderboardResult.getResults();
}
@Override
public void onError(@NotNull Throwable throwable) {
// handle error
}
@Override
public void onComplete() {}
});
Leaderboard#getResults
方法可以指定以下参数限制查询范围:
Observable<LCLeaderboardResult> getResults(
int skip, int limit,
List<String> selectMemberKeys, List<String> includeStatistics)
skip
指定从某个位置开始获取,与limit
一起可以实现翻页,默认 0limit
限制返回的结果数量,默认 20selectMemberKeys
指定返回的 LCRanking 中排行榜成员需要包含的属性,详见下文includeStatistics
指定返回的 LCRanking 中需要包含的其他成绩,详见下文
如需获取历史版本的排行榜信息,可以在排行榜实例上设置版本号:
int previousVersion = currentVersion - 1;
leaderboard.setVersion(previousVersion);
LCRanking 提供了以下方法供获取排名信息:
// 排名,从 0 开始
int getRank()
// 该成绩的用户(user 排行榜)
LCUser getUser()
// 该成绩的 object(object 排行榜)
LCObject getObject()
// 该成绩的 entity(entity 排行榜)
String getEntityId()
// 成绩
double getStatisticValue()
// 该成员在其他排行榜的成绩
List<LCStatistic> getIncludedStatistics()
leaderboard.limit = 10;
[leaderboard getUserResultsWithOption:nil,
callback:^(NSArray <LCLeaderboardRanking *> * _Nullable *rankings, NSInteger count, NSError * _Nullable error) {
// rankings 是排行榜前十的排名信息
}];
Leaderboard 实例上可以设置如下属性限制查询范围:
/// 指定从某个位置开始获取,与 limit 一起可以实现翻页,默认 0
@property (nonatomic) NSInteger skip;
/// 限制返回的结果数量,默认 20
@property (nonatomic) NSInteger limit;
/// 指定返回的 LCLeaderboardRanking 中需要包含的其他成绩
@property (nonatomic, nullable) NSArray<NSString *> *includeStatistics;
/// 指定排行榜版本号,默认 0
@property (nonatomic) NSInteger version;
getUserResultsWithOption
的第一个参数是 LCLeaderboardQueryOption
,可以指定返回的 LCLeaderboardRanking
中排行榜成员需要包含的属性,详见下文。
回调中的 rankings
是一个 LCLeaderboardRanking 数组。
LCLeaderboardRanking 包含如下属性:
// 排行榜名称
@property (nonatomic, readonly, nullable) NSString *statisticName;
/// 排名,从 0 开始
@property (nonatomic, readonly) NSInteger rank;
/// 成绩
@property (nonatomic, readonly) double value;
/// 该成员在其他排行榜的成绩
@property (nonatomic, readonly, nullable) NSArray<LCLeaderboardStatistic *> *includedStatistics;
/// 该成绩的用户(user 排行榜)
@property (nonatomic, readonly, nullable) LCUser *user;
/// 该成绩的 object(object 排行榜)
@property (nonatomic, readonly, nullable) LCObject *object;
/// 该成绩的 entity(entity 排行榜)
@property (nonatomic, readonly, nullable) NSString *entity;
上例我们调用的是 getUserResultsWithOption
方法,所以 user
属性不为空,object
、entity
属性均为空。
如果排行榜是 object 排行榜或 entity 排行榜,那么相应地需要调用 getObjectResultsWithOption
及 getEntityResultsWithCallback
方法。
getObjectResultsWithOption
的参数和 getUserResultsWithOption
相同。
因为 entity 排行榜的成员为字符串,所以 getEntityResultsWithCallback
不支持 LCLeaderboardQueryOption
,第一个参数就是回调,回调的参数和 getUserResultsWithOption
、getObjectResultsWithOption
相同:
- (void)getEntityResultsWithCallback:(void (^)(NSArray<LCLeaderboardRanking *> * _Nullable rankings, NSInteger count, NSError * _Nullable error))callback;
Leaderboard.Limit = 10;
FLCLeaderboard::FRankingsDelegate OnSuccess = FLCLeaderboard::FRankingsDelegate::CreateLambda([=](TArray<FLCLeaderboardRanking> Rankings, int64 Count) {
// 排行榜前十的排名信息
});
FLCError::FDelegate OnError = FLCError::FDelegate::CreateLambda([=](const FLCError& Error) {
// handle error
});
Leaderboard.GetResults(OnSuccess, OnError);
Leaderboard 实例上可以设置如下属性限制查询范围:
/// 指定从某个位置开始获取,与 limit 一起可以实现翻页,默认 0
int64 Skip = 0;
/// 限制返回的结果数量,默认 20
int64 Limit = 20;
/// 是否返回排行榜的数量
bool WithCount = false;
/// 指定排行榜版本号,默认 0
int64 Version = 0;
/// 指定返回的 LCLeaderboardRanking 中需要包含的其他成绩
TArray<FString> IncludeStatistics;
/// 指定返回的 LCRanking 中排行榜成员需要包含的属性
TArray<FString> SelectMemberKeys;
FLCLeaderboardRanking 提供了以下方法供获取排名信息:
// 排名,从 0 开始
int64 Rank;
// 该成绩的用户(user 排行榜)
TSharedPtr<FLCUser> User;
// 该成绩的 object(object 排行榜)
TSharedPtr<FLCObject> Object;
// 该成绩的 entity(entity 排行榜)
FString EntityId;
// 成绩
double Value;
// 该成员在其他排行榜的成绩
TArray<FLCLeaderboardStatistic> Statistics;
默认情况下返回的排行榜结果中的 user
是一个指向 LCUser
的 Pointer。
如果想要像下面这个例子一样,在排行榜结果中显示用户名或者其他的用户属性(对应 _User
表中的属性),那么需要使用 selectKeys
指定需要包含的属性。
排名 | Username | Score↓ |
---|---|---|
0 | Genji | 3458 |
1 | Lúcio | 3252 |
2 | D.Va | 3140 |
- Unity
- Android
- iOS
- UE4
var rankings = await leaderboard.GetResults(limit: 10,
selectKeys: new List<string> { "nickname" });
List<String> selectKeys = new ArrayList<>();
selectKeys.add("nickname");
leaderboard.getResults(0, 10, selectKeys, null).subscribe(/* 略 */);
leaderboard.limit = 10;
LCLeaderboardQueryOption *option = [[LCLeaderboardQueryOption alloc] init];
option.selectKeys = @[@"nickname"];
[leaderboard getUserResultsWithOption:option,
callback:^(NSArray<LCLeaderboardRanking *> * _Nullable rankings, NSInteger count, NSError * _Nullable error) {
// 略
}];
Leaderboard.Limit = 10;
Leaderboard.SelectMemberKeys = {"nickname"};
Leaderboard.GetResults(OnSuccess, OnError);
如果想要在排行榜结果中包含用户的其他成绩,可以使用 includeStatistics
。
例如,查询得分排行榜时同时返回相应用户的击杀数:
排名 | Username | Score↓ | Kills |
---|---|---|---|
0 | Genji | 3458 | 28 |
1 | Lúcio | 3252 | 2 |
2 | D.Va | 3140 | 31 |
- Unity
- Android
- iOS
- UE4
var rankings = await leaderboard.GetResults(limit: 10, selectKeys: new List<string> { "username" }
includeStatistics: new List<string> { "kills" });
List<String> selectKeys = new ArrayList<>();
selectKeys.add("username");
List<String> includeStatistics = new ArrayList<>();
includeStatistics.add("kills");
leaderboard.getResults(0, 10, selectKeys, includeStatistics).subscribe(/* 略 */);
leaderboard.limit = 10;
leaderboard.includeStatistics = @[@"kills"];
LCLeaderboardQueryOption *option = [[LCLeaderboardQueryOption alloc] init];
option.selectKeys = @[@"username"];
[leaderboard getUserResultsWithOption:option,
callback:^(NSArray<LCLeaderboardRanking *> * _Nullable rankings, NSInteger count, NSError * _Nullable error) {
// 略
}];
Leaderboard.Limit = 10;
Leaderboard.SelectMemberKeys = {"username"};
Leaderboard.IncludeStatistics = {"kills"};
Leaderboard.GetResults(OnSuccess, OnError);
如果想要包含的属性是 Pointer 类型或文件类型,那么 selectKeys
只会返回 Pointer 本身。
想要一并获取 Pointer 指向的另一个 class 的数据的话,需要额外使用 includeKeys
指定。
例如,假设 club
是一个指向 Club
类的 Pointer:
- Unity
- Android
- iOS
- UE4
var leaderboard = LCLeaderboard.CreateWithoutData("weapons", "Weapon");
var rankings = await leaderboard.GetResults(limit: 10,
selectKeys: new List<string> { "name", "club" },
includeKeys: new List<string> { "club" });
// 暂未支持,如有此类需求可在 onNext 方法中额外发起一次查询
LCLeaderboard leaderboard = [[LCLeaderboard alloc] initWithStatisticName:@"weapons"];
leaderboard.limit = 10;
LCLeaderboardQueryOption *option = [[LCLeaderboardQueryOption alloc] init];
option.selectKeys = @[@"name", @"club"];
option.includeKeys = @[@"club"];
[leaderboard getUserResultsWithOption:option,
callback:^(NSArray<LCLeaderboardRanking *> * _Nullable rankings, NSInteger count, NSError * _Nullable error) {
// 略
}];
// 暂未支持
注意,排行榜 不保证成绩并列的成员之间的先后顺序。 例如,假设 A、B、C 的成绩为 42、32、32,且排行榜的排序策略为降序,那么查询排行榜的返回结果可能是 A、B、C,也可能是 A、C、B。
当成绩相同时,如果需要其他因素去设置排名可以参考如下示例(示例是通过成绩上传的时间戳去判断排名,当成绩相同时,越早/晚提交,排名越靠前/后):
Android 请求示例:
public class RankingActivity extends AppCompatActivity{
// ...
/**
* 上传/更新成绩
*
* */
private void submitScore() {
double score = 324.45; // 实际的分数
long ts = System.currentTimeMillis() / 1000; // 时间戳
double last_score = toEncode( score, ts); // 将实际分数与时间戳组合生成一个新的数据上传到服务器
Map<String, Double> statistic = new HashMap<>();
statistic.put("word", last_score);
LCLeaderboard.updateStatistic(LCUser.currentUser(), statistic).subscribe(new Observer<LCStatisticResult>() {
@Override
public void onSubscribe(@NotNull Disposable disposable) {}
@Override
public void onNext(@NotNull LCStatisticResult jsonObject) {
Log.e(TAG, "onNext: "+jsonObject.getResults().get(0).toString());
}
@Override
public void onError(@NotNull Throwable throwable) {
ToastUtil.showCus(throwable.getMessage(), ToastUtil.Type.ERROR);
}
@Override
public void onComplete() {}
});
}
/**
* 查询排行榜列表
* */
private void searchRankList() {
// 获取排行榜的实例
LCLeaderboard leaderboard = LCLeaderboard.createWithoutData("word");
leaderboard.getResults(0, 10, null, null).subscribe(new Observer<LCLeaderboardResult>() {
@Override
public void onSubscribe(@NotNull Disposable disposable) {}
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void onNext(@NotNull LCLeaderboardResult leaderboardResult) {
List<LCRanking> rankings = leaderboardResult.getResults();
for(int i=0; i<rankings.size(); i++){
/**
* 将查询的方法进行解密
*/
double score = rankings.get(i).getStatisticValue(); // 获取查询到的分数
String formatScore = String.format("%.3f", score); // 将 double 类型的分数系列化为保留小数点后3位的字符串
BigDecimal decimalValue = new BigDecimal(formatScore);
long longPart = decimalValue.longValue(); // 获取查询到的 StatisticValue 的整数部分
float floatPart = decimalValue.subtract(BigDecimal.valueOf(longPart)).floatValue(); // 获取查询到的 StatisticValue 的小数部分
int[] decryptedScoreTs = toDecode(longPart); // 将整数部分进行解密操作
int decryptedScore = decryptedScoreTs[0]; // 获取实际分数的整数部分
long decryptedTs = decryptedScoreTs[1]; // 获取提交成绩时的时间戳
BigDecimal bigDecimalValue = new BigDecimal(Integer.toString(decryptedScore))
.add(new BigDecimal(Float.toString(floatPart))); // 将实际分数的整数部分拼接上小数部分得到实际的分数
double doubleValue = bigDecimalValue.doubleValue(); // 将实际分数转化为 double 类型
Log.e(TAG, "解析后的score:"+doubleValue + "解析后的时间戳:"+ decryptedTs );
}
}
@Override
public void onError(@NotNull Throwable throwable) {
}
@Override
public void onComplete() {}
});
}
/**
* score 实际的分数
* ts 时间戳
* 将实际分数与时间戳组合生成一个新的数据上传到服务器
*/
public static double toEncode(double score, long ts) {
int int_score = (int) score; // 实际分数的整数部分
float float_score = (float) (score - int_score); // 实际分数的小数部分
/**
* 当排行榜使用降序排列,分数相同时,时间越近,排名靠前,使用这行代码
*/
// long encryptedScoreTs = ((long) int_score << 32) | (ts & 0xFFFFFFFFL); // 将整数部分分数与时间戳进行加密处理合并成一个新的 score
/**
* 当排行榜使用降序排列,分数相同时,时间越近,排名靠后,使用这行代码
*/
long encryptedScoreTs = ((long) int_score << 32) - (ts & 0xFFFFFFFFL); // 将整数部分分数与时间戳进行加密处理合并成一个新的 score
double newScore = (double)encryptedScoreTs + float_score; // 然后将加密生成的数据拼接上小数部分的数据,上传到服务器
return newScore;
}
/**
* 将加密的数据进行解析出实际分数和当时提交成绩时的时间戳
*
*/
public static int[] toDecode(long encryptedNewScore) {
/**
* 当排行榜使用降序排列,分数相同时,时间越近,排名靠前,使用这行代码
*/
// int score = (int) (encryptedNewScore >> 32);
/**
* 当排行榜使用降序排列,分数相同时,时间越近,排名靠后,使用这行代码
*/
int score = (int) (encryptedNewScore >> 32) + 1;
long ts = encryptedNewScore & 0xFFFFFFFFL;
return new int[]{score, (int) ts};
}
}
获取当前用户附近的排名
排名 | Username | Score↓ |
---|---|---|
… | ||
24 | Bastion | 716 |
25 (You) | Widowmaker | 698 |
26 | Hanzo | 23 |
… |
- Unity
- Android
- iOS
- UE4
如果想达到类似上表的效果,在调用 GetResults
方法时额外传入当前用户即可:
var rankings = await leaderboard.GetResults(aroundUser: currentUser, limit: 3, selectKeys: new List<string> { "username" });
如果想达到类似上表的效果,可以调用 getAroundResults
方法:
List<String> selectKeys = new ArrayList<>();
selectKeys.add("username");
leaderboard.getAroundResults(currentUser.getObjectId(), 0, 3, selectKeys, null).subscribe(/* 略 */);
getAroundResults
方法的第一个参数为排行榜成员的 ID(user 或 object 为 objectId
、entity 为字符串),其他参数和 getResults
相同。
如果想达到类似上表的效果,可以调用 getUserResultsAroundUser
方法:
leaderboard.limit = 3;
[leaderboard getUserResultsAroundUser:currentUser.objectId,
option:nil,
callback:^(NSArray<LCLeaderboardRanking *> * _Nullable rankings, NSInteger count, NSError * _Nullable error) {
// 略
}];
如果想达到类似上表的效果,可以调用 GetAroundResults
方法:
Leaderboard.Limit = 3;
Leaderboard.GetAroundResults(CurrentUser->GetObjectId();, OnSuccess, OnError);
GetAroundResults
方法的第一个参数为排行榜成员的 ID(user 或 object 为 objectId
、entity 为字符串),其他参数和 GetResults
相同。
上面的代码示例中,limit
的值为 3,表示获取与当前玩家相邻的前后两个玩家的排名,当前玩家会在结果的中间位置。
如仅需获取当前玩家排名,可指定 limit
为 1。
类似地,可以获取某个 object 或 entity 附近的排名。 例如,获取武器榜上和某件武器排名相近的武器,同时获取这些武器的名称、攻击力、等级:
- Unity
- Android
- iOS
- UE4
var leaderboard = LCLeaderboard.CreateWithoutData("weapons", "Weapon");
var excalibur = LCObject.createWithoutData("Weapon", "582570f38ac247004f39c24b");
var rankings = await leaderboard.GetResults(aroundObject: excalibur, limit: 3, selectKeys: new List<string> { "name", "attack", "level" });
LCLeaderboard leaderboard = LCLeaderboard.createWithoutData("world", "Weapon");
String excaliburObjectId = "582570f38ac247004f39c24b";
List<String> selectKeys = new ArrayList<>();
selectKeys.add("name");
selectKeys.add("attack");
selectKeys.add("level");
leaderboard.getAroundResults(excaliburObjectId, 0, 3, selectKeys, null).subscribe(/* 略 */);
LCLeaderboard leaderboard = [[LCLeaderboard alloc] initWithStatisticName:@"weapons"];
leaderboard.limit = 3;
LCLeaderboardQueryOption *option = [[LCLeaderboardQueryOption alloc] init];
option.selectKeys = @[@"name", @"attack", @"level"];
NSString *excaliburObjectId = @"582570f38ac247004f39c24b";
[leaderboard getObjectResultsAroundObject:excaliburObjectId,
option:option,
callback:^(NSArray<LCLeaderboardRanking *> * _Nullable rankings, NSInteger count, NSError * _Nullable error) {
// 略
}];
FLCLeaderboard Leaderboard("weapons", FLCLeaderboard::Object);
Leaderboard.Limit = 3;
Leaderboard.SelectMemberKeys = {"name", "attack", "level"};
FString ExcaliburObjectId = "582570f38ac247004f39c24b";
Leaderboard.GetAroundResults(ExcaliburObjectId, OnSuccess, OnError);
上面的例子中假定武器排行榜是通过 object 排行榜实现的,存储武器信息的 Class 为 Weapon
。
如果武器排行榜是通过 entity 排行榜实现的,且只需获取武器的名称,那么相应的代码为:
- Unity
- Android
- iOS
- UE4
var leaderboard = LCLeaderboard.CreateWithoutData("weapons", LCLeaderboard.ENTITY_MEMBER_TYPE);
var rankings = await leaderboard.GetResults(aroundEntity: "excalibur", limit: 3);
LCLeaderboard leaderboard = LCLeaderboard.createWithoutData("world", LCLeaderboard.ENTITY_MEMBER_TYPE);
leaderboard.getAroundResults("excalibur", 0, 3, null, null).subscribe(/* 略 */);
LCLeaderboard leaderboard = [[LCLeaderboard alloc] initWithStatisticName:@"weapons"];
leaderboard.limit = 3;
[leaderboard getEntityResultsAroundEntity:@"excalibur",
callback:^(NSArray^(NSArray<LCLeaderboardRanking *> * _Nullable rankings, NSInteger count, NSError * _Nullable error) {
// 略
}];
FLCLeaderboard Leaderboard("weapons", FLCLeaderboard::Entity);
Leaderboard.Limit = 3;
Leaderboard.GetAroundResults("excalibur", OnSuccess, OnError);
控制台
在排行榜控制台(游戏服务 > 云服务 > 排行榜),你可以:
- 新建、重置、编辑、删除排行榜。
- 查看排行榜当前版本的数据、删除成绩、下载排行榜的历史数据归档文件。
- 设置是否允许客户端查询前一版本,是否只允许使用 Master Key 更新分数。
SDK 管理接口
除了通过控制台管理排行榜外,C# SDK 和 Java SDK 也提供了管理接口,可以在服务端等受信任的环境下使用。 另外,也可以直接调用 REST API 提供的管理接口。
下面我们先介绍 SDK 提供的管理接口。
请注意,调用管理接口 API 需要在 SDK 初始化时使用 masterKey,因此只能在服务端等可信任的环境中调用,不得在客户端使用。
- Unity
- Android
- iOS
- UE4
LCApplication.Initialize({{appid}}, {{appkey}}, "https://xxx.example.com", {{masterkey}});
LCApplication.UseMasterKey = true;
LeanCloud.setMasterKey({{masterkey}});
// 不支持
// 不支持
创建排行榜
- Unity
- Android
- iOS
- UE4
var leaderboard = await LCLeaderboard.CreateLeaderboard("time", order: LCLeaderboardOrder.ASCENDING);
可以指定的参数类型和默认值如下:
public static async Task<LCLeaderboard> CreateLeaderboard(string statisticName,
LCLeaderboardOrder order = LCLeaderboardOrder.Descending,
LCLeaderboardUpdateStrategy updateStrategy = LCLeaderboardUpdateStrategy.Better,
LCLeaderboardVersionChangeInterval versionChangeInterval = LCLeaderboardVersionChangeInterval.Week,
string memberType = LCLeaderboard.USER_MEMBER_TYPE)
statisticName
所排名的成绩名字。order
排序,可以传入LCLeaderboardOrder.Descending
或LCLeaderboardOrder.Ascending
。updateStrategy
成绩更新策略,可以传入LCLeaderboardUpdateStrategy.Better
、LCLeaderboardUpdateStrategy.Last
、LCLeaderboardUpdateStrategy.Sum
。versionChangeInterval
自动重置周期,可以传入LCLeaderboardVersionChangeInterval.Never
、LCLeaderboardVersionChangeInterval.Day
、LCLeaderboardVersionChangeInterval.Week
、LCLeaderboardVersionChangeInterval.Month
。memberType
成员类型,传入LCLeaderboard.USER_MEMBER_TYPE
表示成员类型为用户,传入LCLeaderboard.ENTITY_MEMBER_TYPE
表示成员类型为 entity,成员类型为 object 时请传入相应 Class 名称。
LCLeaderboard.createWithMemberType(LCLeaderboard.MEMBER_TYPE_USER, "time",
LCLeaderboard.LCLeaderboardOrder.Ascending,
LCLeaderboard.LCLeaderboardUpdateStrategy.Last,
LCLeaderboard.LCLeaderboardVersionChangeInterval.Day).subscribe(new Observer<LCLeaderboard>() {
@Override
public void onSubscribe(@NotNull Disposable disposable) {}
@Override
public void onNext(@NotNull final LCLeaderboard lcLeaderboard) {
System.out.println("leaderboard created");
}
@Override
public void onError(@NotNull Throwable throwable) {
System.out.println("failed to create leaderboard. Cause " + throwable);
}
@Override
public void onComplete() {}
});
你可以指定以下参数:
public static Observable<LCLeaderboard> createWithMemberType(String memberType, String name,
LCLeaderboardOrder order,
LCLeaderboardUpdateStrategy updateStrategy,
LCLeaderboardVersionChangeInterval versionChangeInterval)
memberType
成员类型,传入LCLeaderboard.MEMBER_TYPE_USER
表示成员类型为用户,传入LCLeaderboard.MEMBER_TYPE_ENTITY
表示成员类型为 entity,成员类型为 object 时请传入相应 Class 名称。name
所排名的成绩名字。order
排序,可以传入LCLeaderboard.LCLeaderboardOrder.Descending
(默认值)或LCLeaderboard.LCLeaderboardOrder.Ascending
。updateStrategy
成绩更新策略,可以传入LCLeaderboard.LCLeaderboardUpdateStrategy.Better
(默认值)、LCLeaderboard.LCLeaderboardUpdateStrategy.Last
、LCLeaderboard.LCLeaderboardUpdateStrategy.Sum
。versionChangeInterval
自动重置周期,可以传入LCLeaderboard.LCLeaderboardVersionChangeInterval.Never
、LCLeaderboard.LCLeaderboardVersionChangeInterval.Day
、LCLeaderboard.LCLeaderboardVersionChangeInterval.Week
(默认值)、LCLeaderboard.LCLeaderboardVersionChangeInterval.Month
。
默认值指相应参数传入 null
时对应的取值。
不支持
不支持
手动重置排行榜
- Unity
- Android
- iOS
- UE4
var leaderboard = LCLeaderboard.CreateWithoutData("score");
await leaderboard.Reset();
LCLeaderboard leaderboard = LCLeaderboard.createWithoutData("score");
leaderboard.reset().subscribe(new Observer<Boolean>() {
@Override
public void onSubscribe(@NotNull Disposable disposable) {
}
@Override
public void onNext(@NotNull Boolean aBoolean) {
if (aBoolean) { // aBoolean should always be true
System.out.println("leaderboard reset");
}
}
@override
public void onerror(@notnull throwable throwable) {
system.out.println("Failed to reset leaderboard. Cause " + throwable);
}
@override
public void oncomplete() {}
});
// 不支持
// 不支持
获取排行榜属性
通过以下接口可以获取当前排行榜的属性,例如:重置周期、版本号、更新策略等。
- Unity
- Android
- iOS
- UE4
var leaderboardData = await LCLeaderboard.GetLeaderboard("world");
LCLeaderboard.fetchByName("world").subscribe(new Observer<LCLeaderboard>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(LCLeaderboard lcLeaderboard) {
int v =lcLeaderboard.getVersion();
String statisticName = lcLeaderboard.getStatisticName();
Log.d(TAG, String.valueOf(v));
Log.d(TAG, statisticName);
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
// 不支持
// 不支持
修改排行榜属性
排行榜创建后,仅可修改自动重置周期和成绩更新策略,其他属性无法修改。
- Unity
- Android
- iOS
- UE4
var leaderboard = LCLeaderboard.CreateWithoutData("equip");
await leaderboard.UpdateVersionChangeInterval(LCLeaderboardVersionChangeInterval.Week);
await leaderboard.UpdateUpdateStrategy(LCLeaderboardUpdateStrategy.Last);
LCLeaderboard leaderboard = LCLeaderboard.createWithoutData("equip");
leaderboard.updateVersionChangeInterval(LCLeaderboard.LCLeaderboardVersionChangeInterval.Week)
.subscribe(new Observer<Boolean>() {
@Override
public void onSubscribe(@NotNull Disposable disposable) {
}
@Override
public void onNext(@NotNull Boolean aBoolean) {
if (aBoolean) { // aBoolean should always be true
System.out.println("version update interval updated");
}
}
@override
public void onerror(@notnull throwable throwable) {
system.out.println("Failed to change version update interval. Cause: " + throwable);
}
@override
public void oncomplete() {}
});
leaderboard.updateUpdateStrategy(LCLeaderboard.LCLeaderboardUpdateStrategy.Last)
.subscribe(new Observer<Boolean>() {
@Override
public void onSubscribe(@NotNull Disposable disposable) {
}
@Override
public void onNext(@NotNull Boolean aBoolean) {
if (aBoolean) { // aBoolean should always be true
System.out.println("update strategy updated");
}
}
@override
public void onerror(@notnull throwable throwable) {
system.out.println("Failed to change update strategy. Cause: " + throwable);
}
@override
public void oncomplete() {}
});
// 不支持
// 不支持
删除排行榜
- Unity
- Android
- iOS
- UE4
var leaderboard = lcleaderboard.createwithoutdata("equip");
await leaderboard.destroy();
LCLeaderboard leaderboard = LCLeaderboard.createWithoutData("equip");
leaderboard.destroy().subscribe(new Observer<Boolean>() {
@Override
public void onSubscribe(@NotNull Disposable disposable) {
}
@Override
public void onNext(@NotNull Boolean aBoolean) {
if (aBoolean) { // aBoolean should always be true
System.out.println("leaderboard deleted");
}
}
@override
public void onerror(@notnull throwable throwable) {
system.out.println("Failed to delete leaderboard. Cause " + throwable);
}
@override
public void oncomplete() {}
});
// 不支持
// 不支持
删除排行榜会删除该排行榜的所有数据,包括当前数据及所有历史版本归档。
更新排行榜成员成绩
有些情况下,你会想要绕过排行榜的更新策略强制更新用户的成绩,你可以使用 overwrite
参数实现这一需求:
- Unity
- Android
- iOS
- UE4
var statistic = new Dictionary<string, double> {
{ "score", 0.0 }
};
await LCLeaderboard.UpdateStatistics(user, statistic, overwrite: true);
Map<String, Double> statistic = new HashMap<>();
statistic.put("world", 0.0);
LCLeaderboard.updateStatistic(currentUser, statistic, true).subscribe(/** 略 **/);
// 不支持
// 不支持
object 和 entity 排行榜只支持在服务端使用 Master Key 更新,但仍会遵循排行榜的更新策略:
- Unity
- Android
- iOS
- UE4
var excalibur = LCObject.createWithoutData("Weapon", "582570f38ac247004f39c24b");
await LCLeaderboard.UpdateStatistics(excalibur, statistic);
// 暂不支持,如有需求可以调用 REST API 接口更新
不支持
不支持
如果需要强制更新,同样需要指定 overwrite
为真:
- Unity
- Android
- iOS
- UE4
await LCLeaderboard.UpdateStatistics("Vimur", statistic, overwrite: true);
// 暂不支持,如有需求可以调用 REST API 接口更新
// 不支持
// 不支持
删除排行榜成员成绩
在服务端使用 Master Key 可以删除任意用户、object、entity 的成绩:
- Unity
- Android
- iOS
- UE4
var otherUser = LCObject.CreateWithoutData(TDSUser.CLASS_NAME, "5c76107144d90400536fc88b");
await LCLeaderboard.DeleteStatistics(otherUser, new List<string> { "world" });
var excalibur = LCObject.createWithoutData("Weapon", "582570f38ac247004f39c24b");
await LCLeaderboard.DeleteStatistics(excalibur, new List<string> { "weapons" });
await LCLeaderboard.DeleteStatistics("Vimur", new List<string> { "rivers" });
// 暂不支持,如有需求可以调用 REST API 接口更新
// 不支持
// 不支持
REST API
下面我们介绍排行榜相关的 REST API 接口。 开发者可以自行编写程序或脚本调用这些接口在服务端进行管理性质的操作。
请求格式
对于 POST 和 PUT 请求,请求的主体必须是 JSON 格式,而且 HTTP Header 的 Content-Type 需要设置为 application/json
。
请求的鉴权是通过 HTTP Header 里面包含的键值对来进行的,参数如下表:
Key | Value | 含义 | 来源 |
---|---|---|---|
X-LC-Id | {{appid}} | 当前应用的 App Id (即 Client Id ) | 可在控制台查看 |
X-LC-Key | {{appkey}} | 当前应用的 App Key (即 Client Token ) | 可在控制台查看 |
管理接口需要使用 Master Key
:X-LC-Key: {{masterkey}},master
。
Master Key
即 Server Secret
,同样可在控制台查看。
详见文档关于应用凭证的说明。
Base URL
REST API 请求的 Base URL(下文 curl 示例中用 {{host}}
表示)即应用的 API 域名,可以在控制台查看。详见文档关于域名的说明。
管理排行榜
创建排行榜
curl -X POST \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{masterkey}},master" \
-H "Content-Type: application/json" \
-d '{"statisticName": "world", "memberType": "_User", "order": "descending", "updateStrategy": "better", "versionChangeInterval": "month"}' \
https://{{host}}/1.1/leaderboard/leaderboards
参数 | 约束 | 说明 |
---|---|---|
statisticName | 必须 | 排行榜的名称,创建后不可修改。 |
memberType | 必须 | 排行榜的成员类型,创建后不可修改。可填写 _Entity 、_User 及其他已有的 class 名称。 |
order | 可选 | 排行榜的排序策略,创建后不可修改。可选项有 ascending 或 descending ,默认为 descending 。 |
updateStrategy | 可选 | 可选项有 better 、last 、sum ,默认为 better 。 |
versionChangeInterval | 可选 | 可选项有 day 、week 、month 、never ,默认为 week 。 |
返回的主体是一个 JSON 对象,包含创建排行榜时传入的所有参数,同时包括以下信息:
version
为排行榜当前版本号。expiredAt
为下次过期(重置)时间。activatedAt
当前版本的开始时间。
{
"objectId": "5b62c15a9f54540062427acc",
"statisticName": "world",
"memberType": "_User",
"versionChangeInterval": "month",
"order": "descending",
"updateStrategy": "better",
"version": 0,
"createdAt": "2018-08-02T08:31:22.294Z",
"updatedAt": "2018-08-02T08:31:22.294Z",
"expiredAt": {
"__type": "Date",
"iso": "2018-08-31T16:00:00.000Z"
},
"activatedAt": {
"__type": "Date",
"iso": "2018-08-02T08:31:22.290Z"
}
}
获取排行榜属性
通过这个接口来查看当前排行榜的属性,例如更新策略、当前版本号等。
curl -X GET \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{masterkey}},master" \
https://{{host}}/1.1/leaderboard/leaderboards/<statisticName>
返回的 JSON 对象包含该排行榜的所有信息:
{
"objectId": "5b0b97cf06f4fd0abc0abe35",
"statisticName": "world",
"memberType": "_User",
"order": "descending",
"updateStrategy": "better",
"version": 5,
"versionChangeInterval": "day",
"expiredAt": { "__type": "Date", "iso": "2018-05-02T16:00:00.000Z" },
"activatedAt": { "__type": "Date", "iso": "2018-05-01T16:00:00.000Z" },
"createdAt": "2018-04-28T05:46:58.579Z",
"updatedAt": "2018-05-01T01:00:00.000Z"
}
修改排行榜属性
这个接口可以用来修改排行榜的 updateStrategy
和 versionChangeInterval
属性,其他属性不可修改。可以只更新某一个属性,例如只修改 versionChangeInterval:
curl -X PUT \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{masterkey}},master" \
-H "Content-Type: application/json" \
-d '{"versionChangeInterval": "day"}' \
https://{{host}}/1.1/leaderboard/leaderboards/<statisticName>
返回的 JSON 对象包含更新的属性信息及 updatedAt
字段。
{
"objectId": "5b0b97cf06f4fd0abc0abe35",
"versionChangeInterval": "day",
"updatedAt": "2018-05-01T08:01:00.000Z"
}
重置排行榜
无论排行榜的重置策略是什么,你都可以通过这个方法重置排行榜。重置时当前版本的数据清空,同时会归档到 csv 文件以供下载,排行榜的 version
会自动加一。
curl -X PUT \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{masterkey}},master" \
https://{{host}}/1.1/leaderboard/leaderboards/<statisticName>/incrementVersion
返回的 JSON 会显示重置后的当前版本号,下一次过期时间 expiredAt
,当前版本的开始时间 activatedAt
:
{
"objectId": "5b0b97cf06f4fd0abc0abe35",
"version": 7,
"expiredAt": { "__type": "Date", "iso": "2018-06-03T16:00:00.000Z" },
"activatedAt": { "__type": "Date", "iso": "2018-05-28T06:02:56.169Z" },
"updatedAt": "2018-05-28T06:02:56.185Z"
}
获取历史数据归档文件
因为每个排行榜最多保存 60 个归档文件,我们建议定期使用这个接口获取归档文件后另行备份保存。
curl -X GET \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{masterkey}},master" \
-G \
--data-urlencode 'limit=10' \
https://{{host}}/1.1/leaderboard/leaderboards/<statisticName>/archives
返回的对象会按照 createdAt
降序排列。其中 file_key
是文件的名称,url
是文件的下载地址,status
包含以下状态:
scheduled
:进入归档任务队列,还未归档,这个状态通常极短。inProgress
:正在归档中。failed
:归档失败,出现这种情况请提交工单联系技术支持。completed
:归档已完成。
{
"results": [
{
"objectId": "5b0b9da506f4fd0abc0abe6e",
"statisticName": "wins",
"version": 9,
"status": "completed",
"url": "https://lc-paas-files.cn-n1.lcfile.com/yK5s6YJztAwEYiWs.csv",
"file_key": "yK5s6YJztAwEYiWs.csv",
"activatedAt": { "__type": "Date", "iso": "2018-05-28T06:11:49.572Z" },
"deactivatedAt": { "__type": "Date", "iso": "2018-05-30T06:11:49.951Z" },
"createdAt": "2018-05-01T16:00.00.000Z",
"updatedAt": "2018-05-28T06:11:50.129Z"
}
]
}
删除排行榜
这个接口将删除排行榜的所有数据,包括当前版本的数据及所有归档文件,删除后无法恢复,请谨慎操作。
删除时只需要指定排行榜的名称 statisticName。
curl -X DELETE \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{masterkey}},master" \
https://{{host}}/1.1/leaderboard/leaderboards/<statisticName>
删除成功时返回空 JSON 对象:
{}
管理成绩
更新成绩
使用 Master Key 可以更新任意成绩,但更新成绩时仍然遵循排行榜的 updateStrategy
属性。
更新用户成绩时需指定相应用户的 objectId
:
curl -X POST \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{masterkey}},master" \
-H "Content-Type: application/json" \
-d '[{"statisticName": "wins", "statisticValue": 5}, {"statisticName": "world", "statisticValue": 91}]' \
https://{{host}}/1.1/leaderboard/users/<objectId>/statistics
返回的数据是服务端当前使用的分数:
{
"results": [
{
"statisticName": "wins",
"version": 0,
"statisticValue": 5
},
{
"statisticName": "world",
"version": 2,
"statisticValue": 91
}
]
}
类似地,更新 object 成绩时需指定相应 object 的 objectId
:
curl -X POST \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{masterkey}},master" \
-H "Content-Type: application/json" \
-d '[{"statisticName": "wins", "statisticValue": 5}, {"statisticName": "weapons","statisticValue": 91}]' \
https://{{host}}/1.1/leaderboard/objects/<objectId>/statistics
更新 entity 成绩时则需指定相应的字符串:
curl -X POST \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{masterkey}},master" \
-H "Content-Type: application/json" \
-d '[{"statisticName": "wins", "statisticValue": 5}, {"statisticName": "cities","statisticValue": 91}]' \
https://{{host}}/1.1/leaderboard/entities/<entityString>/statistics
当前用户可以更新自己的成绩,这个不属于管理接口,不需要 Master Key
,但需要传入当前用户的 sessionToken
(客户端 SDK 更新当前用户的成绩封装了这一接口):
curl -X POST \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{appkey}}" \
-H "X-LC-Session: <sessionToken>" \
-H "Content-Type: application/json" \
-d '[{"statisticName": "wins", "statisticValue": 5}, {"statisticName": "world", "statisticValue": 91}]' \
https://{{host}}/1.1/leaderboard/users/self/statistics
强制更新成绩
附加 overwrite=1
会无视更新策略 better 及 sum,强制使用 last 策略更新用户的成绩。
比如,发现某个用户存在作弊行为时,可以使用这个接口强制更新用户的成绩。
curl -X POST \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{masterkey}},master" \
-H "Content-Type: application/json" \
-d '[{"statisticName": "wins", "statisticValue": 10}]' \
https://{{host}}/1.1/leaderboard/users/<uid>/statistics?overwrite=1
返回的数据是当前服务端使用的分数:
{ "results": [{ "statisticName": "wins", "version": 0, "statisticValue": 10 }] }
类似地,附加 overwrite=1
可以强制更新 object 成绩和 entity 成绩。
删除成绩
如果不希望某个用户出现在榜单中,可以使用该接口删除用户的成绩以及在榜单中的排名(仅删除当前排行榜的成绩,不能删除历史版本的成绩)。
curl -X DELETE \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{masterkey}},master" \
-H "Content-Type: application/json" \
https://{{host}}/1.1/leaderboard/users/<uid>/statistics?statistics=wins,world
成功删除时返回空对象:
{}
类似地,可以删除 object 的成绩:
curl -X DELETE \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{masterkey}},master" \
--data-urlencode 'statistics=weapons,equipments' \
https://{{host}}/1.1/leaderboard/objects/<objectId>/statistics
以及 entity 的成绩:
curl -X DELETE \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{masterkey}},master" \
--data-urlencode 'statistics=cities' \
https://{{host}}/1.1/leaderboard/entities/<entityString>/statistics
同样,当前用户可以删除自己的成绩,这个不属于管理接口,不需要 Master Key
,但需要传入当前用户的 sessionToken
(客户端 SDK 删除当前用户的成绩封装了这一接口):
curl -X DELETE \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{appkey}}" \
-H "X-LC-Session: <sessionToken>" \
-H "Content-Type: application/json" \
https://{{host}}/1.1/leaderboard/users/self/statistics?statistics=wins,world
查询成绩
通过 REST API 可以查询成绩,这些接口不属于管理接口,不需要 Master Key
:
查询某个成绩
指定用户的 objectId 即可获取该用户的成绩。
你可以在请求 url 中指定多个 statistics
来获得多个排行榜中的成绩,排行榜名称用英文逗号 ,
隔开,如果不指定将会返回该用户参与的所有排行榜中的成绩。
curl -X GET \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{appkey}}" \
--data-urlencode 'statistics=wins,world' \
https://{{host}}/1.1/leaderboard/users/<objectId>/statistics
返回结果:
{
"results": [
{
"statisticName": "wins",
"statisticValue": 5,
"version": 0,
"user": {
"__type": "Pointer",
"className": "_User",
"objectId": "60d950629be318a249000001"
}
},
{
"statisticName": "world",
"statisticValue": 91,
"version": 0,
"user": {...}
}
]
}
类似地,指定 object 的 objectId 可以查询该 object 参与的排行榜的成绩:
curl -X GET \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{appkey}}" \
--data-urlencode 'statistics=wins,world' \
https://{{host}}/1.1/leaderboard/objects/<objectId>/statistics
返回示例:
{
"results": [
{
"statisticName": "wins",
"statisticValue": 5,
"version": 0,
"object": {
"__type": "Pointer",
"className": "Weapon",
"objectId": "60d1af149be3180684000002"
}
},
{
"statisticName": "world",
"statisticValue": 91,
"version": 0,
"object": {
"__type": "Pointer",
"className": "Weapon",
"objectId": "60d1af149be3180684000002"
}
}
]
}
获取某个 entity 成绩时则需指定该 entity 的字符串:
curl -X GET \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{appkey}}" \
--data-urlencode 'statistics=wins,world' \
https://{{host}}/1.1/leaderboard/entities/<entityString>/statistics
返回示例:
{
"results": [
{
"statisticName": "wins",
"statisticValue": 5,
"version": 0,
"entity": "1a2b3c4d"
},
{
"statisticName": "world",
"statisticValue": 91,
"version": 0,
"entity": "1a2b3c4d"
}
]
}
查询一组成绩
通过这个接口可以一次性拉取多个 user 的成绩,最多不超过 200 个。在请求中,需要在 body 中传入 user 的 objectId
数组。
curl -X POST \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{appkey}}" \
-H "Content-Type: application/json" \
-d '["60d950629be318a249000001", "60d950629be318a249000000"]'
https://{{host}}/1.1/leaderboard/users/statistics/<statisticName>
查询一组成绩的返回结果与查询单个成绩类似:
{
"results": [
{
"statisticName": "wins",
"statisticValue": 1,
"version": 0,
"user": {
"__type": "Pointer",
"className": "_User",
"objectId": "60d950629be318a249000001"
}
},
{
"statisticName": "wins",
"statisticValue": 2,
"version": 0,
"user": {
"__type": "Pointer",
"className": "_User",
"objectId": "60d950629be318a249000000"
}
}
]
}
类似地,传入 object 的 objectId
数组可以一次性获取多个 object 的成绩(最多不超过 200 个):
curl -X POST \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{appkey}}" \
-H "Content-Type: application/json" \
-d '["60d950629be318a249000001", "60d950629be318a249000000"]'
https://{{host}}/1.1/leaderboard/objects/statistics/<statisticName>
传入 entity 的字符串数组则可以一次性获取多个 entity 的成绩(最多不超过 200 个):
curl -X POST \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{appkey}}" \
-H "Content-Type: application/json" \
-d '["Vimur", "Fimbulthul"]'
https://{{host}}/1.1/leaderboard/entities/statistics/<statisticName>
查询排行榜
获取区间排名
你可以使用这个接口来获取 Top 排名。
curl -X GET \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{appkey}}" \
-G \
--data-urlencode 'startPosition=0' \
--data-urlencode 'maxResultsCount=20' \
--data-urlencode 'selectKeys=username,club' \
--data-urlencode 'includeKeys=club' \
--data-urlencode 'includeStatistics=wins' \
https://{{host}}/1.1/leaderboard/leaderboards/user/<statisticName>/ranks
参数 | 约束 | 说明 |
---|---|---|
startPosition | 可选 | 排行头部起始位置,默认为 0。 |
maxResultsCount | 可选 | 最大返回数量,默认为 20。 |
selectKeys | 可选 | 返回用户在 _User 表的其他字段,支持多个字段,用英文逗号 , 隔开。出于安全性考虑,在非 masterKey 请求下不返回敏感字段 email 及 mobilePhoneNumber 。 |
includeKeys | 可选 | 返回用户在 _User 表的 pointer 字段的详细信息,支持多个字段,用英文逗号 , 隔开。为确保安全,在非 masterKey 请求下不返回敏感字段 email 及 mobilePhoneNumber 。 |
includeStatistics | 可选 | 返回该用户在其他排行榜中的成绩,如果传入了不存在的排行榜名称,将会返回错误。 |
version | 可选 | 返回指定 version 的排行结果,默认返回当前版本的数据。 |
count | 可选 | 值为 1 时返回该排行榜中的成员数量,默认为 0。 |
返回 JSON 对象:
{
"results": [
{
"statisticName": "world",
"statisticValue": 91,
"rank": 0,
"user": {
"__type": "Pointer",
"className": "_User",
"updatedAt": "2021-07-21T03:08:10.487Z",
"username": "zw1stza3fy701rvgxqwiikex7",
"createdAt": "2020-09-04T04:23:04.795Z",
"club": {
"objectId": "60f78f98d9f1465d3b1da12d",
"name": "board games",
"updatedAt": "2021-07-21T03:08:08.692Z",
"createdAt": "2021-07-21T03:08:08.692Z",
},
"objectId": "5f51c1287628f2468aa696e6"
}
},
{...}
],
"count": 500
}
查询 object 排行榜的 Top 排名的接口与之类似,只是将 user
替换为 object
:
curl -X GET \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{appkey}}" \
-G \
--data-urlencode 'startPosition=0' \
--data-urlencode 'maxResultsCount=2' \
--data-urlencode 'selectKeys=name,image' \
--data-urlencode 'includeKeys=image' \
--data-urlencode 'count=1' \
https://{{host}}/1.1/leaderboard/leaderboards/object/<statisticName>/ranks
返回结果:
{
"results": [
{
"statisticName": "wins",
"statisticValue": 4,
"rank": 0,
"object": {
"__type": "Pointer",
"className": "Weapon",
"name": "sword",
"image": {
"bucket": "test_files",
"provider": "leancloud",
"name": "sword.jpg",
"url": "https://example.com/sword.jpg",
"objectId": "60d2f3a39be3183377000002",
"__type": "File"
},
"objectId": "60d2f22f9be318328b000007"
}
},
{
"statisticName": "wins",
"statisticValue": 3,
"rank": 1,
"object": {...}
}
],
"count": 500
}
同理,URL 中的 user
替换为 entity
可查询 entity 排行榜的 Top 排名:
curl -X GET \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{appkey}}" \
-G \
--data-urlencode 'startPosition=0' \
--data-urlencode 'maxResultsCount=2' \
--data-urlencode 'count=1' \
https://{{host}}/1.1/leaderboard/leaderboards/entity/<statisticName>/ranks
返回结果:
{
"results": [
{
"statisticName": "wins",
"statisticValue": 4,
"rank": 0,
"entity": "1234567890"
},
{
"statisticName": "wins",
"statisticValue": 3,
"rank": 1,
"entity": "2345678901"
}
],
"count": 500
}
获取附近排名
在 URL 末端附加相应的 objectId 可获取某用户或 object 附近的排名。
获取某用户附近的排名:
curl -X GET \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{appkey}}" \
-G \
--data-urlencode 'startPosition=0' \
--data-urlencode 'maxResultsCount=20' \
--data-urlencode 'selectKeys=username,club' \
--data-urlencode 'includeKeys=club' \
https://{{host}}/1.1/leaderboard/leaderboards/user/<statisticName>/ranks/<objectId>
参数含义参见上面获取区间排名一节。 获取附近排名的返回结果与获取区间排名类似。
{
"results": [
{
"statisticName": "wins",
"statisticValue": 3,
"rank": 2,
"user": {...}
},
{
"statisticName": "wins",
"statisticValue": 2.5,
"rank": 3,
"user": {
"__type": "Pointer",
"className": "_User",
"username": "kate",
"club": {
"objectId": "60f78f98d9f1465d3b1da12d",
"name": "board games",
"updatedAt": "2021-07-21T03:08:08.692Z",
"createdAt": "2021-07-21T03:08:08.692Z",
},
"objectId": "60d2faa99be3183623000001"
}
},
{
"statisticName": "wins",
"statisticValue": 2,
"rank": 4,
"user": {...}
}
],
"count": 500
}
获取某 object 附近的排名:
curl -X GET \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{appkey}}" \
-G \
--data-urlencode 'startPosition=0' \
--data-urlencode 'maxResultsCount=2' \
--data-urlencode 'selectKeys=name,image' \
--data-urlencode 'includeKeys=image' \
--data-urlencode 'count=1' \
https://{{host}}/1.1/leaderboard/leaderboards/object/<statisticName>/ranks/<objectId>
同理,在 URL 末端附加 entity 字符串即可获取该 entity 附近的排名:
curl -X GET \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{appkey}}" \
-G \
--data-urlencode 'startPosition=0' \
--data-urlencode 'maxResultsCount=2' \
--data-urlencode 'count=1' \
https://{{host}}/1.1/leaderboard/leaderboards/entity/<statisticName>/ranks/<id>
视频教程
可以参考视频教程:如何接入 TapTap 排行榜功能,了解如何在 Untiy 项目中接入排行榜功能。
更多视频教程见开发者学堂。因为 SDK 功能在不断完善,视频教程可能出现与新版 SDK 功能不一致的地方,以当前文档为准。