Logger Bill - A Tour of the Tree (Part 2 - Support Classes)
Troy and I have tried to make as much of the code base generic as possible in order to re-use it in subsequent games.
This is evident in the design of the managers and other "support" classes and interfaces:
- ProfileManager
- AchievementManager
- LeaderboardManager
- Achievement
- Leaderboard
- Ads
- CloudSave
- GPG
- Tweeter
- Emailer
If you havn't read Part 1 of the "A tour of the Tree" seriese click here.
Achievements and Leaderboards
By designing the Achievements and Leaderboards data to be loaded from json files we can keep these generic.
The Achievement base class does all of the work. The LoggerBillAchievement specific class is only loaded because it is the class named in the achievements.json file. It is the only part specific to the Logger Bill game.
This does however raise two issues with reflection:
- We have to specifically add the class to GWT:
<extend-configuration-property name="gdx.reflect.include" value="com.maplescot.loggerbill.gpg.Achievement"/>
<extend-configuration-property name="gdx.reflect.include"
value="com.maplescot.loggerbill.gpg.LoggerBillAchievement"/> - We have to add an entry to proguard:
-keep class com.maplescot.**Achievement* {*;}
For the leaderboards all of the game specific knowledge is in the Profile class.
Profiles
While the ProfileManager class is generic the Profile class very much is not.
It is very game specific since the majority of data stored in the class is specific to this game.
Logger Bill is a pretty simplistic game hence we only store a small number of statistics:
- Total Plays
- Total Chunks
- Total Time
- Best Chunks
- Best CPS
For our previous game The Shaft we kept track of 9 different statistics and used them to test against 17 achievements and 9 leaderboards.
Please don't underestimate the number of stats you will need to track if you want to include these features.
While the ProfileManager handles the loading and saving of the profile it is the profile itself that knows how to store in to local storage and how to serialise itself for remote storage.
Cloud Storage
This was fantastic to get working and I wish we had done it for The Shaft.
Starting the game on my tablet and seeing that my scores are there from my phone and vice versa is great.
You do have to think carefully about how to manage conflicts. I went with a very simple approach but you could be much cleverer and merge results from different devices.
Profile Checksums
People hack their profiles to get the high scores on leaderboards. It's dumb but they do it anyway.
So I included a small checksum value in the profile and only load it if it is correct. Maybe I am just anal and I should let the kids have their fun but I prefer leaderboards that show actual effort.
Resolvers
Most of the examples use a single Resolver for doing platform specific calls but there is no reason to limit yourself to one. I created several for both flexibility and so that I could have default implementations in core for backends that didn't need to support that particular interface.
GPG
While the class is named GPG for Google Play Games it is really a generic (and slightly badly named) class for posting achievements and scores to an online system. It will work equally well with GameCircle (more on that in another article) and probably with Facebook too.
Since most of the work is done in core this really just calls through to the native interface. The hardest part about Google Play Games Services is navigating the Play Developer Console. It can take a bit of trial and effort to get a game fully defined. The one thing I will say is don't publish your game services definitions until you are ready to go in to production. Once published you cannot reset achievements and leaderboards.
Ads
The Ads class is also very simplistic. In this case most of the hard work is done at creation, setting up the ads and loading the initial ad. Interstitials can be tricky as you don't want to annoy people too much. We found the best way was to ignore the callback and load an ad at start and then attempt to show it when we want to show an interstitial and start loading the next one. If there was one to show then nothing happens.
For Logger Bill we went with showing an interstitial every 5 plays. Any more and its way too annoying.
Again while the interface was originally written with AdMob in mind it does also work with Amazon Ads and should work fairly well with most other Ad apis.
Cloud Save
Getting cloud save working was fantastic and I wish we had done this on The Shaft as well. Playing the game across multiple devices and knowing that your saved profile will update is great.
The interface for this is a little odd because of the way the google cloud save works. You have to register a callback which gets passed the cloud saved data once it gets downloaded. I decided to make the ProfileManager handle that but to delegate handling conflicts off to the Profile class. I went with a very simple conflict resolution scheme of whichever profile has the most plays wins. But you could easily do something more complicated and merge your data properly taking the best value from each one.
Other cloud saving apis don't work in exactly the same way but this interface was pretty easy to get working with amazon whispersync as well.
Tweeter
Now you may be wondering why I bothered having a resolver for this since the default implementation actually works fine. The simple answer is that I wanted something that on Android devices would bring up the proper twitter client without popping a chooser dialog.
I did this for the shaft and it is pretty easy to do and gives a slicker feel.
Emailer
And lastlly the Emailer. Again there is a default interface but for Android this wont work with email apps. Android requires an intent of a specific type to provide you with a choice of email apps.
Hits: 5607