I’ve run into the following issue in my Authentication workflow for my game:
This is a bit of an edge case but I’m not sure how I should resolve this situation. At the moment, I am simply setting a player pref so that the next time the user logs back into the app, their Google Play Games data will be used instead of the Anonymous.
This somewhat resolves my issue as it works for the next time the user starts up the app. But I’m wondering how I would be able to have the user sign into their GPGS account upon catching this exception immediately.
Do I need to somehow sign out of the anonymous login, or delete that login and then try to sign into GPGS? If so, how can I do this?
Since your player may have progressed differently on the anonymous account as well as his google play account, we generally recommend to let the players choose which account he wants to keep.
If the player choose to keep his current account, you can perform a force link operation which will remove it from the previous one and link it to the one. If the player chooses the previously linked one, you can just sign out and sign in using google play.
I hope this is useful, let us know if you have any questions.
Sory but your documentation is not enough - how can I get playerID of cloud account to check his progress and etc? There is no way to get cloud player ID without sign in, but if on new device with anonymus sign in I made some progress and than decide to link my account, but I did it before on old device - how can I check my saved cloud user without sign in? Because if I do sign in to check progress - I’ll lost my anonymus account in case that it was signed out
I’ve had some hard moments thinking about how to deal with both AccountAlreadyLinked and AccountLinkLimitExceeded. I’m posting it here in case it is helpful for you or anyone else in the future.
Brief explanation:
This is used to:
Silently log a returning user to their already linked Steam account.
or silently create a new UGS Player account for users not logged in and then link it to Steam.
It will also silently link an anonymous user to their Steam.
It is also gonna get the name of the user (from Steam) and set it in the UGS Player.
In my game flow, I never Unlink accounts. At least not yet. Maybe, in the future, I will add an option to allow the user to do that manually.
public IAuthenticationService Service { get; private set; }
public void Awake() // or any other initializer you use
{
Service = AuthenticationService.Instance;
}
public async Task SilentSignIn(CancellationToken token)
{
// On the first time we open the game, we try to sign in with the native provider
if (!Service.SessionTokenExists && await SignIn(token))
{
// We do not want to auto-generate the player name
string unityGameServicePlayerName = await Service.GetPlayerNameAsync(autoGenerate: false);
if (string.IsNullOrEmpty(unityGameServicePlayerName))
{
// Add the player Name based on the native provider
string providerUserName = await GetUserName(token);
// Should not have any space
providerUserName = providerUserName.Replace(" ", "\\u00A0");
// Should not have more than 50 characters
providerUserName = providerUserName[..Math.Min(providerUserName.Length, 50)];
// Update the player name in the UGS Player
_ = await Service.UpdatePlayerNameAsync(providerUserName);
}
token.ThrowIfCancellationRequested();
}
// In case the native provider did not sign in or from the 2nd time we open the game, we try to sign in anonymously
if (!Service.IsSignedIn && !await SignInAnonymouslyAsync())
{
Debug.LogError("Anonymously sign in failed.");
return;
}
Debug.Log($"===> Signed in with PlayerId: {Service.PlayerId}");
if (await IsLinked(token))
{
// If the linked provider is owned by the current UserId logged in the provider client, we are good to go
if (await IsLinkedToProviderOwnedByCurrentUserId(token))
{
return;
}
}
else
{
// Try to link only when the UGS Player is not linked to the native provider
if (await Link(false, token))
{
return;
}
}
Debug.Log("===> Going to sign out and sign in with native provider...");
// Sign out current UGS Player
Service.SignOut();
// TODO: Delete all local saved data...
// Sign in to recover the UGS Player owned by current Provider UserId
if (await SignIn(token))
{
Debug.Log($"===> Signed in with native provider. PlayerId: {Service.PlayerId}");
return;
}
// All native provider sign in attempts failed. Sign in anonymously.
if (await SignInAnonymouslyAsync())
{
Debug.Log($"===> Signed in anonymously after all provider attempts. PlayerId: {Service.PlayerId}");
return;
}
Debug.LogError("All sign in attempts failed.");
}
public async Task<bool> SignIn(CancellationToken token)
{
await Initialize(token);
token.ThrowIfCancellationRequested();
_sessionTicket = await RefreshTicketForWebApi(token);
token.ThrowIfCancellationRequested();
bool result = false;
try
{
await Service.SignInWithSteamAsync(_sessionTicket, Identity, _appId);
result = true;
}
catch (AuthenticationException e) when (e.ErrorCode == AuthenticationErrorCodes.BannedUser)
{
Debug.LogException(e);
}
catch (AuthenticationException e)
{
// Compare error code to AuthenticationErrorCodes
// Notify the player with the proper error message
Debug.LogException(e);
}
catch (RequestFailedException e)
{
// Compare error code to CommonErrorCodes
// Notify the player with the proper error message
Debug.LogException(e);
}
catch (Exception e)
{
Debug.LogException(e);
}
// When you're done interacting with the entity you must call CancelAuthTicket on the handle.
SteamUser.CancelAuthTicket(_authTicketHandler);
return result;
}
public async Task<string> GetUserName(CancellationToken token)
{
await Initialize(token);
token.ThrowIfCancellationRequested();
string userName = SteamFriends.GetPersonaName();
return userName;
}
private async Task<bool> SignInAnonymouslyAsync()
{
bool result = false;
try
{
await Service.SignInAnonymouslyAsync();
result = true;
}
catch (AuthenticationException e) when (e.ErrorCode == AuthenticationErrorCodes.BannedUser)
{
Debug.LogException(e);
}
catch (AuthenticationException e)
{
// Compare error code to AuthenticationErrorCodes
// Notify the player with the proper error message
Debug.LogException(e);
}
catch (RequestFailedException e)
{
// Compare error code to CommonErrorCodes
// Notify the player with the proper error message
Debug.LogException(e);
}
catch (Exception e)
{
Debug.LogException(e);
}
return result;
}
public async Task<bool> IsLinked(CancellationToken token)
{
if (!Service.IsSignedIn)
{
Debug.LogError("IsLinked - User is not signed in.");
return false;
}
var playerInfo = await Service.GetPlayerInfoAsync();
token.ThrowIfCancellationRequested();
if (playerInfo?.Identities == null || playerInfo.Identities.Count == 0)
{
return false;
}
return playerInfo.Identities.Exists(identity => identity.TypeId == IdProviderKey);
}
public async Task<bool> IsLinkedToProviderOwnedByCurrentUserId(CancellationToken token)
{
var playerInfo = await Service.GetPlayerInfoAsync();
token.ThrowIfCancellationRequested();
if (playerInfo?.Identities == null || playerInfo.Identities.Count == 0)
{
return false;
}
string providerUserId = await GetUserId(token);
return playerInfo.Identities.Exists(
identity => identity.TypeId == IdProviderKey &&
identity.UserId == providerUserId);
}
public async Task<bool> Link(bool forceLink, CancellationToken token)
{
if (!Service.IsSignedIn)
{
Debug.LogError("IsLinked - User is not signed in.");
return false;
}
await Initialize(token);
token.ThrowIfCancellationRequested();
_sessionTicket = await RefreshTicketForWebApi(token);
token.ThrowIfCancellationRequested();
bool result = false;
try
{
var linkOptions = new LinkOptions { ForceLink = forceLink };
await Service.LinkWithSteamAsync(_sessionTicket, Identity, _appId, linkOptions);
result = true;
}
catch (AuthenticationException e) when (e.ErrorCode == AuthenticationErrorCodes.BannedUser)
{
await AuthLocalizedStringsData.ThrowBannedUserException();
}
catch (AuthenticationException e) when (e.ErrorCode == AuthenticationErrorCodes.AccountAlreadyLinked)
{
// This means the Steam Account is already linked to another UGS Player.
// Handle it as you want... In my case, I show a Dialog with a user friendly message.
Debug.LogException(e);
}
catch (AuthenticationException e) when (e.ErrorCode == AuthenticationErrorCodes.AccountLinkLimitExceeded)
{
// This means there is already a Steam Account linked to the UGS Player.
// Handle it as you want... In my case, I show a Dialog with a user friendly message.
Debug.LogException(e);
}
catch (AuthenticationException e)
{
// Compare error code to AuthenticationErrorCodes
// Notify the player with the proper error message
Debug.LogException(e);
}
catch (RequestFailedException e)
{
// Compare error code to CommonErrorCodes
// Notify the player with the proper error message
Debug.LogException(e);
}
catch (Exception e)
{
Debug.LogException(e);
}
// When you're done interacting with the entity you must call CancelAuthTicket on the handle.
SteamUser.CancelAuthTicket(_authTicketHandler);
return result;
}