开发指南
登录
普通登录
游戏采用自己绘制登录按钮时,可通过接口登录方法传入对应的登录类型来告诉 SDK 当前用户登录的类型,初始化成功后才可以使用登录接口。
注意事项
- 测试 TapTap 登录前,请详细阅读文档并完成配置:国内开发者中心文档 / 海外开发者中心文档 。
- 登录流程:先
LoginByType(Default)
自动登录(自动登录: 以上次登录成功过的账户继续登录),如果自动登录失败,再显示 TapTap 登录或游客登录按钮给用户点击授权登录。 - 登录成功后需要调用
XDGCommon.TrackUser
,用于 TapDB 统计用户。 - 登录成功后如果要使用
TDSUser
信息,需要执行var tdsUser = await TDSUser.GetCurrent().Result.Fetch()
后才可以使用。 - 这里的登录成功指的是 XDSDK 登录成功,如果是国内需要接入防沉迷模块,需要在登录成功后游戏自行接入防沉迷接口,之后才是游戏最终登录成功,玩家才可以进入游戏。
- 用了 XDSDK 就不需要再单独初始化
TapDB.init
和LCApplication.init
,XDSDK 里面已经初始化了。
v6.5.0 版本开始,登录方法返回的 error 对象在一些特定 error code 下会包含详细的错误信息,这里的内容需要游戏解析后自行展示给用户。对应的 error code 和内容示例请参考 TapTap 绑定邮箱未验证 和 使用不同方式登录且为一个邮箱时,登录为同一个账号。
v6.8.1 版本开始,SDK 将上述错误情况对应的弹窗显示已经处理掉了,SDK 也不再返回对应的详细内容了,游戏可以不再显示对应的弹窗。
- Android
- iOS
- Unity
- UE
public enum LoginEntryType {
/**
* 默认登录入口,实际操作逻辑是根据缓存 token 恢复上一次登录
*/
DEFAULT(-1, "Default"),
/**
* 游客登录
*/
GUEST(0, "Guest"),
/**
* 苹果登录
*/
APPLE(2, "Apple"),
/**
* 谷歌登录
*/
GOOGLE(3, "Google"),
/**
* 脸书登录
*/
FACEBOOK(4, "Facebook"),
/**
* TapTap登录
*/
TAP_TAP(5, "TapTap"),
/**
* LINE 登录
*/
LINE(6, "LINE"),
/**
* 推特登录
*/
TWITTER(7, "Twitter"),
/**
* Steam 登录
*/
STEAM(10, "Steam"),
/**
* 手机号登录(包含一键登录和手机验证码登录)
*/
PHONE(11, "Phone");
}
XDGAccount.loginByType(LoginEntryType.DEFAULT, new Callback<XDGUser>() {
@Override
public void onCallback(XDGUser xdgUser, XDGError xdgError) {
if (xdgUser != null && xdgError == null) {
// 登录成功
String XDUserID = xdgUser.getId(); // 用户在 XD 账户系统的 XD User ID(用户唯一标识)
String avaterUrl = xdgUser.getAvatar();
String nickName = xdgUser.getNickName();
} else {
// 登录失败
if (xdgError != null) {
// 获取 error code、error 简述和 error 详细内容(详细内容一般是 json 格式,SDK 封装成了 map)
int code = xdgError.getCode();
String errorMsg = xdgError.getMessage();
Map<String,Object> errorDataMap = xdgError.getErrorDataMap();
try {
if (code == 40021) {
String loginType = errorDataMap.get("loginType").toString();
String email = errorDataMap.get("email").toString();
new AlertDialog.Builder(context)
.setMessage(String.format("当前 %s 账号所关联的邮箱( %s )未被验证,请前往 %s 验证邮箱后重新登录游戏。", loginType, email, loginType))
.show();
} else if (code == 40902 || code == 40901) {
String loginType = errorDataMap.get("loginType").toString();
String email = (String) errorDataMap.get("email").toString();
String conflictsJsonStr = errorDataMap.get("conflicts").toString();
JSONArray conflictsArray = new JSONArray(conflictsJsonStr);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < conflictsArray.length(); i++) {
JSONObject data = conflictsArray.getJSONObject(i);
sb.append(data.opt("loginType"));
sb.append("、");
}
new AlertDialog.Builder(context)
.setMessage(String.format("当前 %s 账号所关联的邮箱( %s )对应的游戏账号已绑定其他 %s 账号 。请使用该邮箱所关联的 %s 登录游戏账号后进入「账号安全中心」手动进行账号绑定、解绑操作。", loginType, email, sb, loginType))
.show();
}else {
// ...
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
});
LoginEntryTypeDefault (自动登录)
LoginEntryTypeTapTap
LoginEntryTypeApple (只支持 iOS 13 以上)
LoginEntryTypeGoogle
LoginEntryTypeFacebook
LoginEntryTypeLine
LoginEntryTypeTwitter
LoginEntryTypeSteam
LoginEntryTypeGuest
LoginEntryTypePhone
LoginEntryType type = LoginEntryTypeDefault;
[XDGAccount loginByType:type loginHandler:^(XDGUser * _Nullable result, NSError * _Nullable error) {
if (error) {
// 登录失败
// error.code (错误码,用来区分类型)
// error.localizedDescriptio (可以用来展示给用户的错误原因文案)
// error.userInfo[@"extraData"](详细内容,是个 NSDictionary,只在特定 code 下存在)
if (error.code == 40021 && extraData) {
if (extraData[@"loginType"]) {
NSString *platform = extraData[@"loginType"];
NSString *email = extraData[@"email"];
[self.resultLabel setText:[NSString stringWithFormat:@"当前 %@ 账号所关联的邮箱 %@ 未被验证,请前往 %@ 验证邮箱后重新登录游戏", platform, email, platform]];
} else {
[self.resultLabel setText:error.localizedDescription];
}
} else if ((error.code == 40902 || error.code == 40901) && extraData) {
if (extraData[@"loginType"]) {
NSString *platform = extraData[@"loginType"];
NSString *email = extraData[@"email"];
NSArray *conflicts = extraData[@"conflicts"];
[self.resultLabel setText:[NSString stringWithFormat:@"当前 %@ 账号所关联的邮箱 %@ 对应的游戏账号已绑定其他 %@ 账号 。请使用该邮箱所关联的 %@ 登录游戏账号后进入「账号安全中心」手动进行账号绑定、解绑操作。", platform, email, conflicts[0][@"loginType"], conflicts[0][@"loginType"]]];
} else {
[self.resultLabel setText:error.localizedDescription];
}
} else {
[self.resultLabel setText:error.localizedDescription];
}
} else {
// 登陆成功
// 建议登陆成功后调用 trackUser
[XDGSDK trackUser];
NSString *xdUserID = result.userId; // result.userId 为用户在 XD 账户系统的 XD User ID(用户唯一标识)
NSString *name = result.name;
NSString *nickName = result.nickName;
NSString *avatar = result.avatar;
}
}];
public enum LoginType{
Default,
TapTap,
Google,
Facebook,
Apple,
LINE,
Twitter,
Steam,
Guest,
Phone
}
XDGAccount.LoginByType(LoginType loginType, xdgUser => {
//登录成功,国内开始处理防沉迷,海外就可以进游戏了
// xdgUser.userId 为用户在 XD 账户系统的 XD User ID(用户唯一标识)
Debug.LogFormat($"Wrapper Tap登录成功! userId: {xdgUser.userId} 昵称: {xdgUser.nickName} 头像: {xdgUser.avatar}");
},
error => {
if (error != null && error.extra_data != null){
//参考链接:https://docs.xdglobalapi.com/account/account-server#taptap-%E7%BB%91%E5%AE%9A%E9%82%AE%E7%AE%B1%E6%9C%AA%E9%AA%8C%E8%AF%81
if (error.code == 40021){
游戏需要弹框提示: "当前 %s 账号所关联的邮箱( %s )未被验证,请前往 %s 验证邮箱后重新登录游戏。", loginType, email, loginType。
extra_data = {
"email":"unverified@gmail.com",
"loginType":"taptap"
}
}else if ((error.code == 40902 || error.code == 40901)){
游戏需要弹框提示: "当前 %s 账号所关联的邮箱( %s )对应的游戏账号已绑定其他 %s 账号 。请使用该邮箱所关联的 %s 登录游戏账号后进入「账号安全中心」手动进行账号绑定、解绑操作。", loginType, email, conflicts[0]["loginType"], conflicts[0]["loginType"]。
extra_data = {
"email":"1969910954@qq.com",
"loginType":"google",
"conflicts":[
{
"loginType":"apple",
"userId":382602976365400064
},
{
"loginType":"taptap",
"userId":382602976365400064
}
]
}
}
}
});
UENUM(BlueprintType)
enum class EXDGLoginType : uint8 {
// 自动登录
Default = UINT8_MAX,
// 游客登录
Guest = 0,
WeChat = 1,
Apple = 2,
Google = 3,
Facebook = 4,
TapTap = 5,
LINE = 6,
Twitter = 7,
QQ = 8,
Steam = 10,
Phone = 11
};
FXDGAccount::LoginByType(LoginType,
FXDGUser::FDelegate::CreateLambda([](const FXDGUser& User) {
// 登录成功
// *User.UserId 为用户在 XD 账户系统的 XD User ID(用户唯一标识)
UE_LOG(LogTemp, Log, TEXT("登录成功: XDUserID[%s], 头像Url[%s], 昵称[%s]"), *User.UserId, *User.Avatar, *User.NickName);
}), FXDGError::FDelegate::CreateLambda([](const FXDGError& Error) {
// 登录失败
}));
SDK 默认传入除 Default 之外的类型时会采用的是进行授权登录的方式, 为避免每次进入游戏时需要重新授权登录的行为,在 token 在有效期范围内且未调用登出接口时,通过传入登录类型为 Default 来标识当前登录为自动登录行为,SDK 会记录上一次用户已经正常登录成功的用户数据。
主机登录
我们可以把「Steam/PS/Nintendo」等平台上发行的游戏称之为主机游戏,对于这些游戏,用户们会更习惯用该平台账号登录游戏。对于这类游戏,推荐使用这个接口来登录。
游戏如何正确的使用该接口,可以参考这里的方案
- Unity PC
- UE
// 参考 https://github.com/xd-platform/xd_sdk_pc_upm 文档
XDGAccount.LoginByConsole(xdgUser => {
ResultText.text = $"Steam 主机登录成功:{xdgUser.nickName} userId: {xdgUser.userId} kid: {xdgUser.token.kid}";
}, null,
error => {
ResultText.text = $"Steam 主机登录失败:{error}";
}
);
在项目配置目录(Configs)下的 DefaultXDConfig.ini 文件中添加如下设置开启Steam登录:
bSteamSDKLoginEnable=True
同时需要开启UnrealEngine的"OnlineSubsystem"和"OnlineSubsystemSteam"两个插件
FXDGAccount::LoginByConsole(FXDGUser::FDelegate::CreateLambda([](const FXDGUser& User) {
// 成功
}), FSimpleDelegate::CreateLambda([]() {
// 登录失败,未绑定XDUser
}), FXDGError::FDelegate::CreateLambda([](const FXDGError& Error) {
// 发生错误
}));
在 TapTap 沙盒环境内实现无感登录
XDSDK 从 6.6.0 开始在 Android 上开始支持 TapTap 沙盒环境内的无感登录,但需要将 XDConfig.json 中 tapsdk
的 permissions
内容改为只有一个 basic_info
,这个权限代表游戏只能获取 Tap 授权后的 unionid 和 openid,此时在 TapTap 的沙盒环境中用户在点击 Tap 登录后将不需要在授权页点击确认,能直接获得授权成功。
- basic_info 的权限只能单独使用,如果额外配上诸如 public_profile、user_friends 的权限,则不会有无感登录的效果。在无感登录的结果中,我们将无法提供用户昵称和头像的数据,如果业务需要使用相关数据,需要游戏自行另外调用 TDSUser 的登录接口,并传入相关的权限来重新触发授权。
- iOS 和沙盒外的 Android 环境也能使用 basic_info,但不会有无感登录的效果,会走正常用户手动确认的流程。
- PC SDK 目前不支持 basic_info。
{
xxx,
"tapsdk": {
xxx:xxx,
"permissions": [
"basic_info"
]
},
xxx
}
登出
- Android
- iOS
- Unity
- UE
XDGAccount.logout();
[XDGAccount logout];
XDGAccount.Logout();
FXDGAccount::Logout();
监听用户状态变化
针对用户登出、第三方账号绑定/解绑成功的状态变更,可添加全局回调来监听用户状态的变更:
- Android
- iOS
- Unity
- UE
public static class USER_STATUS_RESULT {
public final static int LOGOUT_CODE = 0x9001;
public final static int BIND_CHANGE_CODE = 0x1001;
public final static int UNBIND_CHANGE_CODE = 0x1002;
public final static int PROTOCOL_AGREED_AFTER_LOGOUT_CODE = 0x2001; // 8193
}
XDGAccount.addUserStatusChangeCallback(new XDGUserStatusChangeCallback(){
@Override
public void userStatusChange(int code,String message){
if(code == Constants.USER_STATUS_RESULT.LOGOUT_CODE){
// 用户退出登录
} else if (code == Constants.USER_STATUS_RESULT.BIND_CHANGE_CODE) {
// 绑定成功
} else if (code == Constants.USER_STATUS_RESULT.UNBIND_CHANGE_CODE) {
// 解绑成功
} else if (code == Constants.USER_STATUS_RESULT.PROTOCOL_AGREED_AFTER_LOGOUT_CODE) {
// 用户退出登录后点击同意协议
}
}
});
typedef NS_ENUM(NSInteger,XDGUserStateChangeCode) {
XDGUserStateChangeCodeLogout = 0x9001, // user logout
XDGUserStateChangeCodeBindSuccess = 0x1001, // user bind success,msg = entry type in string,eg: @"TAPTAP"
XDGUserStateChangeCodeUnBindSuccess = 0x1002, // user unbind success,msg = entry type in string
XDGUserStateChangeCodeProtocolAgreedAfterLogout = 0x2001, // user clicked confirm in agreement dialog showed after logout
};
[XDGAccount addUserStatusChangeCallback:^(XDGUserStateChangeCode userStateChangeCode, NSString *message) {
if (userStateChangeCode == XDGUserStateChangeCodeBindSuccess) {
// 绑定新平台成功
NSLog(@"TDSGlobalDEMO 绑定成功:%@", message);
[XDGAccount getUser:^(XDGUser *_Nullable result, NSError *_Nullable error) {
NSLog(@"TDSGlobalDEMO boundAccounts:%@", result.boundAccounts);
}];
} else if (userStateChangeCode == XDGUserStateChangeCodeUnBindSuccess) {
// 解绑平台成功
NSLog(@"TDSGlobalDEMO 解除绑定成功:%@", message);
[XDGAccount getUser:^(XDGUser *_Nullable result, NSError *_Nullable error) {
NSLog(@"TDSGlobalDEMO boundAccounts:%@", result.boundAccounts);
}];
} else if (userStateChangeCode == XDGUserStateChangeCodeLogout) {
// 用户退出登录
} else if (userStateChangeCode == XDGUserStateChangeCodeProtocolAgreedAfterLogout) {
// 用户退出登录后点击同意协议
}
}];
XDGAccount.AddUserStatusChangeCallback((type, msg) => {
if (type == XDGUserStatusCodeType.LOGOUT){
//SDK已经退出,游戏需要切换到登录页面
}
});
public enum XDGUserStatusCodeType : int{
// 登出
LOGOUT = 0x9001,
// 绑定
BIND = 0x1001,
// 解绑
UNBIND = 0x1002,
// 退出登录后确认协议
ProtocolAgreedAfterLogout = 0x2001,
//出错了,未知code
ERROR = -100
}
enum class EXDGUserChangeState : int64 {
UserLogout = 0x9001, // user logout
UserBindSuccess = 0x1001, // user bind success,msg = entry type in string,eg: @"TAPTAP"
UserUnBindSuccess = 0x1002, // user unbind success,msg = entry type in string
ProtocolAgreedAfterLogout = 0x2001, // user clicked confirm in agreement dialog showed after logout
};
FXDGAccount::OnUserStatusChange.AddLambda([](EXDGUserChangeState UserState, const FString& Msg) {
if (UserState == EXDGUserChangeState::UserLogout) {
// 游戏账号应登出
} else if (UserState == EXDGUserChangeState::UserBindSuccess) {
// 账号被绑定
} else if (UserState == EXDGUserChangeState::UserUnBindSuccess) {
// 账号被解绑
} else if (UserState == EXDGUserChangeState::ProtocolAgreedAfterLogout) {
// 账号登出后协议同意
}
});
获取当前用户信息
在用户已登录的情况下,调用此方法可获取当前已登录用户的信息:
- Android
- iOS
- Unity
- UE
XDGAccount.getUser(Callback<XDGUser> callback);
XDGAccount.getUser(new Callback<XDGUser>() {
@Override
public void onCallback(XDGUser xdgUser, XDGError xdgError) {
String XDUserID = xdgUser.getId(); // 用户在 XD 账户系统的 XD User ID(用户唯一标识)
String avaterUrl = xdgUser.getAvatar();
String nickName = xdgUser.getNickName();
}
});
[XDGAccount getUser:^(XDGUser *_Nullable xdgUser, NSError *_Nullable error) {
if (error) {
// 没登录
} else {
// 当前登录用户为 xdgUser
NSString *xdUserID = xdgUser.userId; // xdgUser.userId 为用户在 XD 账户系统的 XD User ID(用户唯一标识)
NSString *name = xdgUser.name;
NSString *nickName = xdgUser.nickName;
NSString *avatar = xdgUser.avatar;
}
}];
XDGAccount.GetUser(Action<XDGUser> callback, Action<XDGError> errorCallback);
XDGAccount.GetUser((xdgUser) => {
// xdgUser.userId 为用户在 XD 账户系统的 XD User ID(用户唯一标识)
Debug.LogFormat($"Wrapper Tap 登录成功! userId: {xdgUser.userId} 昵称: {xdgUser.nickName} 头像: {xdgUser.avatar}");
}, (error) => {
Debug.LogErrorFormat($"Wrapper Tap登录失败:{error}");
});
FXDGAccount::GetUser();
if (TSharedPtr<FXDGUser> User = FXDGAccount::GetUser())
{
// *User.UserId 为用户在 XD 账户系统的 XD User ID(用户唯一标识)
UE_LOG(LogTemp, Log, TEXT("登录成功: XDUserID[%s], 头像Url[%s], 昵称[%s]"), *User->UserId, *User->Avatar, *User->NickName);
}
else
{
//当前未登录
}
打开用户中心
在用户已登录的情况下,SDK 在用户中心展示了当前已登录用户的一些基本信息,在此页面,用户可操作绑定/解绑第三方登录平台进行帐户的关联。
- Android
- iOS
- Unity
- UE
XDGAccount.openUserCenter();
[XDGAccount openUserCenter];
XDGAccount.OpenUserCenter();
// @param bShowLogoutButton 用户中心是否显示退出登录按钮 [Win64] [macOS]
FXDGAccount::OpenUserCenter(bool bShowLogoutButton = true);
打开帐户注销页面
用户可通过此方法进行帐户的注销操作:
- Android
- iOS
- Unity
- UE
XDGAccount.accountCancellation();
[XDGAccount accountCancellation];
XDGAccount.OpenUnregister();
FXDGAccount::AccountCancellation();
绑定第三方账号
当游戏通过一些报错信息得知用户的某个授权已经过期后,可以使用该接口让用户重新授权以正常使用某些第三方平台的数据。
Added in v6.4.1
绑定接口对于玩家来说如果授权了和之前授权不一样的账号的话,存在「换绑」的可能性,所以游戏需要谨慎使用该方法,使用前需要给到用户明确的提醒。参考 绑定账号接口 文档。
- Android
- iOS
- Unity
- UE
XDGAccount.bindByType(LoginEntryType.FACEBOOK, new XDGBindResultCallback(){
@Override
public void onBindResult(boolean success,XDGError xdgError){
if(success){
// 绑定成功
} else {
// 绑定失败
}
}
});
[XDGAccount bindByType:bindType
bindHandler:^(BOOL success, NSError *_Nullable error) {
if (success) {
// 绑定成功
} else {
// 绑定失败
}
}];
XDGAccount.BindByType(LoginType, (success, error) => {
if(success){
// 绑定成功
} else {
// 绑定失败
ResultText.text = $"绑定结果:{b}, error:{JsonUtility.ToJson(error)}";
}
});
FXDGAccount::BindByType(LoginType, FXDGAccount::FBindResult::CreateLambda([](bool IsSuccess, const FXDGError& Error) {
if (IsSuccess) {
} else {
}
}));
检查本地某个平台的授权是否有效
v6.4.1 支持 TapTap 和 Facebook
v6.7.0 新增支持 Twitter
- Android
- iOS
- Unity
- UE
boolean isTokenActive = XDGAccount.isTokenActiveWithType(LoginEntryType.TAP_TAP);
BOOL isTokenActive = [XDGAccount isTokenActiveWithType:LoginEntryTypeTapTap];
XDGAccount.IsTokenActiveWithType(LoginType.Facebook, (isActive) => {
ResultText.text = $"结果:{isActive}";
});
// [iOS][Android]
FXDGAccount::IsTokenActiveWithType(LoginType);