1 - Introduction
In this tutorial I'm going to show how to implement Google Play Game Services with Firebase using LibGDX. Last year some changes has come and there aren't tutorials about that. If you have been using LibGDX in the last two years or making apps you can go through these changes easily, but if not, you might think a bit confusing. With Firebase (replace Google Analytics) you can keep track of game data like sessions, logs and much more of your game. Although Google Analytics still works, it's just for web tracking. You could do a hack and use it for game, however, it's not recommended. Another change is how to access the Game Services. It was used to add BaseGameUtils library to the project and, with that, we could access the Game Services.
I have already made two games: Relax 'n Jump and Diamond Seeker.
2 - Checking some things first
Before starting, make sure you have installed on your Android Studio these SDK packages:
3 - Getting APP ID from Game Services
First, let's create a game service. Go to Google Play Console and in Game Services click on "Add New Game". Choose "I don't use any Google APIs in my game yet", give it a name, pick a category and click on "Continue". Now, you should have something like the image below:
The number below the name of Game Services is the APP ID you should put in your project. Now, let's talk about what are these settings:
- Game details: Here you just give it a name, description, category and graphic assets;
- Linked apps: We will come back here later;
- Events: it's not mandatory, leave the way it is;
- Achievements: According to Google and other tutorials, you should have at least five achievements in any game. Although, if you're not gonna use them in game, just leave them unimplemented;
- Leaderboards: It's not mandatory but let's create one;
- Testing: Here you have to add tester accounts to test the game service without publishing it, because, after publishing it you can not delete it. Make sure you have clicked on the button to enable alpha test;
- Publishing: Wait until the game is complete. As I already said, you can not delete a game service after publishing it. Google says you must publish the game service a few hours before publishing the game itself;
Now, inside android project, go to res\values and create a xml file, call it ids. It will be empty, so add the following code:
1 2 3 4 | <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_id">234563874525</string> </resources> |
Replace the id number by the yours.
4 - Updating Gradle File (Project Level)
We are going to add some dependencies to our project, in other words, tell it what has to be compiled in gradle.
In our build.gradle (project level) file, add these lines:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | allprojects { ... repositories { ... maven { url "https://maven.google.com" } } } project(":android") { ... dependencies { ... compile "com.google.android.gms:play-services:11.8.0" compile "com.google.android.gms:play-services-auth:11.8.0" ... } } |
5 - Updating Android Manifest file
In order to access the internet and game services, we must enabled that in Android Manifest file with the following lines:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" ... <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> ... <application ... <meta-data android:name="com.google.android.gms.games.APP_ID" android:value="@string/app_id" /> <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version"/> ... </application> </manifest> |
6 - Linking the Game to the Google Play Game Services
Before we continue, you should get the SHA1 fingerprint. To do that, follow these steps.
Go to your Google Play Console and click on Linked apps, choose Android and a dialog box should appear where you have to put your SHA1 (release).
Now it's time to add Firebase to our Game Services: go to Google Play Console and in Game Services of your game click on "ADD FIREBASE".
7 - Adding Firebase to the project in Android Studio
To avoid future errors we should set the min API to 19 in Android Studio (File\Project Structure\Android Module\Flavors) and in Android Manifest file.
In order to use Firebase in the project we have to add its dependencies. We are going to do that by using the Firebase Assistant. In Android Studio go to Tools and click on Firebase to open it. We are interested in the first one from the list: Analytics. Click on "Log an Analytic event", connect to Firebase and a window will open. Check the box "Choose an existing Firebase or Google project" and select the one already created from Game Services. Click on "Connect to Firebase" to close the window. Now, click on "Add Analytics to your app" and accept the changes.
Probably you will get the following error:
"Gradle sync failed: Could not find method
implementation()"
To fix that, open build.gradle(android) file and in dependencies, change the word "implementation" by "compile".
Note that is added "google-services.json" file inside android folder. This file contains the OAuth Client ID to authenticate the user. While testing, everything works just fine, but after publishing it for production, the players won't be able to log into their accounts on Google Play Games, because when Firebase Assistant added the "google-services.json" file it used the SHA1 for debug. So to fix it, go to Firebase Console, click on your project and a overview window will open. Click on three dots menu\settings. At the bottom, you should see the debug SHA1 fingerprint that was added. Delete it and add the SHA1 for release.
Now, go to the top, download the new "google-services.json" file to replace the one added by Firebase. If you open it you will see that OAuth Client ID (client_type 1) is the same from "Linked apps".
8 - Updating Gradle File (Android Level)
Open your build.gradle (android level) copy the following lines:
Now, go to the top, download the new "google-services.json" file to replace the one added by Firebase. If you open it you will see that OAuth Client ID (client_type 1) is the same from "Linked apps".
8 - Updating Gradle File (Android Level)
Open your build.gradle (android level) copy the following lines:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | android { ... defaultConfig { ... multiDexEnabled true } ... dexOptions { preDexLibraries = false javaMaxHeapSize "4g" } } dependencies { ... compile 'com.android.support:multidex:1.0.1' } |
9 - Implementation
Remember that actions to log in, submit score to leaderboard, unlock achievements take place in Android Launcher class. As our game logic is inside core project, we need to create an interface to access the methods in Android Launcher class. So create an interface inside core project and add the following lines:
1 2 3 4 5 6 7 8 9 | public interface PlayServices { public void onSignInButtonClicked(); public void onSignOutButtonClicked(); public boolean isSignedIn(); public void signInSilently(); public void submitScore(String leaderboardId, int highScore); public void showLeaderboard(String leaderboardId); public void setTrackerScreenName(String screenName); } |
Now, we can implement this interface:
1 | public class AndroidLauncher extends AndroidApplication implements PlayServices { |
In order to be able to sign in and access the leardboard we have to declare your related clients. Next, I'm going to show the final Android Launcher class and I won't explain line by line, because it's already well documented here and here.
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 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 | public class AndroidLauncher extends AndroidApplication implements PlayServices { // Client used to sign in with Google APIs private GoogleSignInClient mGoogleSignInClient; private GoogleSignInAccount mGoogleSignInAccount; // Client variables private LeaderboardsClient mLeaderboardsClient; private PlayersClient mPlayersClient; // request codes we use when invoking an external activity private static final int RC_UNUSED = 5001; private static final int RC_SIGN_IN = 9001; private static final int RC_LEADERBOARD_UI = 9004; // tag for debug logging private static final String TAG = "Firebase Test"; // Firebase Analytics private FirebaseAnalytics mFirebaseAnalytics; @Override protected void onCreate (Bundle savedInstanceState) { super.onCreate(savedInstanceState); AndroidApplicationConfiguration config = new AndroidApplicationConfiguration(); config.useImmersiveMode = true; initialize(new FirebaseTest06(this), config); // Create the client used to sign in to Google services. mGoogleSignInClient = GoogleSignIn.getClient(this, new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN).build()); mFirebaseAnalytics = FirebaseAnalytics.getInstance(this); } private void startSignInIntent() { Log.d(TAG, "startSignInIntent"); startActivityForResult(mGoogleSignInClient.getSignInIntent(), RC_SIGN_IN); } private void signOut() { Log.d(TAG, "signOut()"); if (!isSignedIn()) { Log.w(TAG, "signOut() called, but was not signed in!"); return; } mGoogleSignInClient.signOut().addOnCompleteListener(this, new OnCompleteListener<Void>() { @Override public void onComplete(@NonNull Task<Void> task) { boolean successful = task.isSuccessful(); Log.d(TAG, "signOut(): " + (successful ? "success" : "failed")); onDisconnected(); } }); } @Override public void signInSilently() { Log.d(TAG, "signInSilently()"); mGoogleSignInClient.silentSignIn().addOnCompleteListener(this, new OnCompleteListener<GoogleSignInAccount>() { @Override public void onComplete(@NonNull Task<GoogleSignInAccount> task) { if (task.isSuccessful()) { Log.d(TAG, "signInSilently(): success"); onConnected(task.getResult()); } else { Log.d(TAG, "signInSilently(): failure", task.getException()); onDisconnected(); } } }); } public boolean isSignedIn() { Log.d(TAG, "isSignedIn= " + (GoogleSignIn.getLastSignedInAccount(this) != null)); return GoogleSignIn.getLastSignedInAccount(this) != null; } @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { super.onActivityResult(requestCode, resultCode, intent); if (requestCode == RC_SIGN_IN) { Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(intent); try { GoogleSignInAccount account = task.getResult(ApiException.class); onConnected(account); } catch (ApiException apiException) { String message = apiException.getMessage(); if (message == null || message.isEmpty()) { message = getString(R.string.signin_other_error); } onDisconnected(); new AlertDialog.Builder(this) .setMessage(message) .setNeutralButton(android.R.string.ok, null) .show(); } } } private void onConnected(GoogleSignInAccount googleSignInAccount) { Log.d(TAG, "onConnected(): connected to Google APIs"); mGoogleSignInAccount = googleSignInAccount; mLeaderboardsClient = Games.getLeaderboardsClient(this, googleSignInAccount); mPlayersClient = Games.getPlayersClient(this, googleSignInAccount); // Set the greeting appropriately on main menu mPlayersClient.getCurrentPlayer() .addOnCompleteListener(new OnCompleteListener<Player>() { @Override public void onComplete(@NonNull Task<Player> task) { String displayName; if (task.isSuccessful()) { displayName = task.getResult().getDisplayName(); } else { Exception e = task.getException(); handleException(e, getString(R.string.players_exception)); displayName = "???"; } GamesClient gamesClient = Games.getGamesClient(AndroidLauncher.this, mGoogleSignInAccount); //setGravityForPopups(Gravity.TOP | Gravity.CENTER_HORIZONTAL); gamesClient.setGravityForPopups(Gravity.TOP | Gravity.CENTER_HORIZONTAL); gamesClient.setViewForPopups(((AndroidGraphics) AndroidLauncher.this.getGraphics()).getView()); } }); } private void onDisconnected() { Log.d(TAG, "onDisconnected()"); mLeaderboardsClient = null; mPlayersClient = null; } private void handleException(Exception e, String details) { int status = 0; if (e instanceof ApiException) { ApiException apiException = (ApiException) e; status = apiException.getStatusCode(); } } @Override public void onSignInButtonClicked() { startSignInIntent(); } @Override public void onSignOutButtonClicked() { signOut(); } @Override public void submitScore(String leaderboardId, int highScore) { Log.d(TAG, "leaderboardId IN= " + leaderboardId); if (isSignedIn()) { Log.d(TAG, "leaderboardId OUT= " + leaderboardId); mLeaderboardsClient.submitScore(leaderboardId, highScore); } } @Override public void showLeaderboard(String leaderboardId) { Log.d(TAG, "showLeaderboard IN= " + leaderboardId); if (isSignedIn()) { Log.d(TAG, "showLeaderboard OUT= " + leaderboardId); Games.getLeaderboardsClient(this, GoogleSignIn.getLastSignedInAccount(this)) .getLeaderboardIntent(leaderboardId) .addOnSuccessListener(new OnSuccessListener<Intent>() { @Override public void onSuccess(Intent intent) { startActivityForResult(intent, RC_LEADERBOARD_UI); } }); } } @Override public void setTrackerScreenName(String screenName) { mFirebaseAnalytics.setCurrentScreen(this, screenName, null); } @Override protected void onResume() { super.onResume(); Log.d(TAG, "onResume()"); // Since the state of the signed in user can change when the activity is not active // it is recommended to try and sign in silently from when the app resumes. signInSilently(); } } |
As you can see it, in core project main class, we have to create a constructor that receives as parameter a PlayServices object so we can access the methods inside Android Launcher.
1 2 3 4 5 6 7 8 9 | public class FirebaseTest extends Game { ... public PlayServices playServices; public FirebaseTest(PlayServices playServices) { this.playServices = playServices; } ... } |
That's it! Now, you can track screens, submit leaderboard score, etc.
To sign in, the logic has to be like that: if the user signs in for the first time, you should use "onSignInButtonClicked()", but if he have already signed in, you have to use "signInSilently()".
I hope this can help you.
Good luck!
ReplyDeleteI would like to share your article with my friends and colleagues
German Classes in Chennai
android Training in Chennai
Java Training in Chennai
CCNA Training in Chennai
ccna Training institute in Chennai
ccna institute in Chennai