Slash Racer is a very simple game. The player constantly moves forward, and can only control side-to-side movement. They use this movement to collect some objects and avoid colliding with others. For a game with so few elements, it is critical that every element feel as good as possible. Achieving that feeling is something that can only be accomplished through trial-and-error during playtesting. In other words, prototyping and iterating over different gameplay settings. In this development log, I explore the approach I took to iterate on the design of Slash Racer and some of the decisions resulting from that effort.
Iterating on the Core Game Loop
Somewhat surprisingly, I didn’t make several key gameplay decisions until after development had started, including:
- Is it possible for the player to crash, and thereby lose the game?
- Is the player trying to avoid objects, or collect objects?
- How many degrees of freedom should the player have for movement?
- How does the game end?
When prototyping, be open to ideas. Listen to your instincts. If something feels off, change it. Don’t worry if you start heading in a different direction than what you originally had in mind. At this stage, avoid investing too heavily in specific concepts and mechanics—whether that investment is in the form of emotional attachment, or literal time spent implementing ideas.
The biggest example of this with Slash Racer was the decision to completely invert the victory and loss conditions of the game. As mentioned, the original concept required the player to dodge obstacles that spawned at an increasing rate. While playtesting, however, I noted that crashes were far too frequent and I was never really able to get a good feel for the controls and experience the sort of trance-like gameplay I was hoping for.
My radical solution to this problem was simple: just remove the ability for the player to crash at all! Ok, great. Now you can play as long as you want, which is great if you’re really into steering 2D sprites. Obviously, this wouldn’t make for a great game though. It has no objective and no challenge and no way to try and improve. I solved each of these problems in turns, add a new goal (collect items) and way to fail (by running out of time). Although it was a big departure from where I intended to go, the final product ended up being much better because I was willing to pivot.
Iterating on Key Mechanics
Changes to the core of a game have a significant impact on the game’s implementation. For this reason, it is critical to get playable versions of your ideas running without spending too much time fleshing them out. While my above statements about being open to iteration are true, it’s also true that the biggest decisions should be made as quickly as possible. Whenever possible, however, you should strive to make it easy to change up your game’s mechanics during playtesting.
Once I settled on the overall goal of the game (“collect items to extend your play time as long as possible”), there were still a number of significant decisions to make which could drastically change the experiences of players:
- Does the player’s vehicle return to a forward direction if they stop steering?
- Can the player safely run into walls, or does this end the game?
- Should the player’s forward speed be constant, even when they are veering left or right?
As Slash Racer evolved, I tested these and other mechanics in many combinations. As a result, it was critical that I could toggle these features on and off during gameplay. I accomplished this through the use of Feature Flags.
When adding mechanics that I wasn’t yet sure would make the game better, I would add logic to only enable that mechanic if some configuration value was true. This way, I could test the game with different combinations of enabled features by changing configuration. (As opposed to reverting files, commenting out code, etc.) In Unity, I used ScriptableObjects to store this configuration. This approach allowed me to very quickly make configuration changes in the Unity editor, and have those changes immediately applied to the running game.
Iterating on the Details (aka Tuning)
Key mechanics are the meat and bread of your game sandwich, but it’s still important to get the other toppings just right. This is where tuning the details comes in. Slash Racer had a number of parameters to tune during playtesting, including:
- The player’s base speed, maximum speed, and acceleration
- The speed at which the player can turn
- The steering limits imposed on the player
- How much time the player would have when starting a new game
- How much time each pickup added to the clock
Whereas the key mechanics tended to be boolean flags, these tuning parameters are usually numbers. This means you have a lot more (infinite!) number of possible values to pick from. For this reason, it is even more crucial that you can easily update the numbers behind your game and get rapid feedback on changes. For this, I used the same ScriptableObject approach. This allowed me to test different combinations of key mechanics and tuning values in an efficient manner.
Conclusion and Takeaways
In reflecting on how I made incremental improvements to Slash Racer over the course of this month, I noted a few key principles to carry forward into future game projects:
- Don’t skip prototyping: Conventional wisdom is right here.
- Place the big rocks first: Fine-tuning details will be a waste of time if the core systems change or are replaced
- Incorporate flexibility: Be aware of decisions that you may change later, and code your game to accommodate it.
Development on Slash Racer is winding down now, and it should be available for download for the new year. If you are itching to check it out before then, you can check it out on GitHub. If a snack-sized rogue-like adventure sounds like something you’d enjoy, feel free to checkout last month’s project, Shift: Prelude, while you wait.