Unity GameCenter Support

I’ve spend the last couple of days implementing GameCenter support in¬†a Unity game and found more than a few pitfalls all of which goes back to a seriously broken Unity API.

So, if you are¬†planning on using the Social API in Unity3D, here’s a couple of landmines you may not want to step on (I’m sure there are others, but these are the ones that I wasted time on) – oh, and just for the record

1) I love Unity – it’s an awesome platform. It’s just their GameCenter implementation of the Social API that could do with a bit of love ūüôā

2) These issues were found in Unity 4.5 (but Google suggests they have been there forever, so I would not count on them being fixed anytime soon)

Loading scores

The first thing that bit me is that ILeaderBoard.LoadScores only supports a single request at a time. If you try to call it multiple times, only the last invocation will receive a callback (several times, whatever good that will do you). I implemented the following helper to work around the issue:

  struct LoadScoreRequest
  {
    public string            _id;
    public string[]          _userIds;
    public BoardLoadedEvent  _callback;
  }

  public delegate void BoardLoadedEvent(ILeaderboard board, HighScore[] highScore);
  
  private bool _friendsLoaded;
  private bool _loadScoreRequestPending;
  private List<LoadScoreRequest> _loadScoreRequests = new List<LoadScoreRequest>();

  public void CreateAndLoadLeaderBoard(string id, string[]userids, BoardLoadedEvent ondone)
  {
    lock(_loadScoreRequests)
    {
      _loadScoreRequests.Add( new LoadScoreRequest() { _id=id, _userIds = userids, _callback = ondone }  );
      SendNextScoreRequest();
    }
  }

  private void SendNextScoreRequest()
  {
    LoadScoreRequest req;
    lock(_loadScoreRequests)
    {
      if(!_friendsLoaded)
        return;

      if(_loadScoreRequestPending)
        return;

      if(_loadScoreRequests.Count==0)
        return;

      _loadScoreRequestPending = true;
      req = _loadScoreRequests[0];
      _loadScoreRequests.RemoveAt(0);
    }

    if(req._userIds==null)
    {
      req._userIds = new string[Social.localUser.friends.Length];
      int i=0;
      foreach(IUserProfile friend in Social.localUser.friends)
      {
        req._userIds[i++] = friend.id;
      }
    }
    
    ILeaderboard board = Social.CreateLeaderboard();
    board.id = req._id;
    board.SetUserFilter(req._userIds);

    board.LoadScores( (bool scoresloaded) =>
    {
      req._callback(board);
      lock(_loadScoreRequests)
      {
        _loadScoreRequestPending = false;
        SendNextScoreRequest();
      }
    });
  }

Basically, it queues up all requests until the GameCenter authentication has been completed and the friends list has been loaded. It then starts loading the leaderboards one at a time, making sure each board gets returned to the proper callback.

The¬†authentication code is not shown above, but it’s straight forward and simply sets the _friendsLoaded member to “true” after loading the friends list and then calls SendNextScoreRequest in case any requests got queued up while authenticating.

Saving scores

IScore.ReportScore does not work.

At least, I could not get it to work. Use Social.ReportScore() instead as it seems to work as advertised, and has the added advantage that you don’t need to carry around the ILeaderboard reference. Why the broken method is even in the public API I can only guess at.

Error handling

Basically, there isn’t any.

At least, you can forget about checking the boolean “success” parameter provided in the various callbacks. I’ve never seen this have a value other than “true” despite a myriad of issues, none of which I’d personally characterise as a “success”.

Instead, check that the data you expected is available Рfor example, that the user- or highscore-lists are not empty.

UPDATE & WARNING:

Be really careful with Social.LoadAchievementDescriptions – it will load the associated images for all achievements and keep them in memory. They will not be released again.

In my case it amounted to 12 images of¬†1024×1024 pixels which, with some overhead of unknown origin, amounted to 96MB each time the game refreshed the achievements list.

There’s a similar problem with loading user profiles – their profile images are also kept in memory and are not released when you let go of the profile itself.