Spawn Doctor: Part One
Scoping spawn points in Unity with ScriptableObject
Whatever game you happen to be playing, it’s inevitable you’ll encounter some form of spawn. Players spawn, enemies spawn — even collectibles require a spawn system to appear in-game.
In Unity, there are different ways of spawning objects. You can create game objects as your spawn reference and start placing them in scene. But what happens if the number of objects becomes too much? The excess becomes an unwanted overhead as well as a hassle to manage.
One way to deal with all these spawn data is to use ScriptableObject. With it, we can store the data in a collection and spawn it as necessary. However, when using this tool it’s often hard to visualise the data without some form of scene manipulation.
Here, I’d like to show how we can store spawn data in a ScriptableObject and add visual components in scene so that we can quickly edit those data.
In this two-part mini-series, we’ll go over how to:
- Use ScriptableObject to store spawn data.
- Create a visualiser component to render the data using gizmos.
- Sync scene data with ScriptableObject.
- Add UI support to our ScriptableObject.
NB: This tutorial will assume you have some basic experience with building in Unity.
Let’s take an example of setting up an enemy spawn system. First, we’ll put in some some basic data:
EnemySpawnData.cs contains the enemy spawn data, which includes a spawn point and the type of enemy we want to appear.
Next, this spawn data will be stored as a collection in a ScriptableObject:
There are a few things we then want to do with our collection data:
- Store all spawn point data
- Make sure data in ScriptableObject and scene are synced
- Display inspector values in scene
So what’s happening in EnemySpawnCollection.cs?
The actual data which we want to set up for our spawn system. It contains the list of all the spawn points in our scene.
We will call this to write any scene data change back to the ScriptableObject.
When the inspector value changes, OnValidate makes sure those changes are reflected back to the scene elements.
These are functions which we will use to create/clear elements, translating the data from ScriptableObject to scene.
Let me see it
In order to be able to render the data in scene, we’re going to introduce 2 new components here:
This will be the parent containing the visualiser nodes used to represent the spawn data. We will use this to contain all the spawn nodes in the ScriptableObject. These nodes will function as placeholders for our reference, to be cleaned up later on.
Here’s the component which does the actual drawing. In OnDrawGizmos we have a helper function which helps us to render the position of the spawn in scene (more on this below).
In the Update function, we are notified if the node in scene has been moved; the position will then be written back to our ScriptableObject.
Some helper functions we’ll be using to draw the nodes and create the necessary scene objects:
As the name implies, here we’ll use gizmos to render the position of the nodes. Draw also updates colour based on the selected state of the node in scene.
An easy way to toggle between selected and unselected colours.
We’ll use this to create the necessary game object in scene with the required visualiser components.
Generates the visualiser node and parents it to the root. We also keep a reference to the ScriptableObject so that we can update it according to changes in the scene node.
Let’s take a break here and recap what has been done so far. We started out by creating a basic set of data for our enemy spawn point, using a ScriptableObject to store a collection of all the spawn points we require. Then we created 2 new components which we’ll use to visualise it all in scene.
Here’s what can be seen so far in our ScriptableObject!
Unfortunately, there’s nothing much going on (just yet) except for the list. In Part Two, I’ll be talking about how we can make all our elements work together.
Special thanks to Andrew Ching, original author of the node visualiser!