Cross Platform Leaderboard using Firebase Database
Copyright (c) 2018 Google Inc. All rights reserved.
This solution makes it easy to incorporate a cross-platform leaderboard into your Unity® projects. You can follow the steps here to add a LeaderboardController to your own project, or take a look at the Demo Scene to see one in action right away!
- [Overview of Scripts](#Overview of Scripts)
- What's Next
- A Unity® project with at least full .NET 2.0 Api level enabled. To enable .NET 2.0, go to
Edit -> Project Settings -> Player -> Other Settings and change
Api Compatibility Levelto `.NET 2.0.
- A Firebase project. You can create a new Firebase project using the firebase console.
- Follow the Setup steps for using Firebase Database with Unity®.
- Set up public access to your database. Note that this leaves your database open to the public, so you will want to configure rules before releasing your game.
Optional: Setup editor restricted access
If you choose to use rules that disallow public access, you will need to configure the SDK to use a service account to run in the Unity® Editor.
- Copy the rule text found in firebase-db-rules.txt.
- Go to the firebase console, select your project, choose
Databaseand click on the
Rulestab. Paste the contents of
Overview of Scripts
To understand how you might customize these scripts for use in your project, start by exploring each script implemented in the Firebase_Leaderboard>Scripts directory, Firebase_Leaderboard>Demo and Mechahamster. Feel free to read through the code in Unity as you orient yourself to these scripts.If you update the LeaderBoardController script for your game, you will need to update this script with any new information.
|Script Name||Script Purpose|
|Assets>firebase-unity-solutions>Firebase_Leaderboard>Scripts>FirebaseInitializer||This is a centralized script for initializing FireBase in your project.
You should not need to modify this script to customize for your game.
|Assets>firebase-unity-solutions>Firebase_Leaderboard>Scripts>LeaderboardController||This script creates the Firebase.Leaderboard namespace, which is called by many of the other scripts. It contains the primary logic for the leaderboard and handles keeping the data from Firebase and Unity Game code synchronized by sending events to Firebase and triggering updates in the game when receiving new data.|
|Assets>firebase-unity-solutions>Firebase_Leaderboard>Scripts>TopScoreArgs||Defines the event arguments which are sent when LeaderboardController invokes TopScoresUpdated.|
|Assets>firebase-unity-solutions>Firebase_Leaderboard>Scripts>UserScoreArgs||Defines the event arguments which are sent when LeaderboardController invokes UserScoreUpdated.|
|Assets>firebase-unity-solutions>Firebase_Leaderboard>Scripts>UserScore||Represents a single user score record kept in FirebaseDatabase. By default a user score contains a timestamp, score value, and the User's unique ID. You may modify this class to add fields, but if you remove or change any of the three default fields, you will need to update the logic in LeaderboardController to match.|
|Assets>firebase-unity-solutions>Firebase_Leaderboard>Editor>LeaderboardControllerEditor||Creates the LeaderBoard interface, allowing the user to set certain variables and paths in the Unity Inspector window.
|Assets>firebase-unity-solutions>Firebase_Leaderboard>Demo>DemoUIController||In the Firebase_Leaderboard DemoScene, this script controls the Demo Interface.|
|Assets>Hamster>Scripts>States>UploadTime||In Mechahamster, this script creates a class called UploadTime, which manages the transfer of data to Firebase when the user selects the Submit! button at the end of a maze and logs their score.|
|Assets>Hamster>Scripts>States>TopTimes||In Mechahamster, this script creates a class called TopTime, which manages the top finish times for a level.|
|Assets>Hamster>Scripts>Menus>TopTimesGUI||In Mechahamster, this script creates an Interface class for providing code access to the GUI elements in the high score menu.|
|Assets>Hamster>Scripts>States>LevelFinished||In Mechahamster, this script creates a class called LevelFinished, which manages the logic driving the Level Finished menu page.|
|Assets>Hamster>Scripts>Menus>LevelFinishedGUI||In Mechahamster, this script creates an Interface class for providing code access to the GUI elements in the Level Finished menu.|
Setting up the LeaderboardController
- If you haven't already, copy all the
.cssource files from Scripts to your project.
- Add the
LeaderboardControllerMonoBehaviour to a GameObject in your scene.
- It is advisable to disable the GameObject with the LeaderboardController component attached when the player is not viewing the Leaderboard, as leaving it enabled will cause the LeaderboardController to continue to listen for new score records being added to the DB.
- Fill out the fields on the controller as appropriate.
EditorAuth: Enable this and fill out the following optional parameters if you have set up restricted editor authentication for your project.
EditorP12FileName: The location of the P12 key file downloaded when restricted access was set up.
EditorServiceAccountEmail: The service account that has access to your database. This can found on the cloud console's IAM control.
EditorP12Password: The password generated for restricted access.
AllScoreDataPath: If you wish to use a different path to keep all the user score data (for example, if you are already using Firebase DB for other data and want to use a longer path), you can change that here.
- Note that if you do, you will also need to update the path in the rules text if you set up editor restricted access.
Using LeaderboardController EventHandlers
Because Firebase Database calls are asynchronous, the functions that retrieve and add data to the
DB do not return their results directly. Instead, you subscribe to
EventHandlers that trigger
when a result is completed.
There are three events that fire when the LeaderboardController completes various actions.
ScoreAdded: Called when a new user score is saved to the DB.
UserScoreUpdated: Called when the current user's top score is retrieved.
TopScoresUpdated: Called when a set of all users' top scores is retrieved.
Retrieving top scores is as simple as specifying the
Interval fields on the LeaderboardController. When the component is enabled, and Firebase is
initialized, it will automatically retrieve the top scores from the requested time frame. If
EndTime is the present (0), it will also listen for any new scores that would displace any of the
current top scores, and send TopScoresUpdated events accordingly.
For example, if you retrieved the top 5 scores, and the score values are 15, 20, 22, 24, and 31, then the LeaderboardController sends 1 TopScoresUpdated event with those scores when it is enabled. Then, if a new score that is > 15 is added while the component is enabled, it will send a new TopScoresUpdated event with the updated list.
If you want to retrieve the top score for a particular user, call
GetUserScore and provide the
user's unique ID. When the asynchronous call is complete, the
UserScoreUpdated event is invoked.
Adding a new user score
To add a new user score, simply call
AddScore with the following fields:
userId: The unique ID of the user for whom to add a score, based on whatever authentication method (Firebase Auth or otherwise) you prefer.
score: The new score (as an integer).
timestamp(default now): The time the score was achieved.
Security and Authorization
The rules provided in firebase-db-rules.txt are pretty limited, and don't
offer any validation that user's are not manipulating other scores in
all_scores data. A
malicious user could potentially fake their own high score, or even delete other user's scores!
With some build platforms, this may not be a danger, as the code to find the database URL may be obfuscated or hidden. However, you will likely want to protect this data with more limited write rules, limiting what data a user can manipulate via authentication.
The easiest way to do this is to incorporate Firebase Authentication, as
Firebase Database has the built-in
auth variable that contains a user's uid. If you
do use Firebase Authentication, you can replace the rules text for your database with the text
in firebase-db-auth-rules.txt. This limits new score data writes to
user_id attribute matches the
auth.uid. Remember that you must pass the
Firebase Auth UID to the
AddScore as the user's ID, or the writes will fail!
Using Firebase Auth to restrict write access to user's own records will prevent users from being deleting or manipulating other users' records, but it won't stop a malicious user from manipulating their own score data. Again, the likelihood of this happening depends on the build platform.
One common method of securing against this vulnerability is to obfuscate the score in some way, upload the obfuscated value to the Firebase Database, and then have a server-side or serverless process that you control read and respond to that data to calculate the score.
This could be as simple as hashing the score value and de-hashing via a Cloud Function, which then updates the Firebase DB score value for the user.
Or, instead of uploading the score value to the DB, you could instead upload some data about the final game state, and then have a server-side process read that state, compute a score from it, and update the score value in kind.