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.
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:
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:
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!