Energy Clicker - A Postmortem


Energy Clicker - A Postmortem

Nearly a month ago, I cold-dropped my first game: Energy Clicker. Its development had some ups and downs, but I feel satisfied with the final result.

I wanted to take the opportunity to talk about the development of the game in general and to take a brief look at some tools I wrote in the process. Hopefully you will find value in what I would like to share.

Development

The game uses the Godot engine with each script and class written entirely in gdscript. The project began in Godot 3.5 with an upgrade to 4.0 midway. Other than for some initial prototyping, I used locally-compiled instances of the Godot editor for development. I created the published builds in an Ubuntu Docker image that pulls and uses official Godot editor and export template binaries. A shell script builds the Docker image, then launches and extracts each release binary from the resulting image.

The project does not contain any plugins or external libraries other than what ships with the Godot Engine. I used Aseprite for the artwork, including the font which also used YelloAfterlife’s Pixel Font Converter.

Project Goal

After some failed attempts at previous games where I bit off more than I could chew, I decided I needed to commit to a project and see it through to the end. I also wanted to ensure that the project met my own standards, lest I or it befall an embarrassing conclusion. In the end, I launched the game with a notch on my belt and my personal goals met. I now have a better understanding of my own shortcomings as a developer and gained wisdom to make better decisions during the development of future projects.

Project Layout

Someone might find the project layout interesting or educational, so I wanted to spend some time discussing it. I do not mean to call this project structure ideal, so take from this discussion what you will. In fact, I end up proposing a handful of alternatives that may have worked better.

Godot offers something called a Resource file which I used as a method to save instances of classes extending Resource to disk. I used these files to control the base-state of most objects in the game, from auto-clickers themselves to their upgrades.

For those interested: An abbreviated and generalized structure of the game files:

<!-- vale off -->
  • assets
    • pylon.png
  • auto-clickers
    • pylon
      • augmentations.tres - (Resource Instance - ClickAugmentations)
      • auto-clicker-pylon.tres - (Resource Instance - BaseAutoClicker)
      • click-type.tres - (Resource Instance - TypedClickDetails)
      • scene.gd
      • scene.tres
      • purchase
        • building-purchase.tres - (Resource Instance - BuildingPurchase)
        • price-01.tres - (Resource Instance - TypedClickDetails)
        • purchase-conditional.tres - (Resource Instance - Conditional)
        • visible-conditional.tres - (Resource Instance - Conditional)
      • upgrades
        • upgrade-01
          • price-01.tres - (Resource Instance - TypedClickDetails)
          • purchase-conditional.tres - (Resource Instance - Conditional)
          • upgrade-purchase.tres - (Resource Instance - UpgradePurchase)
          • visible-conditional.tres - (Resource Instance - Conditional)
        • upgrade-02
    • portal-room
  • resources
    • base-auto-clicker.gd - (class file extending Resource)
    • conditional.gd - (class file extending Resource)
    • purchase-base.gd - (class file extending Resource)
    • purchase-building.gd - (class file extending purchase-base)
    • purchase-upgrade.gd - (class file extending purchase-base)
    • typed-click-details.gd - (class file extending Resource)
    • … …
<!-- vale on -->

This structure got out of hand as the project scaled. At the game’s release, I had nearly 800 *.tres files in the project. I found it painful to edit this volume of individual files during development, mainly when it came to balancing.

Tackling Problems Pt 1 - The Progression Builder

The Problem

Godot allows easy modification of a Resource file’s @export variables through the editor, such as drop-downs or constrained int and float values. The ‘@export’ variables can also have the Resource type assigned, optionally allowing you to link another Resource files as children. The editor allows you to drill-down and edit the @export variables of these sub-files, which made some specific edits easier, and offered a decent flow for editing nested files.

These editor features made Resource file edits simple, but with my project structure I still found several of the types of changes I wanted to make cumbersome and confusing. I found it difficult to keeping track of which files I had edited, and lacked good visibility of the game’s data at a broader scale.

Creating new resources suffered from the same visibility issues. Combine poor visibility with the added tedium of manually going through menus for each new file to configure its name and select the intended type, and I found myself questioning my earlier choices.

Copying files or entire directories solved the issue of tedious file creation, but it came with its own set of issues. Copied files would require manually re-nesting linked files so that they accurately connected to the files where I copied them to rather than from. Add on top of this that the editor shows the name of the linked file for a given Resource and not its full path and it became a nightmare to painstakingly reset, assign, and verify the nested structure of each individual file after copying. You might think, “Come on, it’s only a few extra clicks”, but “extra clicks” times 800 proved unacceptable for my workflow.

The Solution

Enter the “Progression Builder.” I chose to take advantage of one of Godot’s available controls, the GraphEdit. A control that allows the creation of custom child GraphNode controls, connected together via nodes to build a visual node tree.

I created a scene with a GraphEdit as the root node and started building the pieces I needed. I then created custom GraphNode scenes for each of the Resource class types I needed, adding the appropriate slots for inputs and outputs as well as the occasional control for @export variables for quick editing. In the main scene, once the user selects an auto-clicker, the tool loops through the res://auto-clickers/{type} directory, loading each file and creating GraphNode scene instances and connecting linked files as appropriate.

The tool provided the perfect opportunity to script the creation of well-defined structures of files. This let me avoid the issues I had with manual file creation as well as with copying files as I could just create the class instances from scratch in memory, set the values I needed on each associated GraphNode, and click save once to write the state of each loaded or in-memory Resource to disk. No more manually creating individual files or painstakingly validating their nested structure one file at a time!

Reflection

I found using this tool as a net-gain for my productivity. It felt a little silly duplicating some of the functionality of the editor, but the added visibility and light bulk-editing capabilities dismissed such thoughts.

As an exercise in evaluation and honesty; save for the project’s structure, I probably overestimated the value of this tool while writing it. I must question the choice to store the game’s base data in this volume of individual files. A small collection of CSV files or perhaps a database would have allowed for bulk-editing, as well as provide some visibility to a wider range of data at once. Elegance may have suffered with these solutions, and they may have added their own complexities (such as key management), but these options still could have made working with data at this scale easier and perhaps even eliminated the need for a tool like this.

Tackling Problems Pt 2 - The Progression Tester

The Problem

Along with needing a way to edit the game’s starting states and upgrades, I imagined that a way to test the game’s progression would add value as well. Clicker/Idler games sometimes take a considerable amount of time to progress, and I knew manually testing each change using meatspace time would prove unfeasible.

The Solution

Enter the “Progression Tester”. A half-baked but simple enough idea; simulate the game and print the game’s state at certain checkpoints or intervals. As the game progresses, increase the rate at which time passes by increasing the value of delta. The tool would add value by providing insight into the rate at which a player might progress through the game. Assuming, of course, that the tool accurately scripts believable player behavior to test.

Initial versions of this tool had less sophistication, and ignored the existence of upgrades entirely. Amusingly, some of the first iterations that managed to compile and run, well before any balancing took place, progressed to begin tracking elapsed time at the scale of billions of galactic years without reaching the end of the game. “A well made game!” I thought. /s. I continued to maintain and tweak this tool as I expanded the features of the game.

Some of the statistics I wanted to track and log through this tool found homes in the base game. This includes the global production-per-second stats that can viewed by right-clicking any of the resource values in the inventory. Although it went unused in the game’s release, the global production stats also found a place in the Conditional class, typically used to control the visibility of building and upgrade buttons.

The tool faced at least one major refactor, as the initial version had turned to spaghetti over time. Nearing the end of development, I had yet to see any real value from this tool, and I found myself spending more time tinkering with it instead of actually progressing the game to a releasable state. At one point I concluded I had spent more time working on this tool than it would have taken to just make some reasonable assumptions about the game’s balance and finish it from there!

The final nail in the coffin emerged as a several-layer-deep infinite loop inside recursive logic to determine the next best upgrade or building to buy. I have confidence that I could have solved the error, but I made the decision to abandon the tool altogether after a lengthy battle with the debugger.

My thoughts on Godot’s debugger aside, I felt like I shot myself in the foot with this one. A tool I deemed necessary and had designed as a source of value to the development of the game ended up abandoned and had delayed the release date by a measurable amount of time. I think the game suffered as a result of not having the tool available while working on balancing, but I could no longer justify the effort it demanded and decided to progress without it.

Reflection / Mistakes

Several mistakes plagued the development of this tool. I had falsely convinced myself that this tool necessary to ship an acceptable game.

The decision to write the tool happened much too soon in development. Or rather, perhaps a valid idea at the time, its construction started much too soon during development. Combine this with the fact that the game yet lacked several features while I toyed with this tool, I wasted even more time keeping it up to date as development progressed without much of anything existing to balance yet!

The tool lived in an isolated environment, separate from the main game level and game loop entirely. This simplified some aspects, but then also required manually handling or mocking features that other aspects of the game depended upon (global state, etc). This resulted in lost time debugging or writing difficult-to-maintain band-aid code just to keep the tool in a semi-functional state.

I think in the future I will keep ideas like this in mind, but not start on them until I can accurately quantify their value and necessity, or lack thereof.

I posit that balance testing is best implemented using the main game loop after adding most of the MVP features, and probably achievable in several different ways that don’t require parallel development. In the future I will try cheat scripts or loadable scenes or scenarios - tools that would automate playing the game using the main game loop and potentially apply a heuristic towards a specific goal.

Wrap-Up

I enjoyed working with Godot and will continue to use it in the future. I will also continue the practice of extending the repertoire of tools available to me to make games. Expect future discussions on engines and the tools available in the future. (For example, I have started experimenting with GDExtensions.)

I hope the tools and topics discussed here provide insight or value to anyone working on their own projects. I also hope that my work inspires someone else to make their own game, even if unrelated. I have found making games a rewarding practice, and I look forward to continuing my journey to make larger and more engaging experiences. Please join me for the journey ahead!

Get Energy Clicker

Download NowName your own price

Leave a comment

Log in with itch.io to leave a comment.