In 11.6.0 of the Google Play Services SDK, we are introducing a major change to how the APIs are structured. We've deprecated the GoogleApiClient class, and introduced a decoupled collection of feature-specific API clients. This reduces the amount of boilerplate code required to access the Play Games Services APIs.
The change in the APIs is meant to make the APIs easier to use, thread-safe, and more memory efficient. The new API model also makes use of the Task model to give better separation of the concerns between your activity and handling the asynchronous results of the APIs. This programming model first appeared in Firebase and was well received. To dive in deeper into the wonderful world of Tasks, check out the blog series on tasks and the Tasks API developer guide.
Task
As always, the developer documentation is a reliable source of information on these APIs, as well as all the other Google resources for developers. The Android Basic Samples project, and Client Server Skeleton project have both been updated to use the Play Services API clients so you can see them in action. These sample projects are also the best place to add issues or problems you encounter using these APIs.
These changes seem big, but fear not! Using the Play Services API clients is very simple and will result in much less clutter in your code. There are three parts to using the API clients:
The details of the authentication process are found on the Google Developers website.
The most common use case for authentication is to use the DEFAULT_GAMES_SIGN_IN option. This option enables your game to use the games profile for the user. Since a user's games profile only contains a gamer tag that your game can display like a name, and an avatar for a image, the actual identity of the user is protected. This eliminates the need for the user to consent to sharing any additional personal information reducing the friction between the user and your game.
Note: The only Google sign-in option that can be requested which only uses games profile is requestServerAuthCode(). All others, such as requestIDToken() require the user to consent to additional information being shared. This also has the effect of preventing users from having a zero-tap sign-in experience.
One more note: if you are using the Snapshots API to save game data, you need to add the Drive.SCOPE_APPFOLDER scope when building the sign-in options:
private GoogleSignInClient signInClient; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // other code here GoogleSignInOptions signInOption = new GoogleSignInOptions.Builder( GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN) // If you are using Snapshots add the Drive scope. .requestScopes(Drive.SCOPE_APPFOLDER) // If you need a server side auth code, request it here. .requestServerAuthCode(webClientId) .build(); signInClient = GoogleSignIn.getClient(context, signInOption); }
Since there can only be one user account signed in at a time, it's good practice to attempt a silent sign-in when the activity is resuming. This will have the effect of automatically signing in the user if it is valid to do so. It will also update or invalidate the signed-in account if there have been any changes, such as the user signing out from another activity.
private void signInSilently() { GoogleSignInOptions signInOption = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN) .build(); GoogleSignInClient signInClient = GoogleSignIn.getClient(this, signInOption); signInClient.silentSignIn().addOnCompleteListener(this, new OnCompleteListener<GoogleSignInAccount>() { @Override public void onComplete(@NonNull Task<GoogleSignInAccount> task) { // Handle UI updates based on being signed in or not. enableUIButtons(task.isSuccessful()); // It is OK to cache the account for later use. mSignInAccount = task.getResult(); } }); }
@Override protected void onResume() { super.onResume(); signInSilently(); }
Signing in interactively is done by launching a separate intent. This is great! No more checking to see if errors have resolution and then trying to call the right APIs to resolve them. Just simply start the activity, and get the result in onActivityResult().
Intent intent = signInClient.getSignInIntent(); startActivityForResult(intent, RC_SIGN_IN);
@Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { super.onActivityResult(requestCode, resultCode, intent); if (requestCode == RC_SIGN_IN) { // The Task returned from this call is always completed, no need to attach // a listener. Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(intent); try { GoogleSignInAccount account = task.getResult(ApiException.class); // Signed in successfully, show authenticated UI. enableUIButtons(true); } catch (ApiException apiException) { // The ApiException status code indicates the // detailed failure reason. // Please refer to the GoogleSignInStatusCodes class reference // for more information. Log.w(TAG, "signInResult:failed code= " + apiException.getStatusCode()); new AlertDialog.Builder(MainActivity.this) .setMessage("Signin Failed") .setNeutralButton(android.R.string.ok, null) .show(); } } }
To determine if a user is signed in, you can call the GoogleSignIn.getLastSignedInAccount() method. This returns the GoogleSignInAccount for the user that is signed in, or null if no user is signed in.
if (GoogleSignIn.getLastSignedInAccount(/*context*/ this) != null) { // There is a user signed in, handle updating the UI. enableUIButtons(true); } else { // Not signed in; update the UI. enableUIButtons(false); }
Signing out is done by calling GoogleSignInClient.signOut(). There is no longer a Games specific sign-out.
signInClient.signOut().addOnCompleteListener(MainActivity.this, new OnCompleteListener<Void>() { @Override public void onComplete(@NonNull Task<Void> task) { enableUIButtons(false); } }); );
In previous versions of Play Games Services, the general pattern of calling an API was something like this:
PendingResult<Stats.LoadPlayerStatsResult> result = Games.Stats.loadPlayerStats( mGoogleApiClient, false /* forceReload */); result.setResultCallback(new ResultCallback<Stats.LoadPlayerStatsResult>() { public void onResult(Stats.LoadPlayerStatsResult result) { Status status = result.getStatus(); if (status.isSuccess()) { PlayerStats stats = result.getPlayerStats(); if (stats != null) { Log.d(TAG, "Player stats loaded"); if (stats.getDaysSinceLastPlayed() > 7) { Log.d(TAG, "It's been longer than a week"); } if (stats.getNumberOfSessions() > 1000) { Log.d(TAG, "Veteran player"); } if (stats.getChurnProbability() == 1) { Log.d(TAG, "Player is at high risk of churn"); } } } else { Log.d(TAG, "Failed to fetch Stats Data status: " + status.getStatusMessage()); } } });
The API was accessed from a static field on the Games class, the API returned a PendingResult, which you added a listener to in order to get the result.
Now things have changed slightly. There is a static method to get the API client from the Games class, and the Task class has replaced the PendingResult class.
As a result, the new code looks like this:
GoogleSignInAccount mSignInAccount = null; Games.getPlayerStatsClient(this, mSignInAccount).loadPlayerStats(true) .addOnCompleteListener( new OnCompleteListener<AnnotatedData<PlayerStats>>() { @Override public void onComplete(Task<AnnotatedData<PlayerStats>> task) { try { AnnotatedData<PlayerStats> statsData = task.getResult(ApiException.class); if (statsData.isStale()) { Log.d(TAG,"using cached data"); } PlayerStats stats = statsData.get(); if (stats != null) { Log.d(TAG, "Player stats loaded"); if (stats.getDaysSinceLastPlayed() > 7) { Log.d(TAG, "It's been longer than a week"); } if (stats.getNumberOfSessions() > 1000) { Log.d(TAG, "Veteran player"); } if (stats.getChurnProbability() == 1) { Log.d(TAG, "Player is at high risk of churn"); } } } catch (ApiException apiException) { int status = apiException.getStatusCode(); Log.d(TAG, "Failed to fetch Stats Data status: " + status + ": " + task.getException()); } } });
So, as you can see, the change is not too big, but you will gain all the goodness of the Task API, and not have to worry about the GoogleApiClient lifecycle management.
The pattern of changes is the same for all the APIs. If you need more information, you can consult the Developer website. For example if you used Games.Achievements, you now need to use Games.getAchievementClient().
The last major change to the Play Games Services APIs is the introduction of a new API class, GamesClient. This class handles support methods such as setGravityForPopups(), getSettingsIntent(), and also provides access to the multiplayer invitation object when your game is launched from a notification.
Previously the onConnected() method was called with a connection hint. This hint was a Bundle object that could contain the invitation that was passed to the activity when starting.
Now using the GamesClient API, if there is an invitation, your game should call signInSilently(); this call will succeed since the user is known from the invitation. Then retrieve the activation hint and process the invitation if present by calling GamesClient.getActivationHint():
Games.getGamesClient(MainActivity.this, mSignInAccount) .getActivationHint().addOnCompleteListener( new OnCompleteListener<Bundle>() { @Override public void onComplete(@NonNull Task<Bundle> task) { try { Bundle hint = task.getResult(ApiException.class); if (hint != null) { Invitation inv = hint.getParcelable(Multiplayer.EXTRA_INVITATION); if (inv != null && inv.getInvitationId() != null) { // retrieve and cache the invitation ID acceptInviteToRoom(inv.getInvitationId()); return; } } } catch (ApiException apiException) { Log.w(TAG, "getActivationHint failed: " + apiException.getMessage()); } } });
When a method call fails, the Task.isSuccessful() will be false and information about the failure is accessed by calling Task.getException(). In some cases the exception is simply a non-success return value from the API call. You can check for this by casting to an ApiException:
Task.isSuccessful()
Task.getException()
ApiException
if (task.getException() instanceof ApiException) { ApiException apiException = (ApiException) task.getException(); status = apiException.getStatusCode(); }
In other cases, a MatchApiException can be returned and contains updated match data structure. It can be retrieved in a similar manner:
if (task.getException() instanceof MatchApiException) { MatchApiException matchApiException = (MatchApiException) task.getException(); status = matchApiException.getStatusCode(); match = matchApiException.getMatch(); } else if (task.getException() instanceof ApiException) { ApiException apiException = (ApiException) task.getException(); status = apiException.getStatusCode(); }
If the status code is SIGN_IN_REQUIRED, this indicates that the player needs to be re-authenticated. To do this, call GoogleSignInClient.getSignInIntent() to sign in the player interactively.
The change from the GoogleApiClient usage to a more loosely coupled API clients usage will provide benefits of less boilerplate code, more clear usage patterns, and thread safety. As you migrate your current game to API clients, refer to these resources:
Sign-In Best practices for Games:
https://developers.google.com/games/services/checklist
Play Games Services Samples:
Android Basic Samples
Client Server Skeleton
StackOverflow:
https://stackoverflow.com/questions/tagged/google-play-games
Posted by Matteo Vallone, Google Play Partner Development Manager
To build awareness of the awesome innovation and art that indie game developers are bringing to users on Google Play, we have invested heavily over the past year in programs like Indie Corner, as well as events like the Google Play Indie Games Festivals in North America and Korea.
As part of that sustained effort, we also want to celebrate the passion and innovation of indie game developers with the introduction of the first-ever Google Play Indie Games Contest in Europe. The contest will recognize the best indie talent in several countries and offer prizes that will help you get your game noticed by industry experts and gamers worldwide.
Prizes for the finalists and winners:
Entering the contest:
If you're based in Czech Republic, Denmark, Finland, France (coming soon), Germany, Iceland, Israel, Netherlands, Norway, Poland (coming soon), Romania, Spain, Sweden, Turkey, or UK (excl. Northern Ireland), have 15 or less full time employees, and published a new game on Google Play after 1 January 2016, you may now be eligible to enter the contest. If you're planning on publishing a new game soon, you can also enter by submitting a private beta. Check out all the details in the terms and conditions. Submissions close on 31 December 2016.
The process:
Up to 20 finalists will get to showcase their games at an open event at the Saatchi Gallery in London on the 16th February 2017. At the event, the top 10 will be selected by the event attendees and the Google Play team. The top 10 will then get the opportunity to pitch to a jury of industry experts, from which the final winner and runners up will be selected.
Even if someone is NOT entering the contest:
Even if you're not eligible to enter the contest, you can still register to attend the final showcase event in London on 16 February 2017, check out some great indie games, and have fun with various industry experts and indie developers. We will also be hosting a workshop for all indie games developers from across EMEA in the new Google office in Kings Cross the next day, so this will be a packed week.
Get started:
Enter the Indie Games Contest now and visit the contest site to find out more about the contest, the event, and the workshop.
Originally posted on Android Developers blog
Posted by Morgan Dollard, Product Manager of Google Play Games
With mobile gamers across 190 countries, Google Play Games is made up of a vibrant and diverse gaming community. And these players are more engaged than ever. Over the past year, the number of games reaching over 1 million installs grew by 50 percent.
Today, at our annual Developer Day at the Game Developers Conference, we announced new platform and ads tools for developers, of all sizes reach, to reach this global audience and accelerate the growth of their games business. Check out below the full range of features that will help game developers build their apps, grow their users base, and earn more revenue.
In February, we introduced Gamer IDs so that anyone could create a gaming persona. We also simplified the sign-in process for Google Play Games so players could pick up playing their game more quickly. We’re also working on product enhancements to make Play Games a little more social and fun, which will mean more engaged players who’re playing your game for longer. One example is the launch of Gamer friends (coming soon!), where your players can add and interact with their friends from within the Google Play Games app (without needing a Google+ account).
We’re also launching the Indie Corner, a new collection on Google Play, that will highlight amazing games built by indie developers. You can nominate your awesome indie game for inclusion at g.co/indiecornersubmission. We’ll pick the best games to showcase based on the quality of the experience and exemplary use of Google Play game services.
In January, we added features to Player Analytics, the free reporting tool of Google Play game services, which helps you understand how players are progressing, spending and churning. Today, we previewed some upcoming new tools that would be available in the coming months, including:
Promoting your game and growing your audience is important, but it’s just as important to reach the right audience for your game, the players who want to open the game again and again. That’s why today we’ve unveiled new features that make it simpler to reach the right audience at scale.
AdMob helps game developers around the world maximize revenue through in-app advertising. At GDC, we also announced a new way to help you earn more through AdMob Mediation. Rewarded advertising is a popular form of game monetization -- users are given the choice to engage with ads in exchange for an in-app reward. AdMob Mediation will enable you to easily monetize your apps with rewarded video ads from a number of ad providers. Supported networks and platforms include AdColony, AppLovin, Chartboost, Fyber, Upsight and Vungle, with more being added all the time.
You can learn more about this, and all our ads announcements on the Inside AdWords blog.
This is just the start of what we’ve got planned for 2016. We hope you can make use of these tools to improve your game, engage your audience, and grow your business and revenue.
Posted by Stewart Miles, Fun Propulsion Labs*
To celebrate the holiday season at Fun Propulsion Labs, we're trading our sushi mats and baking pans for candy canes and snowballs. Please join us for a special holiday-themed version of Pie Noon and Zooshi! Zooshi and Pie Noon are open source, cross-platform games built from a suite of libraries that eager C++ developers can use to build their own projects.
You can download and run Zooshi's Santa mode on Google Play and find the latest open source release on our GitHub page. The holiday version of Pie Noon is available on Google Play as Snowdown in Santa Tracker and on our GitHub page. Happy Holidays!
* Fun Propulsion Labs is a team within Google that's dedicated to advancing gaming on Android and other platforms.
Posted by Alex Ames, Fun Propulsion Labs*
At Fun Propulsion Labs we spend some of our time building sample games to help demonstrate how to make easy-to-build, performant, cross-platform games. With the growth of Google Cardboard, we got to work and over many long evenings, feeding our animal hunger on sushi, we came up with Zooshi. Zooshi is an open source, cross-platform game written in C++ which supports:
Zooshi serves as a demonstration of how to build Android games using a suite of newly released and updated open source game technologies from Google:
As in our previous release, Pie Noon, we also made extensive use of Flatbuffers, Mathfu, fplutil, and WebP.
You can download the game in the Play Store and the latest open source release from our GitHub page. We invite you to learn from the code to see how you can apply these libraries and utilities in your own Android games. Take advantage of our discussion list if you have any questions, and don’t forget to toss some sushi around while you’re at it!
Posted by Anthony Maurice, Fun Propulsion Labs at Google
Fun Propulsion Labs at Google* is back with an exciting new release for game developers. We’ve updated Pie Noon (our open source Android game) to add support for Google Cardboard, letting you jump into the action directly using your Android phone as a virtual reality headset! Select your targets by looking at them and throw pies with a flick of the switch.
We used the Cardboard SDK for Android, which helps simplify common virtual reality tasks like head tracking, rendering for Cardboard, and handling specialized input events. And you might remember us from before, bringing exciting game technologies like FlatBuffers, Pindrop, and Motive, all of which you can see in use in Pie Noon.
You can grab the latest version of Pie Noon on Google Play to try it out, or crack open the source code, and take a look at how we brought an existing game into virtual reality.
Posted by Jon Simantov, Fun Propulsion Labs at Google
Originally posted to the Google Open Source blog
Fun Propulsion Labs at Google* is back today with some new releases for game developers. We’ve updated Pie Noon (our open source Android TV game) with networked multi-screen action, and we’ve also added some delicious new libraries we’ve been baking since the original release: the Pindrop audio library and the Motive animation system.
Got an Android TV and up to 4 friends with Android phones or tablets? You’re ready for some strategic multi-player mayhem in this updated game mode. Plan your next move in secret on your Android phone: will you throw at an opponent, block an incoming attack, or take the risky approach and wait for a larger pie? Choose your target and action, then watch the Android TV to see what happens!
We used the NearbyConnections API from the most recent version of Google Play Games services to easily connect smartphones to your Android TV and turn our original Pie Noon party game into a game of turn-based strategy. You can grab the latest version of Pie Noon from Google Play to try it out, or crack open the source code and take a look at how we used FlatBuffers to encode data across the network in a fast, portable, bandwidth-efficient way.
Pindrop is a cross-platform C++ library for managing your in-game audio. It supports cross compilation to Android, Linux, iOS and OSX. An early version of this code was part of the first Pie Noon release, but it’s now available as a separate library that you can use in your own games. Pindrop handles loading and unloading sound banks, tracking sound locations and listeners, prioritization of your audio channels, and more.
Pindrop is built on top of several other pieces of open source technology:
You can download the latest open source release from our GitHub page. Documentation is available here and a sample project is included in the source tree. Please feel free to post any questions in our discussion list.
The Motive animation system can breathe life into your static scenes. It does this by applying motion to simple variables. For example, if you’d like a flashlight to shine on a constantly-moving target, Motive can animate the flashlight so that it moves smoothly yet responsively.
Motive animates both spline-based motion and procedural motion. These types of motion are not technically difficult, but they are artistically subtle. It's easy to get the math wrong. It's easy to end up with something that moves as required but doesn't quite feel right. Motive does the math and lets you focus on the feeling.
Motive is scalable. It's designed to be extremely fast. It also has a tight memory footprint -- smaller than traditional animation compression -- that's based on Dual Cubic Splines. Our hope is that you might consider using Motive as a high-performance back-end to your existing full-featured animation systems.
This initial release of Motive is feature-light since we focused our early efforts on doing something simple very quickly. We support procedural and spline-based animation, but we don't yet support data export from animation packages like Blender or Maya. Motive 1.0 is suitable for props -- trees, cameras, extremities -- but not fully rigged character models. Like all FPL technologies, Motive is open source and cross-platform. Please check out the discussion list, too.
You might remember us from such Android games as Pie Noon, LiquidFun Paint, and VoltAir, and such cross-platform libraries as MathFu, LiquidFun, and FlatBuffers.
Want to learn more about our team? Check out this recent episode of Game On! with Todd Kerpelman for the scoop!
Posted by Wouter van Oortmerssen, Fun Propulsion Labs at Google*
After months in development, the FlatBuffers 1.1 update is here. Originally released in June 2014, it’s a highly efficient open source cross-platform serialization library that allows you to read data without parsing/unpacking or allocating additional memory. It supports schema evolution (forwards/backwards compatibility) and optional JSON conversion. We primarily created it for games written in C++ where performance is critical, but it’s also useful more broadly. This update brings:
Download the latest release from our github page and join our discussion list for more details.
*Fun Propulsion Labs is a team within Google that's dedicated to advancing gaming on Android and other platforms.
<target action="addTarget"> <itemPath>assets/images/blank.png</itemPath> <longitude>-157.8459651634087</longitude> <latitude>21.31249095467307</latitude> <imageRadius>.0000018</imageRadius> <targetRadius>20</targetRadius></target>
drawImage()
// Each animation frame is 16x16 pixelsvar sheet = new ig.AnimationSheet( 'player.png', 16, 16 );// This creates the "run" animation: it has 6 frames (the 2nd row // in the image), with each one being shown for 0.07 secondsvar run = new ig.Animation( sheet, 0.07, [6,7,8,9,10,11] );
// Create a 2D tilemapvar map = [ [5, 3, 4], [2, 7, 1], [6, 0, 3]];// Specify a layer with a tilesize of 16px and our tilemapvar layer = new ig.BackgroundMap( 16, map, 'tileset.png' );layer.draw();