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.
publicclassBumbleApplicationextendsApplication{//Declare the Scoreloop ClientprivatestaticClientclient;staticvoidinit(finalContextandroid_game_context){if(client==null){client=newClient(android_game_context,"gameSecret",null);}}@OverridepublicvoidonCreate(){super.onCreate();init(this);ScoreloopManagerSingleton.init(this,"gameSecret");}@OverridepublicvoidonTerminate(){super.onTerminate();ScoreloopManagerSingleton.destroy();}}
publicclassActionResolverAndroidimplementsActionResolver{HandleruiThread;BumbleAndroidmContext;//Your class here which extends AndroidApplicationScoreloopHandlerhandler;publicActionResolverAndroid(BumbleAndroidmContext){uiThread=newHandler();//This binds the handler to the "main" thread, see documentation of handlerthis.mContext=mContext;handler=newScoreloopHandler(mContext);}@OverridepublicvoidshowScoreloop(){Intentintent=newIntent(mContext,EntryScreenActivity.class);mContext.startActivity(intent);}@OverridepublicvoidsubmitScore(finalintmode,finalintscore){uiThread.post(newRunnable(){@Overridepublicvoidrun(){handler.submitScore(score);handler.getRankingForScore(score);}});}@OverridepublicvoidrefreshScores(){uiThread.post(newRunnable(){@Overridepublicvoidrun(){Toast.makeText(mContext,"Refreshing scores",Toast.LENGTH_SHORT).show();handler.getGlobalHighscores();handler.getTodayHighscores();}});}@Overridepublicvoidbootstrap(){uiThread.post(newRunnable(){@Overridepublicvoidrun(){handler.getGlobalHighscores();handler.getTodayHighscores();//Upload local scores, if anyScoreloopManagerSingleton.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.
publicclassScoreloopHandler{privateBumbleAndroidmContext;//Your class here which extends AndroidApplicationpublicScoreloopHandler(BumbleAndroidcontext){mContext=context;}publicvoidsubmitScore(intscoreValue){Log.d("Scoreloop","submitting score");finalScorescore=newScore((double)scoreValue,null);finalScoreControllermyScoreController=newScoreController(newScoreSubmitObserver());myScoreController.submitScore(score);}publicvoidgetRankingForScore(intscoreValue){Scorescore=newScore((double)scoreValue,null);RankingControllercontroller=newRankingController(newRankingRequestObserver());controller.loadRankingForScore(score);}publicvoidgetGlobalHighscores(){ScoresControllermyScoresController=newScoresController(newGlobalRankObserver());myScoresController.setSearchList(SearchList.getGlobalScoreSearchList());myScoresController.setRangeLength(15);myScoresController.loadRangeForUser(Session.getCurrentSession().getUser());}publicvoidgetTodayHighscores(){ScoresControllermyScoresController=newScoresController(newDailyRankObserver());myScoresController.setSearchList(SearchList.getTwentyFourHourScoreSearchList());myScoresController.setRangeLength(15);myScoresController.loadRangeForUser(Session.getCurrentSession().getUser());}privateclassRankingRequestObserverimplementsRequestControllerObserver{@OverridepublicvoidrequestControllerDidFail(RequestControllerarg0,Exceptionarg1){}@OverridepublicvoidrequestControllerDidReceiveResponse(RequestControllercontroller){Rankingranking=((RankingController)controller).getRanking();finalintrank=ranking.getRank();mContext.postRunnable(newRunnable(){@Overridepublicvoidrun(){//This code runs on the "libgdx" thread.//Use a local variable to store the rank in the MainProject and set it here}});}}privateclassScoreSubmitObserverimplementsRequestControllerObserver{@OverridepublicvoidrequestControllerDidFail(finalRequestControllerrequestController,finalExceptionexception){Log.d("Scoreloop","score submitted exception "+exception.getMessage());}@OverridepublicvoidrequestControllerDidReceiveResponse(finalRequestControllerrequestController){Log.d("Scoreloop","score submitted successsfully");}}privateclassDailyRankObserverimplementsRequestControllerObserver{@OverridepublicvoidrequestControllerDidFail(RequestControllerarg0,Exceptionarg1){}@OverridepublicvoidrequestControllerDidReceiveResponse(RequestControllercontroller){finalList<Score>retrievedScores=((ScoresController)controller).getScores();mContext.postRunnable(newRunnable(){@Overridepublicvoidrun(){//This code runs on the "libgdx" thread.//Use a local variable to store the daily ranks in the MainProject and set it here}});}}privateclassGlobalRankObserverimplementsRequestControllerObserver{@OverridepublicvoidrequestControllerDidFail(RequestControllerarg0,Exceptionarg1){}@OverridepublicvoidrequestControllerDidReceiveResponse(RequestControllercontroller){finalList<Score>retrievedScores=((ScoresController)controller).getScores();mContext.postRunnable(newRunnable(){@Overridepublicvoidrun(){//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
publicclassBumbleGameextendsGame{ActionResolveractionResolver;MainMenuScreenmainMenuScreen;publicBumbleGame(ActionResolverresolver){this.actionResolver=resolver;}@Overridepublicvoidcreate(){Assets.load();mainMenuScreen=newMainMenuScreen(this);setScreen(mainMenuScreen);if(actionResolver!=null)actionResolver.bootstrap();}publicvoidsaveScore(intscore){//Submit to score loopif(actionResolver!=null)actionResolver.submitScore(0,score);}publicvoidshowScoreloop(){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.