Sponsored By
Matt Javanshir

November 27, 2023

6 Min Read

Setting the scene.

What’s up, world?

For anyone who works in and around super small indie video game studios, you’ll have noticed that individuals often have multiple roles in the project. People wear many hats. So many hats. More hats than a birthday party, fancy wedding and a day at the races combined. Maelstroms of hats. Hats everywhere. Hatters gonna hat, after all.

So for a project like our upcoming game Orbitect, which is an ambitious physics-based construction roguelite with a complex tapestry of configuration, it was super important that we utilised our time in a sensible, efficient way in order to stay as focused and efficient as possible so that we could realise our vision without spending decades in the process and tying ourselves in a myriad of nebulous (albeit rewarding!) knots.

This post is about an example of how our small but mighty team have used some spreadsheet-based automation to streamline selections of our production in order to iterate and refine our gameplay balancing at speed and scale. We’ll be writing more in the future about exactly how we tackled the balancing itself, as well as how Orbitect is utilising configuration on the programming side, but for now — feel free to treat yourself to a little insight into how we’ve joined the two together in data-driven harmony.

What’s our problem?

In Orbitect, the player uses blocks and stat-based panels to craft a space station capable of destroying waves of space debris. Destroying that space trash earns the player money and resources to repair, upgrade and expand their station, so on and so forth, ad infinitum (well, until the end of the campaign at least). We have designed many levers of configuration in order to control the flow and feel of gameplay. For example:

Panels: How much does one cost? What’s it’s health? Strength? Defence? Speed? Efficiency? How much to upgrade or repair? How much damage can it do? What description does the player see?

Debris: How massive, or metallic, or explosive are they? How much money is earned from destroying it? What’s the probability of reward if the debris is destroyed? What reward does the player get if they destroy it?

Waves of debris: When and how does debris appear? How much debris appears in a given wave? How long does the wave last? What does the play get for clearing a wave?

Wave Patterns: What type of ways can debris appear in a wave? Which direction? How often? How fast? Does it target a specific part of your station?

Bosses: When in the campaign do they appear? How difficult are they? What do you get for defeating them?

Stat Rewards: What rewards are available and for which stats? Are all panels affected from a reward? How much is rewarded?

Gameplay boosts: How much does each one cost? How much do they affect gameplay?

  • Other player values: How much money does the player start with? You get the picture…

All in we have just over 70 configurable attributes to work with, each with dozens (or more) entries comprising the overall configuration of the game. Can you imagine if we had to keep track of this manually in-engine? Tweaking the configuration to refine the game’s campaign experience would be a time consuming, miserable, communicative monstrosity and would most likely fail before it began. Thankfully, we are making use of a data interchange format called JSON to store attributes and their values across various files.

Here’s one excerpt as an example: 

{
 "wavePatterns": [{
   "name": "Wavepattern 1",
   "description": "Rest wave",
   "speed": 1,
   "direction": "All",
   "spawntype": "Every 5 Seconds",
   "target": "None",
   "difficultymultiplier": 1
  },
  {
   "name": "Wavepattern 2",
   "description": "Build Back wave",
   "speed": 2,
   "direction": "All",
   "spawntype": "Every 4 Seconds",
   "target": "None",
   "difficultymultiplier": 1
  }
 ]
}

When it comes to refining the game design itself — updating multiple values quickly and accurately directly in the JSON is still a challenge. It’d still require us to manually scroll through and edit various attributes. And if we had multiple waves, debris, panels etc in the game (which we do), then we’d have to do this multiple times.

So what did we do about it?

Enter: The Spreadsheet.

That’s right. We’ve employed the old reliable stalwart that holds the entire knowledge working world together — a spreadsheet. We used one to lay out the various configurations across multiple tabs. For example:

These are placeholder values! Don’t go getting all excited.

Not only is this useful because of the use of rows, columns, and formatting that we all know and love, but because we can make use of formulas to create feux-JSON configurations in the spreadsheet itself. For example:

We specify a row and column from our easy to understand spreadsheet that corresponds to a cell containing an attribute name. We do the same for that attribute’s value.

We hardcode in some curly brackets that will be needed in the JSON no matter the configuration values.

We add a Y/N column to flag whether the value is a string (because if so we need speech marks for a string).

We use the “indirect” function and Char(34) with the above to create a formula that acts as a concatenation and pre-populates the feux-JSON with spreadsheet values where they are needed. Some logic is also needed here to remove a comma from the last value in an encased sequence.

We can scale the JSON for all entries by adding 1 to our row reference (or a letter along for our column reference) in the next row down so that all rows from the spreadsheet are captured in the JSON file.

  • Thanks to the excellent JSONLint for providing an excellent validator!

Here’s a snippet of one to illustrate what it looks like:

Making the feux-JSON.

Great! That’s already saved us some time trawling through JSONs. However, we were still maybe spending around five minutes copying, pasting, zipping up and sending to one another every time we wanted to tweak a few parameters and test out the gameplay implications. Given we are iterating repetedly during our development, we had an opportunity to further help ourselves. So we utilised a google apps script (with thanks to this thread, and this thread, as well as Chat GPT!) to automatically copy and paste our feux-JSONs into (real life) JSON files, and store them onto a specific folder in our shared google drive, for anyone in our team to pick up and play in their local build (and eventually use in the main development build). We even made a little button to run the apps script and export them!

Not the best looking button in the world, but I’m a spreadsheet geek, not an artist. What do you want from me?

Behold! Exported JSONs hot off the press (of the button).

Now at the click of a button we have fresh Orbitect gameplay configurations ready to use the next time we run the campaign in-game. The couple of hours invested in making these little innovations (can I be so bold as to call them innovations? Probably! If you’ve read this far down then you must like this approach too, right?) has saved us exponentially more time in the run up to release. It has paid for itself within a couple of development days.

So there we have it. Is it rocket science? Hardly. Just a good old spit-and-sawdust approach to saving us a little bit of time, hundreds of times. Little friction-busting steps like this along the way help our team to release a game that reaches for the stars, and takes out the trash along the way.

Orbitect is a construction roguelite mashup available now for PC (Windows).

Read more about:

Featured BlogsBlogs
Daily news, dev blogs, and stories from Game Developer straight to your inbox

You May Also Like