Sunday, March 18, 2018

Diamond Seeker


Diamond Seeker is a game based on match 3 games, but instead of matching 3 pieces by swapping them, you have to select pieces of the same type, vertically of horizontally. You can watch a video here.

The Game
As I said, here you have to select pieces of the same type, but you are allowed to use boosters as well. There are five game modes:
  • Time: Here you have to do your best in 60 seconds;
  • Moves: Don't worry about time, you have 30 moves;
  • Light: The more gems you get, the more the board will be lit;
  • Sequence: Make the sequence shown above the board;
  • Endless: Just relax. Don't worry about time, moves or light.
There are five type of boosters:
  • Time: With this booster you can get five more seconds during gameplay;
  • Moves: When you start a new game, there are 30 moves, however, if you need more, you can get five extra moves with this booster;
  • Light: When you are in light mode you might need an extra light. This booster increases the percentage of light by 15;
  • Sparkler: This booster you can use in any mode, it removes all the same type of gems that you select;
  • Bomb: Like the previous one, you are allowed to use in any mode, but it removes just a gem from the board.  
The game has six different themes: Classic, Modern, Nature, Dark, Vortex and Landscape. As you can see in the image below, the image shows the game using Classic theme.



 Now, the Vortex theme:




That's not all! There are almost 50 sounds to you to choose (Jukebox, see images below). You can choose the sound of selection, sparkler and explosion and four leaderboards to keep up with your progress among the other players.



You can get more boosters by buying them using the accumulated points, nevertheless, one way to make points faster it's buying the Gem Doubler. In other words, if your final score is 100, with Gem Doubler activated is 200. 

This game uses the LibGDX framework and some of the sounds and sprites I got from websites. If you are interested you can download the game and check them on Credits. :)














Relax 'n Jump







Relax 'n Jump is a game based on the old ones from Atari. There's no rule at all, except one: Jump! You can watch a video here.

1 - Goal 
The objective is going up as long as you can, jumping on the platfoms and avoiding obstacles that each time shows up in different positions. And along the way you can collect some coins to booster your final score.

2 - How to Play 
The screen is vertically divided in three equal parts: left, middle and right. If you touch the right, the zombie goes to the right (left is similar) and if you touch on the middle, the zombie jumps. Just that! With these three different actions you are capable of playing it. It's endless.

3 - The Game 
On the menu game you can select the character you want to play with: a zombie girl or a zombie boy, check the leaderboard, sound, music, log into your Google Play Games account and rate the game, as well.



Each time you start a new game the controls are shown:




The amount of coins varies depending on how many obstacles you were able to pass. An image of the game with some coins on the screen can be viewed below:



4 - How It Was Made
In this game I have used the framework LibGDX with Box2D engine for physics and the most of resources (sprites, background, etc) I got from websites. You can download the game here and check them on Credits. :)
 



Saturday, March 17, 2018

Game Services + Firebase in LibGDX using Android Studio

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