Molded Bits

We mold bits.

Global Scores - Integrating Scoreloop With Libgdx

| Comments

If you are looking for a post on why to integrate Scoreloop in your games, see this and this.

Scoreloop provides two ways to integrate their leaderboards in your games

  • ScoreloopUI - Use the UI provided by Scoreloop for quick integration
  • ScoreloopCore - Manage scores and achievements with Scoreloop, and display them in your own UI

This post shows you how to do both.

For Bumble, we decided to use both. The highscores screen lets you view the local highscores saved on device, as well as the global and daily highscores from Scoreloop. The main menu lets you navigate to the ScoreloopUI, where the user can view friends, check leaderboards, update Scoreloop profile and such.

Lets get started.

Background Reading

The catch is Scoreloop requires every call to its sdk methods be made from the UI (main) thread of your application (more). While all our game related code runs in a separate thread, spawned for us by libgdx (more).This means we need a way to communicate between the two threads. Lucky for us Android provides a simple way to do just that, handlers. There is an excellent tutorial here that shows how to use handlers for communication between the Android “main” thread, and the libgdx thread.

Setup

Before getting started, we need to make sure of a few things

  • Setup the 3-tier project setup as described on the libgdx wiki.
  • Create an account on Scoreloop and get the SDK from here
  • Read the documentation provided with Scoreloop. Perform the steps mentioned in “Project Setup” for both, ScoreloopUI and ScoreloopCore (or if you want to integrate just one of them, then just follow the relevant steps).

Overview

Here’s an overview of how everything is gonna work,

First, we create a setup that allows us to communicate between the “main” thread and the “libgdx” thread.

Main Project

  • Create an interface in the Main Project (lets call it “ActionResolver”). Add methods for interacting with Scoreloop. We will implement this in the Android project.
  • Create a contructor in the ApplicationListener that takes an object implementing ActionResolver as parameter. Save a reference to this object in the ApplicationListener, as we need this to make calls to Scoreloop.

Android Project

  • Create a class that implements the interface “ActionResolver”.
  • Pass the an object of the above class to the ApplicationListener when initializing it, using the constructor we made in the Main Project.

With this, you can start communicating from your “libgdx” thread to your “main” thread.

  • Create a class called ScoreloopHandler to handle interaction with Scoreloop servers.

Code

For Scoreloop integration, we first need to extend the Application class. This is covered in the Scoreloop documentation.

BumbleApplication (BumbleApplication.java) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class BumbleApplication extends Application {

  //Declare the Scoreloop Client
  private static Client client;

  static void init(final Context android_game_context) {
      if (client == null) {
          client = new Client(android_game_context,
                  "gameSecret", null);
      }
  }

  @Override
  public void onCreate() {
      super.onCreate();
      init(this);
      ScoreloopManagerSingleton.init(this,
              "gameSecret");
  }

  @Override
  public void onTerminate() {
      super.onTerminate();
      ScoreloopManagerSingleton.destroy();
  }
}

ActionResolver interface in the Main Project

ActionResolver (ActionResolver.java) download
1
2
3
4
5
6
public interface ActionResolver {
  public void bootstrap();
  public void showScoreloop();
  public void submitScore(int mode, int score);
  public void refreshScores();
}

Implementation of ActionResolverInterface in the AndroidProject

ActionResolverAndroid (ActionResolverAndroid.java) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class ActionResolverAndroid implements ActionResolver {
  Handler uiThread;
  BumbleAndroid mContext; //Your class here which extends AndroidApplication
  ScoreloopHandler handler;
  
  public ActionResolverAndroid(BumbleAndroid mContext) {
      uiThread = new Handler(); //This binds the handler to the "main" thread, see documentation of handler
      this.mContext = mContext;
      handler = new ScoreloopHandler(mContext);
  }

  @Override
  public void showScoreloop() {
      Intent intent = new Intent(mContext, EntryScreenActivity.class);
      mContext.startActivity(intent);
  }
  
  @Override
  public void submitScore(final int mode, final int score) {
      uiThread.post(new Runnable() {
          @Override
          public void run() {
              
              handler.submitScore(score);
              handler.getRankingForScore(score);
          }
      });
  }
  
  @Override
  public void refreshScores() {
      uiThread.post(new Runnable() {
          @Override
          public void run() {
              Toast.makeText(mContext, "Refreshing scores", Toast.LENGTH_SHORT).show();
              handler.getGlobalHighscores();
              handler.getTodayHighscores();
          }
      });
  }
  
  @Override
  public void bootstrap() {
      uiThread.post(new Runnable() {
          @Override
          public void run() {
              handler.getGlobalHighscores();
              handler.getTodayHighscores();
              
              //Upload local scores, if any
              ScoreloopManagerSingleton.get().submitLocalScores(null);
          }
      });
  }
}

It is of note here that we pass an object of our class extending AndroidApplication to ActionResolverAndroid. This is important because once we receive the results from Scoreloop servers for our queries (the list of scores for example), we need to be able to update the “libgdx” thread with these results. AndroidApplication provides us with a convinient method to do this, postRunnable.

Implementation of ScoreloopHandler.java

ScoreloopHandler (ScoreloopHandler.java) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
public class ScoreloopHandler {

  private BumbleAndroid mContext; //Your class here which extends AndroidApplication

  public ScoreloopHandler(BumbleAndroid context) {
      mContext = context;
  }

  public void submitScore(int scoreValue) {
      Log.d("Scoreloop", "submitting score");
      final Score score = new Score((double) scoreValue, null);
      final ScoreController myScoreController = new ScoreController(new ScoreSubmitObserver());
      myScoreController.submitScore(score);
  }

  public void getRankingForScore(int scoreValue) {
      Score score = new Score((double) scoreValue, null);
      RankingController controller = new RankingController(new RankingRequestObserver());
      controller.loadRankingForScore(score);
  }

  public void getGlobalHighscores() {
      ScoresController myScoresController = new ScoresController(new GlobalRankObserver());
      myScoresController.setSearchList(SearchList.getGlobalScoreSearchList());
      myScoresController.setRangeLength(15);
      myScoresController.loadRangeForUser(Session.getCurrentSession().getUser());
  }
  
  public void getTodayHighscores() {
      ScoresController myScoresController = new ScoresController(new DailyRankObserver());
      myScoresController.setSearchList(SearchList.getTwentyFourHourScoreSearchList());
      myScoresController.setRangeLength(15);
      myScoresController.loadRangeForUser(Session.getCurrentSession().getUser());
  }

  private class RankingRequestObserver implements RequestControllerObserver {

      @Override
      public void requestControllerDidFail(RequestController arg0,
              Exception arg1) {

      }

      @Override
      public void requestControllerDidReceiveResponse(RequestController controller) {
          Ranking ranking = ((RankingController) controller).getRanking();
          final int rank = ranking.getRank();
          mContext.postRunnable(new Runnable() {
              @Override
              public void run() {
          //This code runs on the "libgdx" thread.
          //Use a local variable to store the rank in the MainProject and set it here
              }
          });
      }
  }

  private class ScoreSubmitObserver implements RequestControllerObserver {

      @Override
      public void requestControllerDidFail(final RequestController requestController,
              final Exception exception) {
          Log.d("Scoreloop", "score submitted exception " + exception.getMessage());
      }

      @Override
      public void requestControllerDidReceiveResponse(final RequestController requestController) {
          Log.d("Scoreloop", "score submitted successsfully");

      }
  }

  private class DailyRankObserver implements RequestControllerObserver {

      @Override
      public void requestControllerDidFail(RequestController arg0,
              Exception arg1) {
          
      }

      @Override
      public void requestControllerDidReceiveResponse(RequestController controller) {
          final List<Score> retrievedScores = ((ScoresController) controller).getScores();
          mContext.postRunnable(new Runnable() {
              @Override
              public void run() {
          //This code runs on the "libgdx" thread.
          //Use a local variable to store the daily ranks in the MainProject and set it here
              }
          });
      }
  }
  
  private class GlobalRankObserver implements RequestControllerObserver {

      @Override
      public void requestControllerDidFail(RequestController arg0,
              Exception arg1) {

      }

      @Override
      public void requestControllerDidReceiveResponse(RequestController controller) {
          final List<Score> retrievedScores = ((ScoresController) controller).getScores();
          mContext.postRunnable(new Runnable() {
              @Override
              public void run() {
          //This code runs on the "libgdx" thread.
          //Use a local variable to store the global ranks in the MainProject and set it here
              }
          });
      }

  }
}

Now we can call the Scoreloop methods from the game code as such,

Call Scoreloop methods from game code (BumbleGame.java) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class BumbleGame extends Game {

  ActionResolver actionResolver;
  
  MainMenuScreen mainMenuScreen;
  
  
  public BumbleGame(ActionResolver resolver) {
      this.actionResolver = resolver;
  }
  
  @Override
  public void create() {
      Assets.load();
      
      mainMenuScreen = new MainMenuScreen(this);
      setScreen(mainMenuScreen);
      
      if(actionResolver != null)
          actionResolver.bootstrap();
  }
  
  public void saveScore(int score) {
      //Submit to score loop
      if(actionResolver != null)
          actionResolver.submitScore(0, score);
  }

  public void showScoreloop() {
    if(actionResolver != null) {
      actionResolver.showScoreloop();
    }
  }
}

Thats about all you need to integrate Scoreloop with libgdx for global scores. Hope it helps. If I missed anything, do let me know through the comments.

Cheers,
basilisk
Molded Bits

Comments