Configuring Shapes

This is the seventh tutorial in a series about Object Management. It adds some behavior to shapes and makes it possible to configure them, per spawn zone.

This tutorial is made with Unity 2017.4.12f1.

Each spawn zone does its own thing.

Shape Rotation

We can create shapes with varied appearance, but they just sit in place until destroyed. Let's spice things up by making them do something. Specifically, we'll make all shapes spin.

Adding Spin

The most straightforward way to make an object spin is by invoking the Rotate method of its Transform component, just like we did for RotatingObject. In this case, we have to add a FixedUpdate method to Shape and invoke it in there. We begin by using the object's local forward direction as its rotation axis.

void FixedUpdate () {transform.Rotate(Vector3.forward);}

Spinning shapes.

The default time step is 0.02, which means that FixedUpdate gets invoked 50 times per second. Hence, we end up with shapes that rotate 50° per second. But let's make the rotation speed explicit, by multiplying the forward vector by 50 multiplied by the time delta. That makes the rotation independent of the time step.

transform.Rotate(Vector3.forward * 50f * Time.deltaTime);

Randomized Rotation

The next step is to give each shape a random angular velocity. Add a public AngularVelocity property to make it possible to configure, then use that to determine the rotation each update.

Game now has to set the shape's angular velocity in CreateShape. We can use Random.onUnitSphere to get a random rotation axis. Multiply it by 50 so we once again end up with a rotation of 50° per second.

Saving Angular Velocity

At this point we're not saving the angular velocity yet. Loading a game will produce shapes with arbitrary angular velocities, because recycled shapes keep their old velocity. As saving the angular velocity changes the safe file format, increase the save version to 4.

Updating all Shapes Together

Before shapes rotated, they didn't need to be updated. But now Unity has to invoke the FixedUpdate method of all active shapes. While a regular method invocation isn't really a problem, FixedUpdate and other special Unity methods require additional overhead which can slow things down. This isn't an issue when there are only a few active shapes, but can become a performance bottleneck when dealing with lots of shapes.

Profiler showing 1000 separate FixedUpdate invocations.

Instead of leaving the updating of shapes to Unity, we can manage it ourselves. Game already contains a list of all active shapes, which we can use to update them. But we cannot just invoke FixedUpdate, because Unity will still invoke that method as well. We have to rename it to something else. Let's use GameUpdate, and make it public so that Game can access it.

In the FixedUpdate method of Game, loop through the shapes list and invoke the new GameUpdate of each shape. Do this at the beginning, before new shapes are spawn. That keeps the behavior consistent with earlier versions of our game.

Is this optimization really worth it?

When you're dealing with thousands of similar objects, they all need to update, and you're already keeping track of them yourself, then it can be worth it. How much performance you gain should be profiled, as it varies per target platform. The most gains can be had in the editor. Note that when you find yourself in this situation, it is also a good idea to investigate whether the Entity Component System introduced in Unity 2018 is a better fit, but I won't cover that in this tutorial.

Shape Movement

Our shapes can now rotate, but they still remain where they were spawned. Let's change that, by giving each shape a random velocity as well.

Adding Velocity

Like we did for the angular velocity, give Shape a Velocity property.

public Vector3 Velocity { get; set; }

Each update, add the velocity multiplied by the time delta to the shape's position. We can use the local position instead of the more costly position property, because the shapes should always be root objects.

Velocity Per Spawn Zone

Giving each shape a random movement direction produces a rather chaotic scene. Instead, we could have all shapes move in the same direction. But rather than using a single uniform direction, we could use a unique velocity per spawn zone. That makes it possible to create more elaborate levels.

Currently, Game creates and configures each new shape and asks the level for a spawn point. If we want to make the velocity dependent on the spawn zone as well, then Game must ask for a velocity too. Rather than do that, we'll instead move the entire responsibility for shape configuration from Game to SpawnZone.

Add a public ConfigureSpawn method to SpawnZone, with a shape parameter. Copy the code from Game.CreateShape to this method, except for the first and last lines that create the instance and add it to the list. The method's parameter replaces the instance variable, and SpawnPoint can now be accessed directly, instead of having to go through the level.

At this points everything still works as before, except that it's now SpawnZone that configures the shapes.

Relative Velocity

Now that we configure the shape inside SpawnZone, we have access to the transformation data of the zone. We can use that to make the shape's velocity relative the the zone's orientation, just like the shape's position is also relative. Let's use the zone's local forward direction, multiplied by a random speed.

This works as expected for sphere and cube zones, but not when using a composite spawn zone. Right now the forward direction of the composite spawn zone itself is used, instead of those of its sub zones. To make this work, CompositeSpawnZone must override ConfigureSpawn to forward the invocation to one of its sub zones, just like it does for SpawnPoint. The code can be copied from that property, only changing what it does at the end.

Configuration per Spawn Zone

Migrating the responsibility for shape configuration from Game to SpawnZone doesn't just make it easy to set a relative movement direction. It also makes it possible to use different kinds of movement per spawn zone.

Movement Direction

First, let's make it possible to choose between either a forward or an upward movement direction. To make this choice explicit, create a SpawnMovementDirection enumeration type. Because this type only really makes sense in the context of shape configuration per spawn zone, define it inside the SpawnZone class, instead of putting it in its own script file. Then give SpawnZone a configuration field of this type.

Does the nested type have to be declared public?

No, but there is no compelling reason to make it protected either. You might need it to be public when working directly with the enumeration outside the class, for example for a custom editor. Outside the SpawnZone class and those that extend it, the enumeration type can be accessed via its fully-qualified name SpawnZone.SpawnMovementDirection.

Now we can check in ConfigureSpawn whether the movement direction is set to upward. If so, use transform.up, otherwise keep using transform.forward.

Outward Movement

Besides choosing a uniform movement direction, it is also possible to make the shapes move away from the center of the spawn zone. Add an Outward option to the enumeration for that.

public enum SpawnMovementDirection {
Forward,
Upward,Outward
}

The correct direction for outward movement is found by subtracting the zone's position from the shape's position and normalizing the result. Note that we have to use transform.position and not the local position, because spawn zones need not be root objects. Also, the relation doesn't last after configuration, so the direction doesn't change if the zone happens to move.

Having to use two fields to control a single range is inconvenient, especially if we want to add more ranges later. Unity doesn't have a range type for floats, so let's make one ourselves. Create a struct type named FloatRange with public min and max float fields. Essentially, it's a Vector2 with appropriately-named fields and without the vector-related functionality. Instead, give it a convenient RandomInRange property that takes care of the invocation of Random.Range.

Note that FloatRange is not specific to shape configuration and is defined in its own script file, as usual.

To make Unity save float range values, mark the type with the Serializable attribute. The attribute exists in the System namespace, but that namespace also contains a Random type, which clashes with Unity's version. To avoid that, just write System.Serializable instead of using the namespace.

[System.Serializable]
public struct FloatRange {
…
}

Now we can use one FloatRange field and its convenient property in SpawnZone.

Isolating the Configuration

We can also create a type to contain all configuration options for spawning. That neatly groups them together and makes it so we don't have to prefix all fields with spawn. So define a serializable SpawnConfiguration struct type inside SpawnZone and put relevant fields plus the enumeration type in it, with their prefixes removed. Then SpawnZone only needs a single spawn configuration field.

Shouldn't SpawnConfiguration be a class?

The point is to group data together, while keeping it inside the SpawnZone object, which is exactly what the struct type does. As a class, the data would exist as its own object somewhere else in memory and spawnConfig would be a reference to that object. If we were to pass around configurations then a class would be appropriate, but we're not going to do that.

Adjust the references in ConfigureSpawn to match. At this point, because the movement direction names have become very long, it can be convenient to replace the if-else sequence with a switch block.

How does switch work?

A switch block is an archaic way to branch based on a single variable or field. It uses labels to control the flow of execution. Each label is defined by case followed by a value and a colon. If the value used to switch matches a label, code execution jumps to just after that label. There is also a special default label, which is used when none of the other labels match.

Instead of using code blocks for each case, the relevant code sections have to be terminated with a break or a return statement.

Besides that, it is possible to declare multiple labels together, like case 1: case 2: DoAB(); break; which is equivalent to if (x == 1 || x == 2) { DoAB(); }. It is also possible to use goto to jump to another case. But that use case is rare. I'm only using it here to keep code lines shorter by not having to repeat spawnConfig.movementDirection.

Composite Spawn Zone Override

Note that all spawn zone types now have spawn configuration options, thus also the composite spawn zone. We can use that to override the configuration of its sub zones. Add a toggle to CompositeSpawnZone to make that optional. If it should override, then have it invoke the base implementation of ConfigureSpawn instead of forwarding it to one of the sub zones. It will then use its own configuration, while still selecting a spawn point from a sub zone.

Advanced Configuration

Now that we have created a way to configure spawn movement per zone, we can extend this approach. There are more things that we can control, and we can improve the presentation of these options further.

Angular Speed and Scale

Additional candidates for configuration are the rotation speed and scale of the shapes. Add a FloatRange field for both to SpawnConfiguration and use them in ConfigureSpawn.

We can add even more options—like a way to control the angular rotation axis—but the point is that the inspector of our configuration quickly becomes large and unwieldy. Each float range takes up three lines when expanded, which is a lot. It would be better if each could fit on a single line.

Custom Property Drawer

We can override Unity's default way to draw a FloatRange value by creating a custom property drawer for it. Add a FloatRangeDrawer class for this purpose. As it deals with the editor, its file should be placed in an Editor folder. That tells Unity to compile and combine it with all the other editor-related code and keep it out of builds.

An editor script.

Editor classes rely on things from the UnityEditor namespace, so use that in addition to UnityEngine. To make the class a property drawer, it has to extend the PropertyDrawer class.

Besides that, we have to tell Unity what type we want to create a custom property drawer for. That's done by adding the CustomPropertyDrawer attribute to our class. We have to supply it with the relevant type as an argument, which we can specify with the help of typeof.

Now Unity will invoke the OnGUI method of our property drawer each time it has to show the UI for a FloatRange value. We have to override that method to create our own UI. It has three parameters: a Rect defining a region to draw in, a SerializedProperty representing the float range value, and a GUIContent containing the default label to use for it. Initially, leave the method empty.

Shouldn't position be named area, rect, or something similar?

That would make more sense, because it really describes a rectangular UI region and not just a position. But Unity consistently uses position, so I'll do that too.

Because we're not doing anything in OnGUI, nothing gets drawn. But the default property reserves a single line for itself, so the inspector for our spawn configuration has already shrunk to the desired size.

We should start by telling the Unity editor that we're creating the UI for our property, by invoking EditorGUI.BeginProperty with the same arguments as OnGUI, only with the label and property swapped. And once we're done we invoke EditorGUI.EndProperty. We'll create the UI in between these invocations. While it doesn't seem to do anything, this ensures that the editor will be able to deal with prefabs and prefab overrides.

Our float range property consists of two sub-properties, its min and max floats. We can access them by invoking FindPropertyRelative on the property, with the appropriate name as a string argument. That once again gives us a SerializedProperty instance. The simplest way to show the UI for such a property is to invoke EditorGUI.PropertyField with the position and the property as arguments. Do this for the min value.

The UI for the min and max fields end up drawn on top of each other, because we used the same position settings for both. When drawing properties, Unity gives us a rectangular region to drawn in and we have to take care of layout ourselves. In this case, we can simply halve the width of the region and increase the horizontal coordinate by that width for the second field.

Next, we'll have to add the label for the range. That is done by invoking EditorGUI.PrefixLabel with the position and label that was given to us. As the label takes up some space, the method returns a modified region that gives us the remaining space for the rest of our UI.

This screws up our layout, because Unity uses a fixed width for the labels, which is too wide for our min and max fields. We can override that width by setting the EditorGUIUtility.labelsWidth property. Let's set it to half of the width we use per field.

That looks good, but only because our range fields end up with an indentation of one step. Unity keeps track of the UI indentation globally, but we can override it by setting the EditorGUI.indentLevel property. Make sure it's set to 1, which pushes the label text one step to the right.

Notice that when an input field is selected, the corresponding label turns blue. But when a min fields is selected, the label of its range also turns blue. That's because they end up with the same UI control ID. We can avoid that by adding a specific control ID as an argument when invoking PrefixLabel. Selecting the entire range is pointless, so use GUIUtility.GetControlID(FocusType.Passive). That prevents is from becoming blue and skips it when you use the tab key to step through the UI controls in the editor.

Finally, we should restore the indent level and label width to their original values when we're done. It doesn't matter in this case, because Unity's default editor restores the values for us, but we cannot rely on that in general.

Configurable Color

Another thing that we can make configurable is the allowed range of random colors. Up to now this was fixed, but we have neat float ranges that we can use to configure it. In fact, we can create a dedicated ColorRangeHSV struct to contain these ranges and provide a convenient property to get a random color out of it. Once again—like FloatRange—this struct stands on its own and is not specific to spawn configuration.

Range Sliders

The hue, saturation, and value must all fall between 0 and 1, so it doesn't make sense to allow any other values. If they were simple float field, then we could have used the Range attribute to enforce this in the editor, turning the input fields into sliders.

[Range(0f, 1f)]
public FloatRange hue, saturation, value;

Range attribute doesn't work.

But that doesn't work, because Range only works for a float or int. So let's create our own attribute. That's done by defining a class that extends PropertyAttribute. The convention is to add Attribute as a suffix, so we'll name it FloatRangeSliderAttribute. Although we only use this metadata in the editor, its script file must not be placed in an Editor folder, as we're going to use this type in ColorRangeHSV.

The attribute is just a container for two properties, Min and Max. They should be publicly readable, but only have to be set by the attribute itself.

Now we can use our own attribute instead of Range. As an attribute, we can refer to it as FloatRangeSlider, omitting the Attribute suffix.

[FloatRangeSlider(0f, 1f)]
public FloatRange hue, saturation, value;

That by itself doesn't change how the float ranges are drawn, because all we've done is attach some metadata to the field definitions. We have to create another custom property drawer, this time for FloatRangeSliderAttribute instead of for FloatRange. Again begin with a basic drawer that leaves the UI empty.

Before drawing a property, the Unity editor checks whether there is a drawer that applies to an attribute attached to it. If so, it uses that one. Otherwise, it checks where there is a drawer that applies to the property's type and uses that one. If not, it will use its default drawer. So attributes take precedence, and we once again end up with empty lines.

We still need to access the min and max properties, but this time we want to draw a slider to indicate a range, not two separate float fields. So keep hold of them with variables.

We can access the float values of min and max via the floatValue property. First we have to get them, then after we've shown the range slider we'll have to set them, in case they were changed. Unity will take care of detecting changes and supporting undo and redo for us.

Next, we need to know the limit of the slider that we are about to show, which is stored in our attribute. We can access it via the attribute property of PropertyDrawer. The type of attribute is PropertyAttribute, so we have to cast it to our own type, by writing attribute as FloatRangeSliderAttribute.

Now we have all that we need to draw a slider range, by invoking EditorGUI.MinMaxSlider. As arguments we'll use the position and label, followed by the min and max values, finishing with the min and max limits. Because the min and max can be changed by the slider, we have to supply them as reference arguments, by putting ref in front of them. That turns them into references to the variables—as if they were objects instead of floats—so MinMaxSlider can alter them. That is necessary because a method cannot return two values.

Sliders with Values

While the sliders are nice, it isn't possible to specify exact values, except for the extremes. This might not be a problem as colors don't need to be exact, but it makes it impossible to check the values of one slider to copy for use somewhere else. So let's add regular input fields for the min and max values too.

First, we'll remove the label from the slider, which makes it possible to put it in between two float fields. Simply remove the label argument from the invocation of MinMaxSlider.

We'll begin by dividing the remaining space equally between the three parts. First use EditorGUI.FloatField to draw a float input field for min, without a label. It returns the possibly changed value. After that comes the slider, followed by the max input field.

We can improve the layout by dedicating half of the width to the slider, leaving the float fields with a quarter each. Also, it looks better if we add a little padding between the slider and floats. Do this by subtracting four pixels from the width of the float fields, shifting the horizontal positions to compensate.