Issue with player names on leaderboards

Hi there,

I have encountered an issue in my game with player names in the Leaderboards api.

My game runs on the Oculus/Meta Quest platform. When we sign users into the unity authentication back end, we update their player name with the following code:

string cachedUnityPlayerName = AuthenticationService.Instance.PlayerName;
string unityPlayerNameBase = string.IsNullOrEmpty(cachedUnityPlayerName) ? "" : cachedUnityPlayerName.Split('#')[0];
if (string.IsNullOrEmpty(cachedUnityPlayerName) || !unityPlayerNameBase.Contains(oculusPlayerName))
{
    Debug.Log($"[Authentication] Oculus player: {oculusPlayerName} updating player name");
    if (oculusPlayerName.Any(char.IsWhiteSpace))
    {
        oculusPlayerName = oculusPlayerName.Replace(' ', '_');
        Debug.Log($"[Authentication] Oculus player has whitespaces in their name, replace with underscores. Oculus player name for authentication is now: {oculusPlayerName}");
    }
    await AuthenticationService.Instance.UpdatePlayerNameAsync(oculusPlayerName);
}

After this code runs, we also upload the player name to cloud save as a publically accessible variable, because the Authentication player name is not directly accessible (can’t figure out why this is not a feature):

var data = new Dictionary<string, object> { { "playerName", oculusPlayerName} };
await CloudSaveService.Instance.Data.Player.SaveAsync(data, new SaveOptions(new PublicWriteAccessClassOptions()));

Some of our users have reported that their names are being displayed incorrectly in the leaderboard. Here is an example: In the attached screenshot, the player in first place has what appears to be a randomly generated username “SarcasticOrganizedLynx”. I have double checked in the Unity cloud dasboard, and the correct name is being uploaded to cloud save, but clearly not to the Authentication Service.

Can anyone explain why there is a mismatch?

2 Likes

I know this thread is old, but it can help others:

This random name is created when you call:

  • AuthenticationService.Instance.GetPlayerNameAsync()

if you do not want the name to get auto-generated, you have to pass false:

  • AuthenticationService.Instance.GetPlayerNameAsync(autoGenerate: false)
2 Likes

Did you ever find a solution to this? We have the same problem.

To clarify and expand on what @sandolkakos already wrote above…

You can get and set Player Names using these methods provided by the Unity Authentication SDK:

  • AuthenticationService.Instance.GetPlayerNameAsync()
  • AuthenticationService.Instance.UpdatePlayerNameAsync()

If a name has already been set once it will be persisted and it can be read locally without calling out to to a service by reading from AuthenticationService.Instance.PlayerName.

You can also interact with Player Names using the REST API.

Player Names are supported as first class feature in Unity Leaderboards, Friends and Lobby services, and when you get or set a Player Name it will be returned automatically (e.g. in a Leaderboards response).

The Player Names feature is distinct from Cloud Save, although you can also use Player Data and Public property to store player names, you would need to write the logic to resolve the Player IDs to fetch whatever property you wanted to use in Cloud Save to store player names.

If you are calling UpdatePlayerNameAsync() and find the name is not persisting to Leaderboards this could indicate an issue with the data being passed to UpdatePlayerNameAsync() and/or missing error handling.

If the Player Names feature is behaving as expected - if it’s returning as if successful, and there are no errors but it’s not persisting the player name - please do get in touch with the Project ID and Player ID either here or via DM and I can look into it to see if there is an issue impacting a specific player account that is causing it not to be persisted.

Best regards,

Iain Collins

PS: Apologies for not replying to this one this sooner, we had a bit of an issue with a monitoring feed for posts on some forum topics for a while, and so I only saw this thread when @Claytonious replied to it.

Thanks for the extra info @IainUnity3D . I think in our case the root cause here was that our display names originate from our players’ Meta accounts (we are a Quest game and we have linked to Oculus for authentication). Some of those names contain spaces. I think that this, therefore, causes Unity’s authentication service to refuse to set them (the docs are clear that spaces are not allowed). We are trying a fix now which sanitizes those display names before sending them to Unity which hopefully will resolve the problem.

However, I should point out @IainUnity3D that the reason this had gone unnoticed was that Unity’s AuthenticationService, like all of the cloud service client SDK’s, uses async C# methods. Because we were calling AuthenticationService.UpdatePlayerNameAsync() from a normal non-async method, we did NOT ever see any exceptions being raised by it even when it was rejecting our arguments. So we see nothing on our bug telemetry from players or in our own testing. It appears to succeed because any exception it’s throwing just disappears into the ether without even being logged.

This insistence by all of the Unity cloud services SDK’s to use async patterns has always made them oddballs in the Unity world. I can see pros and cons. But these discussion boards and content all over the Internet is raging with opinions about how and why async works in Unity, with the most notable pitfall being that you can very often accidentally silence all exceptions.

If you guys insist on staying the async route, then you should probably at least log errors to the console when something goes wrong? Because there’s a high chance the caller will never be aware of an exception if you throw one.

When working with async Task methods, you should always await them in a try/catch.
if you don’t await them, even inside a try/catch, the exceptions will not get caught.

private async void Start()
{
    try
    {
        await MyAsyncTaskMethod();
    }
    catch (Exception e)
    {
        Debug.LogException(e);
    }
}

You do not need to add try/catch everywhere if you do not need to handle a specific case, but at least the starting point should be awaited in a try/catch. See the example, a non-Task method has the async keyword to make it possible to await inside the try/catch.

If you do not use async calls for server communication, what would be the approach? Callbacks with Coroutines used to be the worse systems I’ve ever seen in any project I worked before we have the wonderful async Task feature <3

Yes, I know. But in this scenario (and many like it), we are calling Unity authentication services from within the context of a callback from an Oculus SDK method which is itself NOT async.

This comes up all the time. Sometimes you need to call from a method that is not async and you can’t change the signature of the method you’re calling from because it’s third party or from core Unity where something’s not async yet. So you can’t await. But you can call your own async void method, or you can call an async Task method (which both give you DIFFERENT exception handling) or you can manually get an awaiter and wrap its handling yourself, and so on. There are various ways to cope with calling an async method from a non async method, but they’re clunky and some of them can lead to deadlocks if done incorrectly, etc. And as is the topic here, there are many ways to accidentally lose their exceptions if you’re not careful.

This is similar to how things were when async was first rolling out across asp.net ecosystems: once ALL of the third parties and frameworks are async, then it’s great. But when it’s a mixture of the two it’s painful.

Because so much of unity core is not async, and even more of the unity third party sdks are not async, BUT unity authentication and other cloud services ARE, this kind of problem crops up.

I’m uninterested in arguing the merits of async vs Coroutines with callbacks etc. Both work fine at the end of the day so do whatever works for you. But I’m pointing out this specific kind of pitfall that arises because Unity cloud sdks have chosen to be async when most of the context that they run in is not.

I understand this feeling because I passed through it when starting to work with the async Task feature. But after some time working with it, you will start to get the mindset of adding try/catch whenever needed, and your unhandled exceptions will not get lost anymore.

I don’t get this part. Could you provide an example?

I’m thinking here… maybe I am wrong cause I do not know your project… if you are using a 3rd party code, that means it will not call the UGS services by itself, but your Application is the middle field between the 3rd party and UGS, right?

If I click a UnityEngine.UI.Button to load the Learderboards, I expect my app to continue running (and it does not matter if I’m using Coroutines or async Task). Then, when I have the result, I just handle the data to show the list. What is out of context in this example?

Other example… The Steam SDK structure is callback based… Lets say I want to save the Steam User Avatar on the UGS. I have to make a call to request this data on Steam SDK and wait for the callback to return with the data. Then I get this data, and call the async Task based UGS API to save it and wait for the success or failure (with try/catch, since that is how the UGS API informs us about failures).
What is out of context in this other example?

(not being rude here, sorry if it feels like I am, English is not my first language)