Creating games can be difficult and time consuming. You have to code all kinds of systems, add and modify art and sound, and of course design levels.
As a programmer, I often found myself overlooking level design, and forgetting just how time consuming and frustrating it could be.
But I also know that as a programmer, there are things I can do to make it easier for myself (and any designers working on the games).
Today, I’ll show you one very useful technique you can use to drastically reduce the time spent on design work, while making it a much more fun process.
The Example – Spawn Points
Enemies are a very common thing in video games, and in a large number of them, enemies are created/spawn throughout the game.
The GameObject spawning them could be simple, instantiating an enemy on a set interval.
Before I show you my technique, let me show you how I used to create them.
Version 1 – A simple transform (very bad)
When I first started placing spawn points in a game, I did it by simply placing a transform. The screenshot below is actually a step beyond what I used to do, because in this one I’ve actually enabled the Icon so you can see it.
If you haven’t used the Icons before, the selection dialog is just to the left of the Active checkbox in the inspector.
I quickly moved on from just placing a transform though because it got really hard to tell exactly where the spawn point was in the world. If the transform is below the ground, I wouldn’t be able to tell without moving the camera all around. The same goes for a spawn point that’s in a building, hovering over the ground, etc.
Version 2 – Using a cube (less bad)
The next evolution of my spawn points involved cubes. Creating spawn points with a cube renderer mostly resolved the issue with not being able to easily see the position in the scene.
To make this work though, I needed my spawn points to disable the renderer in their Awake() call so I didn’t have random boxes showing in the world when the game was being played.
It also didn’t really solve the issue of spawning enemies on the ground, so I’d have to make my spawners do a raycast downward to the ground to get their spawn point before popping out an enemy.
I’d try to place the boxes just a bit over the ground, but found that I wasted a lot of time lining things up right, testing, making minor movements, testing, etc.
In addition to that, it felt ugly, but I used this technique for a very long time….
Version 3 – Custom Editors
After using the previous methods for way too long, I finally came up with a solution that solved my previous problems and made building levels much faster.
As you can see in the image, Version 3 looks drastically different. There are colored spheres with lines attaching them. There’s text over them instead of in an Icon, and that text has a lot of info to it.
Before I show you how it’s done, let me explain what it is you’re seeing.
The Green spheres show actual spawn points for this game. These are points where enemies will be instantiated.
The Blue spheres are waypoints. Enemies spawn at the green spheres then walk to the blue ones.
The lines between them show which waypoints belong to each spawnpoint.
What’s that Text?
The text over the spawn point shows a few things. Let’s examine the top left spawn point.
“Intro 1 0:25-0:28 Spawn 2 [1/3] after 5(8s)“
Intro 1 – This is the name of the wave/area this spawn point belongs to. In this case, it’s the first introductory wave the player gets when they start the game.
0:25-0:28 – Here you see the time in the wave that this spawn point will be active. This spawn point is active for a very short time, starting 25 seconds into the wave and ending only 3 seconds later.
Spawn 2 [1/3] – This tells us how many enemies will spawn from this point. It’s going to spawn 2 zombies, one every three seconds (the [1/3] shows the count and interval). The first one will spawn immediately, and the second after 3 seconds.
after 5 – This part isn’t visible on all spawn points, only on spawn points that delay their start. You can see that in the Hierarchy, this spawn point is under a gameobject that enables after 20 seconds. Each spawnpoint in a timer can have an additional delay added to them to avoid a large list of timers in the hierarchy. The 5 second delay is what makes this spawner start at 0:25 instead of 0:20.
(8s) – The last thing you see just shows how long this spawnpoint is enabled. For this one, after 8 seconds it will auto disable itself. This is just time of the last spawn minus the time the spawn point becomes enabled (28 – 20 in this case).
Snapping to the Terrain or Navmesh
One final benefit of this system that I want to show before getting into code is the ability to have your spawn points and waypoints automatically snap to the terrain or navmesh. In the example below, you can see that when I move this waypoint around it will automatically find its place on the ground as soon as I release it.
This saves a ton of time and resolves that entire issue of lining things up. Don’t do these things manually, have the editor do it for you.
How It Works
To make my custom spawn points work like they do, I take advantage of two great features in Unity, Gizmos and Custom Inspectors.
Both parts do about half of the work required to get the full functionality.
Let’s start with this snippet from my EnemySpawner.cs script
The first thing we do here is get the Wave parent of this spawner. This is the GameObject that all spawners and timers will be under for a specific wave or area of the game.
In the example above, you saw the green part “Intro 1“. That part was just the name of the wave we find right here.
Line 6 takes this wave name and formats uses string.Format to split the wave name from the current spawners name, which is why “Intro 1” is above the spawning details.
On Line 8, we check to see if the wave this gizmo is for is currently selected. We then use that to determine if we want a green spawner gizmo or a gray one. I do this so we can easily tell which spawners are related. All spawners in a wave will be colored at the same time, and all the ones from other waves will just show up as gray.
Line 12 draws the sphere using Gizmos.DrawSphere, in whichever color we’ve chosen.
Lines 14-15 will draw the final text above the sphere if the spawner is in the selected wave.
The OnDrawGizmos code is pretty short, and on it’s own it does a bit of really useful stuff, but there’s a lot missing. It does show the spheres, and it places the name above the sphere with the wave name as a prefix, but there’s a lot more we want to happen.
For example the label from line 15 has a lot of useful info, and we pull that from the name, but we don’t want to manually enter that info, we want it auto generated and updated whenever we change things.
To generate the name, with all the useful data, we override the ToString method of our EnemySpawner class.
If you’ve never overridden the ToString method, you may want to check out this description for a simpler sample of how it works https://msdn.microsoft.com/en-us/library/ms173154.aspx
In this example, we’re building up the rest of the label text. While I won’t go into the details of each line, the end result of this method looks like this:
Every object in c# implements the ToString method that you can override (the default return value for most objects is the name of the class/type).
"0:25-0:28 Spawn 2 [1/3] after 5(8s)"
The Custom Editor
To tie this all together, we use a custom editor for the EnemySpawner.
Before you see the bigger parts of the script, let’s start with the initial attribute that tells Unity this class is a custom editor.
The CustomEditor attribute allows you to tell the engine which MonoBehaviour you want the editor to be used for. This is specified by giving it the type of the MonoBehaviour. In this example it’s typeof(EnemySpawner).
Also remember to add the using UnityEditor statement and make the base class for your custom editor be of type “Editor“.
The Editor class has one important method you need to override. Check out this expanded version of the script and the OnInspectorGUI method that’s being overridden.
This method is called every frame in the editor while the Inspector window is visible and the object is selected. If the Inspector is not visible, or is showing some other game object, this code won’t be called.
The first thing we do in this OnInspectorGUI method is cache the component we’re working with.
On line 12, we assign the target gameobject to the _enemySpawner variable.
Line 13 calls the base editor class version of OnInspectorGUI so it can handle anything that we’re not dealing with. This is required because we’re overriding the behavior of OnInspectorGUI.
The variable target is defined by the editor class and specifies the gameobject this editor is showing currently
Lines 14-19 are a single method call to create a range slider that will fill the min and max movement speed. I do this just to enforce the idea that the max must be greater than the minimum. As a benefit, it also makes the value a little easier to visualize.
Lines 21-24 are there to add waypoints to the spawners. I won’t cover in detail how they work, but these buttons essentially add a child object that will be used as a waypoint. If it’s a random waypoint, my navigation code will select one at random, if it’s static, the enemies will path around them in order. These also have their own gizmo and custom editor code to make them show up as blue in the scene view.
Line 28 just calls a method to disable any left over colliders or renderers on the spawner. Generally there aren’t any, but sometimes one gets created with a cube or sphere and I want to make sure that’s disabled right away. I could just remove them here too, but disabling does the same job and feels safer.
Line 30 does one of the most important parts. It calls the method to stick the spawner to the ground. Sticking the spawner down is done by a raycast from the spawners current position aimed downward. We get the hit point and update the spawners position.
Line 33 wraps it all up by updating the spawners name. It uses the overridden ToString() method we created above to determine the objects new name.
Auto Naming in Action
For a custom editor to work, you need to place the script in a sub-folder named “Editor“. This sub-folder can be anywhere in your project, and you can have multiple Editor folders, but only scripts in an Editor folder will work.