Menu Close

Using Java to create iOS apps | LibGDX Google Play Games integration | #7

iOS applications are written in Swift or Objective-C using the Apple’s Integrated Development Environment XCode. Due to the fact that libGDX apps are written in Java (or Kotlin, Python, Clojure, Scala), it is not that easy like creating the Android part. If you really want to target the iOS platform, you have to use either RoboVM (project was closed to the time I started with libGDX iOS coding, but there is the MobiDevelop’s RoboVM Fork) or Multi-OS Engine (which was the best alternative by that time and now).

What is the Multi-OS Engine and how can you use it?

The Multi-OS Engine is a technology that provides developers to use Java for creating iOS and Android mobile applications on Windows and Mac OS X machines. You are able to reuse your platform-independent Java code instead of writing a complete new app for each platform, but it is necessary to add native UIs. It offers plugins for Android Studio and Eclipse, is 100% open source and can be used via Maven. MOE is now hosted by its originator, Migeran.

You might look here on how to get started. Basically it is (if you are using Android Studio) installing the Multi-OS Engine Plugin and start coding. Here are some sample repositories:

If you start a libGDX project from scratch, I recommend using the Setup App which creates a custom project and downloads the required dependencies automatically for you.

Using the iOS frameworks and libraries in your Java code

The Multi-OS Engine comes with a built-in “Nat/J library for Java native binding” function, means that you can use the Wrapnatjgen wrapper to generate Java bindings for selected native header files (Objective-C). You can either use the command line or the context menu (might be easier) to Link third party frameworks or libraries to your project:

moe-context-menu

After that, make sure that you have the wrapped jar file in the ios/lib folder and you Xcode project has the libraries / frameworks linked, otherwise you will run into linker errors. Add them per hand if necessary (Xcode: Open Build Phases -> Link Binary with Libraries).

Adding the iOS Google Play Services framework to your project

I followed the official instructions on Get Started with Play Games for iOS (Option2: Manual installation). I had to download the following frameworks:

  • Google Sign-In important: When testing the app via Testflight, I couldn’t even launch the app. I figured out that the reason was the deprecated GoogleSignIn.bundle. So you might need to remove it from the Xcode project.
  • Google+ SDK
  • Play Games C++ SDK

Also make sure that you have the following frameworks in your project (Xcode: Open Build Phases -> Link Binary with Libraries):

  • AddressBook.framework
  • AssetsLibrary.framework
  • CoreData.framework
  • CoreLocation.framework
  • CoreMotion.framework
  • CoreTelephony.framework
  • CoreText.framework
  • Foundation.framework
  • MediaPlayer.framework
  • QuartzCore.framework
  • SafariServices
  • Security.framework
  • StoreKit
  • Security.framework
  • SystemConfiguration.framework
  • libc++.dylib
  • libz.dylib

At last, add the -ObjC linker flag (like in my project) at Build Settings -> Linking -> Other Linker Flags.

Implementation

At first you need to implement the following interfaces: GPPSignInDelegate, GPGStatusDelegate, GIDSignInUIDelegate and GIDSignInDelegate. Then you define the scopes (we only need one for the games authentification) and set the preferred toast and achievement notification placements:

((GPGManager) GPGManager.sharedInstance()).setWelcomeBackToastPlacement(GPGToastPlacement.Center);
((GPGManager) GPGManager.sharedInstance()).setAchievementUnlockedToastPlacement(GPGToastPlacement.Center);
NSArray<?> scopes = NSArray.arrayWithObject(NSString.stringWithString("https://www.googleapis.com/auth/games"));
GIDSignIn.sharedInstance().setScopes(scopes);

Then you need to “tell” the Google Play Games API where to find the implemented delegate methods (In my project, I implemented them in the same class IOSMoeLauncher):

GIDSignIn.sharedInstance().setDelegate(this);
GIDSignIn.sharedInstance().setUiDelegate(this);
((GPGManager) GPGManager.sharedInstance()).setStatusDelegate(this);

In the last step for the configuration and initialization part, you need to set the cliend id and call the silent login method. You can find ID in your Developer Console.

GIDSignIn.sharedInstance().setClientID("1092360994879-8m1ki1gtjseni28ud2d9s3q2cnmfl4c0.apps.googleusercontent.com");
((GPGManager) GPGManager.sharedInstance()).signInWithClientIDSilently("1092360994879-8m1ki1gtjseni28ud2d9s3q2cnmfl4c0.apps.googleusercontent.com", true);

To enable your application to open links and also supporting the old code, you have to add the following code into your methods:

@Override
public boolean applicationOpenURLOptions(UIApplication app, NSURL url, NSDictionary<String, ?> options) {
    return GIDSignIn.sharedInstance().handleURLSourceApplicationAnnotation(url, options.get("UIApplicationOpenURLOptionsSourceApplicationKey").toString(), options.get("UIApplicationOpenURLOptionsAnnotationKey"));
}
@Override
public boolean applicationOpenURLSourceApplicationAnnotation(UIApplication application, NSURL url, String sourceApplication, @Mapped(ObjCObjectMapper.class) Object annotation) {
    return GIDSignIn.sharedInstance().handleURLSourceApplicationAnnotation(url, sourceApplication, annotation);
}

@Override
public void signInPresentViewController(GIDSignIn gidSignIn, UIViewController uiViewController) {
    if (this.uiViewController != null) {
        this.uiViewController.showDetailViewControllerSender(uiViewController, gidSignIn);
    }
}

Submitting a player’s score to a leaderboard

Probably the most important feature is to submit a player’s score to a leaderboard. Here is code with the completion handler to detect whether the submission was successful or not:

if (((GPGManager) GPGManager.sharedInstance()).isSignedIn()) {
    GPGScore score = GPGScore.scoreWithLeaderboardId("my_leaderboard_id");
    score.setValue(42);
    score.submitScoreWithCompletionHandler(new GPGScore.Block_submitScoreWithCompletionHandler() {
        @Override
        public void call_submitScoreWithCompletionHandler(GPGScoreReport gpgScoreReport, NSError nsError) {
            if (nsError != null) {
                Log.d("error on submitting high score");
            } else {
                Log.d("successfully submitted high score");
            }
        }
    });
}

Displaying a leaderboard

Displaying a leaderboard is very simple but important:

if (((GPGManager) GPGManager.sharedInstance()).isSignedIn()) {
    ((GPGLauncherController) GPGLauncherController.sharedInstance()).presentLeaderboardWithLeaderboardId("my_leaderboard_id");
}

Loading the player’s best score from a leaderboard

In order to load the player’s best score from a leaderboard (maybe because he or she removed the app) you nee the following code:

if (((GPGManager) GPGManager.sharedInstance()).isSignedIn()) {
    GPGLeaderboard gpgLeaderboard = GPGLeaderboard.leaderboardWithId(Leaderboard.Scores.getId());
    gpgLeaderboard.setSocial(false);
    gpgLeaderboard.loadScoresWithCompletionHandler(new GPGLeaderboard.Block_loadScoresWithCompletionHandler() {
        @Override
        public void call_loadScoresWithCompletionHandler(NSArray<?> nsArray, NSError nsError) {
            GPGLocalPlayerScore score = gpgLeaderboard.localPlayerScore();
            if (score != null) {
                long nScore = score.scoreValue();
                Log.d("Retrieved score: " + nScore);
            }
        }
    });
}

Leave a Reply

Your email address will not be published. Required fields are marked *

17 + fourteen =

This site uses Akismet to reduce spam. Learn how your comment data is processed.