After being exposed to the Google Firebase (Fb) service, I wanted to explore making a framework that would allow the Fb and Fb functions server act as a low bandwidth semi-authoritative game server for many (100+) simultaneous online players. This is what I created and learned along the way. This framework uses the Master Collection Firebase C3 Runtime plugin to access Fb from the client (The Master Collection also has the Playfab/Photon plugin which is more purpose-built for multiplayer.)
My main focus was on the framework, not the client gameplay experience, so the gameplay is a very simple two-team clickfest. However, in the future, more interesting gameplay could make a simple MMO game.
The overall concept is that the Fb server has the common game state (in this case ’nodes’ with just the property of team ownership (Team Blue, Team Yellow, Pink (neutral)). The goal of the game is to ‘own’ the majority of nodes on each victory condition check, once per minute, if a team owns the majority of nodes, their score increases. The server also has other state: info on per user login (browser details), last user logged in, the global scoreboard (Yellow Score, Blue Score) and when is the next victory condition check.
Game Example: https://kindeyegames.com/mnt
The victory condition check is implemented as a Fb function, so it’s semi- server authoritative. The clients are not involved in the check and changing the score, this code is run on the Google Cloud Platform (GCP). The Fb function is triggered by a https call. In this case, the call is done once per minute via an external cron job service (cron-job.org), because Fb itself does not have a timer function. In this case, the simple victory condition check is whether one team owns 2/3 of the nodes. If so, add one to their score in the database and clear the nodes back to neutral. If no team has one, set winner as neutral (draw), leave the board, do not update the score. Also, send when the next time check will be done (which again is rough, not exact per client, so when the time gets close, the client change the UI to show the Victory condition check is ‘soon.’
https://cron-job.org
Framework:
The Fb function also updates the score database which triggers the score listeners on each client. They can use this data to update the score and show which team won the round.
firebase.google.com/docs/functions/get-started
The diagram shows the general flow during gameplay: user clicks on a node that is not their team, an update database request is made to the server to update the node to be owned by the client's team. This triggers the database to send an update to all other clients which have registered listeners to the node state database. All the other connected clients get the single node update and they update their internal game state and sprite graphics to indicated the new owner of the node.
The construct 3 client handles all the UI, showing score updates, player input, game sprite updates (on changing team) and Round end UI (who won, etc.) The MC Fb plugins handle all the database updates and loading (using ‘ItemTable’), the Fb authorization (using anonymous in this case, you can do other logins, like password, key, google login, etc.) The plugin also handles registering the listener function (so the web application gets an update when a certain area of the database change, for example, node state/ownership or the score.) Most of the responses are in JSON format, so the new C3 JSON plugin is useful to parse the Firebase messages from the server.
One idea around this type of setup is the loose synchronization of game state, for this clickfest, it does not really matter what is the location/behavior of the nodes on each client, they can be different. What is important is their state being synced. So it’s not required to send X,Y data of the nodes as they move around in the game. In general the players may think it’s coordinated, but in this case, it doesn’t really matter. It would be simple and low game state bandwidth to add ‘fixed X,Y’ positions (e.g. a grid for the nodes in this clickfest was implemented earlier.)
Bandwidth and $
The Firebase service mainly charges based on bandwidth used (until you get many 1000s of connections, which would probably break this style of game.) To reduce BW usage, the full game state is only sent on init and a round win condition. The rest of the time, only per-node state update is sent on game change. However, this is sent to every client attached. It’s good to reduce the BW of the data sent as much as possible, but GCP also charges for the TLS, etc. overhead, so even small updates can really add up. Lower update rate games would definitely be less expensive (e.g. semi-turn based, or more time/actions required on the client before it can send a state update.) To be transparent - this framework has not been deployed beyond Alpha testing, so I have not seen what is realistic bandwidth usage (and have not optimized that much for minimum bandwidth usage. So please try out the demo, so we can get more data!)
Semi-security
Security is hard. However, it’s good to implement a few features, since even the simplest online, multiplayer games/apps get hacked. To implement a few layers of the security ‘onion', there are a few options available with Firebase:
Api-key
In general you can try different methods to try to hide the Firebase API key, not store directly in the client (e.g. get the API key from an external source, from the client code and dynamically load it with the client initialization) However, generally the Fb community seems to accept that the API key may be public and you need to add other security and not even spend the effort to hide the API key.
javebratt.com/hide-firebase-api
Security Rules
This is one of the big available tools. The Fb database can have security rules that only allow access to different areas of the database based on user authorization, use ID, admin and also validating data sent. For example, this case, the user can update the user login database, but no user can read, only the admin can read. For the score, only admin can update (e.g. the Victory Condition function can update), however all can read (e.g. clients can get/read the latest score, etc.) Some areas can be limited to update only (e.g. client cannot add new data to the nodes database, they can only update the data already there.) Test rules using the simulator.
firebase.google.com/docs/database/security
gist.github.com/codediodeio/6dbce1305b9556c2136492522e2100f6
Domain Check (https referrer limitation)
This only allows certain domains to access firebase (e.g. your domain where your game is hosted and in this case the cron-job server. In a release game, the cron-job server should probably also be implemented on the game server host.)
javebratt.com/hide-firebase-api
https Function name/‘Key’
For the https FB firebase functions, besides obfuscating the name / URL of the function call, a key can also be added for the function to check before running. This could get more secure using a rolling key, public / secret key, authorization headers, etc.
Some ideas on further https security:
github.com/firebase/functions-samples/tree/master/authorized-https-endpoint
itnext.io/working-with-firebase-functions-http-request-22fd1ab644d3
Firebase Daily Billing Limit (prevent you from going broke)
You can set daily billing limits so that if your game becomes ‘too’ popular or there is a bad actor or bug in your client or https function caller that causes frequent updates, you can limit the $ damage, but your game access to FB will go down. If you using free service, I think it will just cap out and disable your app also if you go over the free limits.
firebase.google.com/docs/firestore/quotas
Future work
Instead of doing the simple clickfest game, implement more in-depth gameplay which takes advantage of the simple client/server state updates. For example, a co-op Zombie Invasion / Tower Defense game with players going after Zombies that die/respawn and take multiple hits to kill. Again, you could have the Zombie exact behavior different on different client, but update the state of their health on each hit (via database state update), or even simpler, require a number of hits from the player client to kill the Zombie and only update its state then (ignoring hits from other players.) Other client-side mechanism such as multiple hits to an object, surround an object, etc. will also lower overall state update bandwidth.
If you have implemented something similar I would be interested to hear about it. At some point in the future, I may also put this sample C3 project, with Fb functions and security rules up on Git if there is interest.