Using Google Spreadsheets in Defend the Dam

Published by: 0

This week has been an exciting one here at Dragon Army, as our latest game, Defend the Dam, is now available on iOS. If you haven’t already downloaded it, we suggest you pause here and go check it out for yourself.

Now that we have that covered, let’s go behind the scenes on how we used Google Spreadsheets when developing the content you’ll see in the game.

Defend the Dam has a LOT of content — enemies, turrets, levels, challenges, daily challenges, boosts, player progression, and even the copy for the tutorial. All of this data is static, which is to say–it’s the same for every player. When authoring static content for a game (or really, for any application), there are a number of things you want to worry about:

1) How do you author/update the content?

2) How do you deliver the content?

3) How do you integrate the content into your game?

For Defend the Dam, we decided very early in the development process to use Google Spreadsheets as our Content Management System (CMS). In this post I’ll walk through the three above points and talk about how we’ve integrated those spreadsheets into our game development process. Here’s a gist (link) which talks more technically about how to get this going in Unity.

1) How do you author/update the content?

How you author your content is going to depend heavily on what your content looks like. If you’re doing a tile-based RPG, then a spreadsheet is probably a poor choice. Ultimately our content goes into the game as a CSV, so on the low end we could just have our developers and content designers use a text editor to write CSVs… but a text editor isn’t the greatest tool for editing tabular data. On the higher end, we could write a custom web CMS in a tool like Ruby on Rails for entering content. This allows us to have more control over the types of data entered into the CMS, custom validation logic, real SQL-backed foreign key constraints, and more fine-grained control over how data is exported… but it also means we have to spend time building the CMS.

For us Google Spreadsheets strikes a nice balance of being much more usable than a text editor and essentially no work to set up. In database terms, each sheet on a spreadsheet is essentially a single SQL database. The first row is used for property names and the second row is used to define the property type (string, float, int, bool, enum). Past that is the actual data for the app. Our largest model is our Wave model with 3,178 rows (a single playable level in DtD has ~10 waves of enemies, and each wave can have any number of enemies). Our smallest is our enemy model, which contains things like velocity, health, and damage for our 14 basic enemies. Google Spreadsheets start to bog down once you get into the multi-thousand row territory, but it wasn’t painful enough for us that we considered switching over to a custom CMS.

Google Spreadsheets automatically version and keep history, but we also check in the derived CSVs into our own source control. Should something catastrophic every happen to one of our content sheets, we could just re-import the most recent CSV.

2) How do you deliver the content?

There are a couple of ways to do this. Out of the box, Google Spreadsheets allows you to download a sheet as a CSV. This can be pretty cumbersome, though, so we ended up writing a ruby script to automatically pull the data from each sheet into its own CSV (see update_models.rb in the gist). We chose CSV over JSON because (1) it’s a lot easier to convert a spreadsheet into a CSV and (2) CSVs are a lot more compact (no need to duplicate all the keys with each object record).

Defend the Dam doesn’t have a persistent backend server (other than for data logging), so we actually just throw the CSVs into our Resources folder inside Unity. If we decide to rebalance or tweak a level, that means we need to run the update_models script to pull fresh content from Google and then make a new Unity build.

In future games we’ll likely store those content sheets on a web server. When a game boots up, it can compare its local version of each content sheet with the server version and download updates as necessary. This provides a valuable way to update a live-operating game or even server different versions of the content to different users.

3) How do you integrate the content into your game?

We wrote our own simple ORM in C# to load in the CSV data. It’s loosely based on ruby’s ActiveRecord, but massively slimmed down and simplified (see BaseModel.cs in the gist). For each CSV-backed model, we extend our BaseModel into a new class and point it to a CSV. From that point we can look up static data from anywhere in the game by doing things like Model.find(id) or Model.findByProperty(“name”,”name_of_thing”).

Wherever possible, we prefer using a unique name as a primary key over an integer ID in code, as this allows us to search through the code for instances that reference a particular data model. Searching for instances of “porcupine” or “forest_5” are a lot easier than searching for the number 15 (which just happens to be the numerical ID for both the porcupine in our Creep Model and fifth forest level in our Level Model). Things like has_many and belongs_to relationships are created as needed on the derived classes instead of being hard-coded into BaseModel. We don’t use them in a ton of places and it’s fairly easy to write the logic when we need it.

defendthedam

Because Defend the Dam has so much content (mostly on the level side), we’re really pushing Google Spreadsheets about as far as they can be pushed. We’ve spent hundreds of hours tweaking minor things in the content documents without any major hiccups. The most annoying thing we dealt with was that on our multi-thousand row Wave model the logic for auto-inserting IDs would occasionally stutter and we’d get duplicates. Once this got annoying enough, we added an “auto” data type to just create IDs at runtime. This worked for our Wave model because we just needed the IDs to be sequential and nothing else in the game references waves by ID (no “wave_id” foreign keys), but if our data was more interconnected this might’ve been a bigger problem.

We suspect that most model-based games have less content than DtD, so if anyone is looking for an easy CMS for their game models in Unity, definitely take a look at the gist and see if you can borrow something from Defend the Dam!

If you do end up using our BaseModel or need some help figuring things out, feel free to shoot me an email (will [at] dragonarmy [dot] com) or tweet at me as @SimianLogic.