Simple Unity Playmode Configuration
Save your team's sanity by implementing a handy configuration helper
Building a game in any engine will inevitably result in a degree of complexity that requires management. If you’re working as part of a team, this complexity can quickly multiply, and as time goes on and features are added, you’ll invariably find that interests begin to compete.
Developers working on different systems may begin to step on each other’s toes: level designers may want the world to populate with enemies, but they might not necessarily want the player to be able to take damage whilst testing the flow of a level. The team tasked with implementing combat mechanics might want to make enemies behave particularly aggressively, or passively, whilst iterating on their systems, but your player character’s default health makes testing a chore. Perhaps your crafting system team would benefit from collectable items being highlighted with a bright green shader that renders through walls, but your GDD demands that your game is a dark, moody adventure where resources are difficult to find, and requires players to thoroughly investigate every nook and cranny.
In this post, I’ll walk through a simple pattern I’ve become fond of lately, that helps to allow different teams to specify their own preferences during development and test with ease.
When In Doubt, Make It Configurable
During the early days of development, it can be very tempting to hard-code certain values, or define them once in a prefab and then just modify your component’s settings before entering play-mode (or even afterwards!) to test particular scenarios.
For a solo developer or a very small team, this workflow is ‘OK’ for the most part, but any reasonably sized project or team may begin to notice that collectively, they spend an awful lot of time working around the game’s default configuration, manually tweaking values at runtime, or inventing team or scenario-specific tools and utilities which allow the developer to hack a path through the game’s various systems and set things up to more easily iterate on their own particular feature.
This kind of ad-hoc (ad-hack?) or limited configuration process will only compound the complexity problem over time, and as the project grows, your developers will likely end up spending a not-insignificant amount of time fiddling and tweaking things every time they want to test a particular feature. This kind of wasted time quickly becomes routine, and therefore incredibly dangerous: you almost certainly didn’t budget for ‘messing about’ when you estimated how long a particular feature would take to implement, and even 20 seconds wasted every time you enter play-mode will very quickly add up.
Failure to grasp the nettle early on means it becomes more and more difficult to keep iteration times down as the project grows, so it’s wise to continuously think about the scenarios you and your development teams would like to be able to test, and to build your systems in a way that supports overriding the default configuration.
The Goal
What we’re aiming to achieve is a simple system whereby developers in a Unity project are able to launch playmode, and have the system detect their specific configuration preferences during startup.
Anyone who’s used Unity will be intimately familiar with the Playmode button. Press it, and within a few seconds, you’re in the game. It does one job, and it does it well! Unfortunately, game development usually requires a bit more flexibility than the Playmode button allows. In our case, we’re interested in entering playmode, but with a bunch of configuration overrides we can parse at runtime, in order to support whatever workflow we’re currently engaged in.
What we will achieve in this post is a very simple system which allows Unity developers to:
Create a Scenario asset, which contains a few configuration options
Click a button on the Scenario asset’s inspector which caches it for later usage, and then immediately enters playmode.
Retrieve the selected Scenario asset at runtime, and use it to override our default configuration
Though the example I’ll demonstrate is very basic, you should be able to immediately identify how useful this simple setup could be for you and your team.
Scenario Assets
In order to define a Scenario, we first need to think about the kinds of things we’d like to be able to configure. In this example, imagine a simple ‘defeat the enemies to earn points’ style game. The obvious targets for configurability here are enemy and player health: perhaps we want to play through a level, but we only want enemies to have 1 HP so that it’s trivial to kill them. Or perhaps we’d like our player to have infinite health, so we can’t be damaged as we test how enemies react to the player’s presence or as we run and jump around the level.
Let’s define a simple Scriptable Object to contain some these configuration values:
You’ll notice I also added configuration options for player and enemy colours, just for fun.
In the Unity editor, we can now create as many instances of this PlaymodeScenario scriptable object as we like, by opening the Assets menu and selecting ‘Create/Velocity/Playmode Scenario’:
We can now select and modify our new Scenario asset. In this case, I’ve created two different scenarios:
Indestructible Enemies
Indestructible Player
In this case, a value of -1 for health means ‘infinite’, but the details here are irrelevant: our only goal is to make sure the values from the Scenario asset are used when appropriate.
Now that we have a few Scenario assets ready to go, let’s move on.
If you find my scribblings informative, helpful, or interesting, then please consider subscribing - either for free, or enjoy a 20% discount forever!
Caching a Scenario
In order for the Scenarios we defined above to be effective, we need a mechanism for the developer to instruct the Unity editor to launch into playmode, and for the relevant code to make use of the selected Scenario.
Thankfully, this is pretty straightforward: all we need to do is utilise EditorPrefs for some temporary storage.
Let’s create a static class called PlaymodeScenarioUtils, and add the following code:
Note the user of the UNITY_EDITOR define. This lets us ensure that this code is only accessible in the editor. Though we might want to add support for different Scenarios in built versions of the game, we’re only interested in editor support for the time being.
In GetScenarioOrDefault, we attempt to retrieve the cached scenario asset path. If there’s no path available, then we return null. Otherwise, we load the asset at the cached path, immediately nullify the cached path value, and then return the loaded asset. Nullifying the cached value ensures that we don’t keep hold of the cached Scenario beyond a single run - any future calls to GetScenarioOrDefault will return null.
We also need a way for runtime code to get hold of the cached Scenario asset, so let’s add that functionality to our PlaymodeScenarioUtils class:
Here, our static constructor checks whether we have a cached Scenario, and loads it. If there’s no cached scenario, or we’re running a build of the game, then we just create a default instance of the PlaymodeScenario asset, which contains our default configuration values.
Whichever Scenario is loaded, be it the cached asset, or a default instance, the result is assigned to the readonly field Scenario - which can be accessed statically, making it easily accessible by any code that needs to use it.
Next, we need a way to actually cache the Scenario we want to use, and to enter playmode immediately after doing so.
Thankfully, that’s trivial! Still inside PlaymodeScenarioUtils:
The final piece of the puzzle here is to add some basic editor functionality which allows us to call EnterPlaymodeWithScenario from the PlaymodeScenario inspector.
Create a new editor-only class (i.e - one that exists within an Editor folder, or within an editor-only assembly), that provides a custom inspector window for PlaymodeScenario assets:
All we’re doing here is adding a “Play” button to the inspector for PlaymodeScenario assets, which lets us call PlaymodeScenarioUtils.EnterPlaymodeWithScenario with a reference to the Scenario asset we’re currently inspecting.
This means we’re now able to select a scenario in the editor, and launch directly into playmode from the inspector:
Now that we’ve closed the loop on our ‘create / cache / load’ functionality, all that’s left for us to do is actually use the Scenario asset at runtime!
Putting it all together
Now all of the ‘interesting’ work is done, all we need to do is grab the loaded Scenario asset at runtime, and do something with it.
In this example, I have a single class named ScenarioInitializer which performs my basic runtime initialisation: a player and an enemy Entity are spawned, and their initial values are configured based upon those found in the Scenario:
Pretty simple stuff. This game won’t be getting any Bafta nominations just yet, but we now have a nice, simple way to test 3 different scenarios:
The default scenario, that players will see. This uses the values hard-coded into the PlaymodeScenario class itself.
An editor-only scenario where the enemy has infinite health
An editor-only scenario where the player has infinite health
Here’s a video demonstrating all 3 scenarios:
As you can see, with just a few simple scripts, we’ve built the foundation of a system that allows anyone working on the game to define their own custom overrides and to launch directly into the game without any runtime mucking about, or modifying any in-scene objects or prefabs.
With a little extra work, you can create scenarios that provide all manner of customisation options for your developers and designers, ensuring that their iteration time is kept as short as possible, and that they’re able to prioritise their own workflow requirements without negatively impacting anybody else on the team!
Summary
As your project grows, it’s critical to ensure development remains as smooth as possible. Though there’s no silver-bullet for game development, and Unity is sorely lacking in some areas which could reduce development time and boost team velocity, we can be thankful that Unity is at least flexible enough to allow us to self-serve functionality that works with us rather than against us.
The tooling illustrated in this post is incredibly basic, and most serious projects will be vastly more complex than the silly example I’ve created here, but even in the most ambitious projects, this solution is flexible enough to be expanded so that it covers a wide variety of use-cases.
If you’re already a subscriber, you may have read my post about Bootstrapping your Unity Game with Addressables, in which I present a method to ensure that any time you enter playmode, all of your ‘system’ stuff is automatically instantiated and ready to be used. Though the bootstrapping system serves a different purpose than that illustrated in this post, it’s easy to see how the two might be combined, merged, and built upon in order to provide a robust bootstrapping and configuration system which supports a huge variety of scenarios and workflows, either in-editor or in builds. In serious projects, the number of parties involved in your game’s development can grow significantly; and investing some time into ensuring that a variety of interests can be served with minimal fuss will almost certainly pay off!
If you have any quick workflow tips and tricks up your sleeve, feel free to share them in the comments!
Further Reading
If you’ve enjoyed this post, check out these previous posts with similar themes:
Bootstrapping your Unity game with Addressables
In this post, we’ll explore a straightforward way to assert control over your Unity game, keep your scenes clean, and lay the foundations of a flexible, extendable bootstrapper that leans on Unity’s Addressables system.
Ship First, Build Second
When you begin working on a new project, it’s incredibly tempting to ‘start at the beginning’. You have some vision for how the user should interact with the software, so you start building whatever the user would first see, and continue from there.
Dear Students: Learn Version-Control
I’ve been working as a coder for (almost) twenty of your finest, British years now, and I am sometimes asked what the number one lesson I would impart to the younger generation seeking to break into ‘the industry’ is.