After the launch of Undersea Odyssey last week, I've been splitting my time between a lot of different endeavors: promoting, working on the artwork for the next release, laying plans for my next major game project...and bugfixing. In fact, I've already put out two new updates for UO, fixing things that were only discovered once I had more people playing than just beta testers, who Google flips some magical switch for so that everything just works for them. That's a helpful feature for testing in-app purchases, but not for, say, leaderboards.
When a player goes to check the leaderboards in-game, they see a list of the top ten players and their scores, then their own rank and score underneath. ...Or they should. The first thing that I discovered very quickly was that if the initialization and login to Google Play fails, none of this data will be available, not even the global scores that have nothing to do with the current player. There isn't a great way to handle this, either. I thought about not letting the player past the first screen if their sign-in fails, but I figured that in the end it was a better option to always let them play the game, just not see updated leaderboards. I also discovered that if you have your Google Play settings set to private ("don't let other users see my game activity"), that includes leaderboard scores as well, so you'll never show up in the top ten. Worse, if you go and flip that setting later, your score has a high probability of getting lost in the void; you or other players still won't be able to see it, but it exists on the leaderboard now, so you'll need to set a better score to get it to update and finally become visible.
Those aren't things that I can do very much about, unfortunately. Something that I did manage to fix was the query for the top ten scores. As more began to populate, I started to notice that the names and scores weren't always matching up. The scores would always be in correct numerical order, but sometimes the names would be all shuffled up. This was absolutely baffling to me, and I must have checked over the relevant code a dozen times: fetch the scores from the Google server, and in the callback, populate an array with their user IDs. Then make another call to fetch the human-readable names associated with the IDs (since Google didn't make a direct accessor to get them from the scores themselves). As I said above, the order of the scores was always correct, and the IDs were added to the array in that same order, so why did the names come back wrong? I found exactly one unanswered bug report from 2017 about this exact same issue, and the only thing that that user was able to deduce was that even though you pass the ID-to-name function an ordered array, the order of the return data is not guaranteed. Which has got to be the stupidest thing that I have ever...
Okay, so I solved it by doing the username calls one at a time; they can't come back out of order if you only ever ask for one. I didn't do this originally because I was afraid of the performance, waiting for several callbacks to return, and since their stupid broken method is set up to take an array argument...but there doesn't seem to be a noticeable difference on my test device, so no harm done.
The second thing I was able to do something about was the player score and rank, which should be displayed where the question marks along the bottom of the above image are. In my original system design, I stored an array holding the player's personal best scores for each level, and populated the first field based on that. Then I called another Google method to get the leaderboard scores with the "PlayerCentered" parameter. This was supposed to get the scores around the player's score on that leaderboard (and I still have no idea what that really means...like if I pass it a "5" parameter instead of the "1" I always use, will it get the two above and two below, with the player's score in the middle?). In my beta tests, this worked fine: when the tester had set a score for the given level, their rank was displayed as 1. When they hadn't, their rank was 0. After the game launched and I started seeing other scores, I started getting back 1s even when they didn't have a score on that board. As best I could figure, this was because the return of "PlayerCentered" is ambiguous. If Google can't find the requested player, they just return...the top score, I guess? Which is why it was always 1. When I was seeing 0s, I think that was because there were no scores at all on the board, so there wasn't even a top score to return. Okay, so the first thing I tried to fix this was to only fetch the leaderboard rank if I had a local score saved; don't even bother going to the server if I didn't have local data saying the level had been played, just leave a bunch of question marks instead.
This is what's going on in the current build you can download from the Play Store. But it isn't perfect. I've discovered that if your app's data gets cleared, or if you need to uninstall and reinstall (like, for testing purposes), and your save data file gets erased, you can get into the exact situation shown above: a score on the leaderboard, but no information about local score or rank. On one hand, maybe that's "working as intended": you got rid of your save, you lost your records. But they do still exist on the leaderboard, so you get a weird visual. And there's something else: cross-device play. There's currently no way (and no planned way, either) to carry over your unlocked stuff, but if you want to play the game on multiple phones, shouldn't you see your same high scores across the board too? The simplest solution would be to just decouple the local score functionality at all, and always go for the server when a personal best needs to be displayed, just like I do for the global best. I was hesitant about this for a few reasons, however. First, the increased number of callbacks, their performance, etc. again. There are quite a lot of places I pull this local score up. Second, there are a few other places in the code which check for specific scores for levels, or whether they have been played at all...and I didn't want to rely on callbacks, or Google's questionable return values, for those things as well.
So here's what I ended up going with: immediately after initialization and sign-in when the game first starts, I search for the player on each leaderboard, and if their score is non-zero (score, not rank, because the former seems to work in all cases), I save whatever I find into my local array. Then I can continue to use the local numbers how I have been, updating to both them and the server only when a new high score is achieved. And I can continue to only fetch player rank on the leaderboards menu if I have a non-zero local score...because that non-zero came from the same leaderboard during initialization, so I know it's there.
The update with this absolute fix will go live at the same time as the next level- and sub-pack drops. But why not get the game and start grinding for treasure in the meantime?
Undersea Odyssey is available now: https://play.google.com/store/apps/details?id=com.ZWOLYAGAMES.UnderseaOdyssey