# Configuring Shapes

Variety of Randomness

• Make shapes rotate and move.
• Add configuration per spawn zone.
• Improve the inspector.

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.

## 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.

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);
}```

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.

```	public Vector3 AngularVelocity { get; set; }

…

void FixedUpdate () {
transform.Rotate(AngularVelocity * Time.deltaTime);
}```

`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.

```	void CreateShape () {
…
instance.AngularVelocity = Random.onUnitSphere * 50f;
}```

To randomize the rotation speed as well, replace 50 with a random range, say between 0 and 90 degrees per second.

`		instance.AngularVelocity = Random.onUnitSphere * Random.Range(0f, 90f);`

### 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.

`	const int saveVersion = 4;`

Write the angular velocity after the shape's color.

```	public override void Save (GameDataWriter writer) {
base.Save(writer);
writer.Write(color);
writer.Write(AngularVelocity);
}```

Read the angular velocity when loading as well, when the save version is high enough. Older saved games didn't have an angular velocity, so use the zero vector for those.

```	public override void Load (GameDataReader reader) {
AngularVelocity =
}```

### 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.

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.

```	//void FixedUpdate () {
public void GameUpdate () {
transform.Rotate(AngularVelocity * Time.deltaTime);
}```

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.

```	void FixedUpdate () {
for (int i = 0; i < shapes.Count; i++) {
shapes[i].GameUpdate();
}

…
}```

## 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.

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.

```	public void GameUpdate () {
transform.Rotate(AngularVelocity * Time.deltaTime);
transform.localPosition += Velocity * Time.deltaTime;
}```

### Saving Velocity

Save the velocity too, writing it directly after the angular velocity.

```	public override void Save (GameDataWriter writer) {
base.Save(writer);
writer.Write(color);
writer.Write(AngularVelocity);
writer.Write(Velocity);
}```

And load it, once again using the zero vector when reading from old files.

```	public override void Load (GameDataReader reader) {
AngularVelocity =
}```

### Randomized Velocity

When a new shape is created in `CreateShape`, give it a random direction and speed, by multiplying `Random.onUnitSphere` with `Random.Range`, for example with speeds between zero and two units per second.

```	void CreateShape () {
…
instance.AngularVelocity = Random.onUnitSphere * Random.Range(0f, 90f);
instance.Velocity = Random.onUnitSphere * Random.Range(0f, 2f);
}```

### 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.

```	public void ConfigureSpawn (Shape shape) {
Transform t = shape.transform;
t.localPosition = SpawnPoint;
t.localRotation = Random.rotation;
t.localScale = Vector3.one * Random.Range(0.1f, 1f);
shape.SetColor(Random.ColorHSV(
hueMin: 0f, hueMax: 1f,
saturationMin: 0.5f, saturationMax: 1f,
valueMin: 0.25f, valueMax: 1f,
alphaMin: 1f, alphaMax: 1f
));
shape.AngularVelocity = Random.onUnitSphere * Random.Range(0f, 90f);
shape.Velocity = Random.onUnitSphere * Random.Range(0f, 2f);
}```

In `GameLevel`, remove the `SpawnPoint` property and add a `ConfigureSpawn` method, which simply forwards to the method of its spawn zone.

```	//public Vector3 SpawnPoint {
//	…
//}

public void ConfigureSpawn(Shape shape) {
spawnZone.ConfigureSpawn(shape);
}```

Finally, remove all configuration code from `CreateShape`, instead relying on the `ConfigureSpawn` method of the level.

```	void CreateShape () {
Shape instance = shapeFactory.GetRandom();
//Transform t = instance.transform;
//…
//instance.Velocity = Random.onUnitSphere * Random.Range(0f, 2f);
GameLevel.Current.ConfigureSpawn(instance);
}```

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.

```	public void ConfigureSpawn (Shape shape) {
…
shape.Velocity = transform.forward * Random.Range(0f, 2f);
}```

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.

```	public override Vector3 SpawnPoint {
…
}

public override void ConfigureSpawn (Shape shape) {
int index;
if (sequential) {
index = nextSequentialIndex++;
if (nextSequentialIndex >= spawnZones.Length) {
nextSequentialIndex = 0;
}
}
else {
index = Random.Range(0, spawnZones.Length);
}
spawnZones[index].ConfigureSpawn(shape);
}```

To make it possible to override `ConfigureSpawn`, we have to mark it as `virtual` in `SpawnZone`.

```	public virtual void ConfigureSpawn (Shape shape) {
…
}```

## 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.

```	public enum SpawnMovementDirection {
Forward,
Upward
}

[SerializeField]
SpawnMovementDirection 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`.

```	public virtual void ConfigureSpawn (Shape shape) {
…
shape.AngularVelocity = Random.onUnitSphere * Random.Range(0f, 90f);

Vector3 direction;
if (spawnMovementDirection == SpawnMovementDirection.Upward) {
direction = transform.up;
}
else {
direction = transform.forward;
}
shape.Velocity = direction * Random.Range(0f, 2f);
}```

### 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.

```		if (spawnMovementDirection == SpawnMovementDirection.Upward) {
direction = transform.up;
}
else if (spawnMovementDirection == SpawnMovementDirection.Outward) {
direction = (t.localPosition - transform.position).normalized;
}
else {
direction = transform.forward;
}```

### Random Movement

Let's also support a random direction, which is what we started with. Add `Random€` to the enumeration.

```	public enum SpawnMovementDirection {
Forward,
Upward,
Outward,
Random€
}```

And use `Random.onUnitSphere` to produce a random direction vector.

```		else if (spawnMovementDirection == SpawnMovementDirection.Outward) {
direction = (t.localPosition - transform.position).normalized;
}
else if (spawnMovementDirection == SpawnMovementDirection.Random€) {
direction = Random.onUnitSphere;
}
else {
direction = transform.forward;
}```

### Speed Range

Besides the direction of movement, we can also control the speed range. All we need is configuration fields for both the minimum speed and the maximum speed.

```	[SerializeField] float spawnSpeedMin, spawnSpeedMax;

public virtual void ConfigureSpawn (Shape shape) {
…
shape.Velocity = direction * Random.Range(spawnSpeedMin, spawnSpeedMax);
}```

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.

```using UnityEngine;

public struct FloatRange {

public float min, max;

public float RandomValueInRange {
get {
return Random.Range(min, max);
}
}
}```

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`.

```	//[SerializeField] float spawnSpeedMin, spawnSpeedMax;
[SerializeField]
FloatRange spawnSpeed;

public virtual void ConfigureSpawn (Shape shape) {
…
shape.Velocity = direction * spawnSpeed.RandomValueInRange;
}```

### 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.

```	//public enum SpawnMovementDirection {
//	…
//}

//[SerializeField]
//SpawnMovementDirection spawnMovementDirection;

//[SerializeField]
//FloatRange spawnSpeed;

[System.Serializable]
public struct SpawnConfiguration {

public enum MovementDirection {
Forward,
Upward,
Outward,
Random€
}

public MovementDirection movementDirection;

public FloatRange speed;
}

[SerializeField]
SpawnConfiguration spawnConfig;```

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.

```	public virtual void ConfigureSpawn (Shape shape) {
…

Vector3 direction;
switch (spawnConfig.movementDirection) {
case SpawnConfiguration.MovementDirection.Upward:
direction = transform.up;
break;
case SpawnConfiguration.MovementDirection.Outward:
direction = (t.localPosition - transform.position).normalized;
break;
case SpawnConfiguration.MovementDirection.Random:
direction = Random.onUnitSphere;
break;
default:
direction = transform.forward;
break;
}
shape.Velocity = direction * spawnConfig.speed.RandomValueInRange;
}```

### 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.

```	[SerializeField]
bool overrideConfig;

…

public override void ConfigureSpawn (Shape shape) {
if (overrideConfig) {
base.ConfigureSpawn(shape);
}
else {
int index;
if (sequential) {
index = nextSequentialIndex++;
if (nextSequentialIndex >= spawnZones.Length) {
nextSequentialIndex = 0;
}
}
else {
index = Random.Range(0, spawnZones.Length);
}
spawnZones[index].ConfigureSpawn(shape);
}
}```

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`.

```	public struct SpawnConfiguration {

…

public FloatRange angularSpeed;

public FloatRange scale;
}

…

public virtual void ConfigureSpawn (Shape shape) {
Transform t = shape.transform;
t.localPosition = SpawnPoint;
t.localRotation = Random.rotation;
t.localScale = Vector3.one * spawnConfig.scale.RandomValueInRange;
shape.SetColor(Random.ColorHSV(
hueMin: 0f, hueMax: 1f,
saturationMin: 0.5f, saturationMax: 1f,
valueMin: 0.25f, valueMax: 1f,
alphaMin: 1f, alphaMax: 1f
));
shape.AngularVelocity =
Random.onUnitSphere * spawnConfig.angularSpeed.RandomValueInRange;

…
}```

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.

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.

```using UnityEditor;
using UnityEngine;

public class FloatRangeDrawer : PropertyDrawer {}```

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`.

```[CustomPropertyDrawer(typeof(FloatRange))]
public class FloatRangeDrawer : PropertyDrawer {}```

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.

```	public override void OnGUI (
Rect position, SerializedProperty property, GUIContent label
) {}```

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.

```	public override void OnGUI (
Rect position, SerializedProperty property, GUIContent label
) {
EditorGUI.BeginProperty(position, label, property);
EditorGUI.EndProperty();
}```

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.

```		EditorGUI.BeginProperty(position, label, property);
EditorGUI.PropertyField(position, property.FindPropertyRelative("min"));
EditorGUI.EndProperty();```

We end up with the min value for each range, fully editable. Let's add the max values as well, using the same approach.

```		EditorGUI.BeginProperty(position, label, property);
EditorGUI.PropertyField(position, property.FindPropertyRelative("min"));
EditorGUI.PropertyField(position, property.FindPropertyRelative("max"));
EditorGUI.EndProperty();```

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.

```		EditorGUI.BeginProperty(position, label, property);
position.width = position.width / 2f;
EditorGUI.PropertyField(position, property.FindPropertyRelative("min"));
position.x += position.width;
EditorGUI.PropertyField(position, property.FindPropertyRelative("max"));
EditorGUI.EndProperty();```

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.

```		EditorGUI.BeginProperty(position, label, property);
position = EditorGUI.PrefixLabel(position, label);
position.width = position.width / 2f;```

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.

```		position.width = position.width / 2f;
EditorGUIUtility.labelWidth = position.width / 2f;```

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.

```		EditorGUIUtility.labelWidth = position.width / 2f;
EditorGUI.indentLevel = 1;```

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.

```		position = EditorGUI.PrefixLabel(
position, GUIUtility.GetControlID(FocusType.Passive), label
);```

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.

```		int originalIndentLevel = EditorGUI.indentLevel;
float originalLabelWidth = EditorGUIUtility.labelWidth;
EditorGUI.BeginProperty(position, label, property);

…

EditorGUI.EndProperty();
EditorGUI.indentLevel = originalIndentLevel;
EditorGUIUtility.labelWidth = originalLabelWidth;```

### 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.

```using UnityEngine;

[System.Serializable]
public struct ColorRangeHSV {

public FloatRange hue, saturation, value;

public Color RandomInRange {
get {
return Random.ColorHSV(
hue.min, hue.max,
saturation.min, saturation.max,
value.min, value.max,
1f, 1f
);
}
}
}```

Adding color configuration to `SpawnConfiguration` is now simply a matter of adding a `ColorRangeHSV` field to it.

```	public struct SpawnConfiguration {

…

public ColorRangeHSV color;
}```

Now `ConfigureSpawn` can use the new property instead of worrying about the details of creating a random color.

```		//shape.SetColor(Random.ColorHSV(
//	hueMin: 0f, hueMax: 1f,
//	saturationMin: 0.5f, saturationMax: 1f,
//	valueMin: 0.25f, valueMax: 1f,
//	alphaMin: 1f, alphaMax: 1f
//));
shape.SetColor(spawnConfig.color.RandomInRange);```

### 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;```

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.

```using UnityEngine;

public class FloatRangeSliderAttribute : PropertyAttribute {

public float Min { get; private set; }

public float Max { get; private set; }
}```

Add a constructor method that has the minimum and maximum as parameters, to initialize the properties. To keep the range sensible, enforce that the maximum isn't less than the minimum.

```	public FloatRangeSliderAttribute (float min, float max) {
if (max < min) {
max = min;
}
Min = min;
Max = max;
}```

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.

```using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(FloatRangeSliderAttribute))]
public class FloatRangeSliderDrawer : PropertyDrawer {

public override void OnGUI (
Rect position, SerializedProperty property, GUIContent label
) {
EditorGUI.BeginProperty(position, label, property);
EditorGUI.EndProperty();
}
}```

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.

```		EditorGUI.BeginProperty(position, label, property);
SerializedProperty minProperty = property.FindPropertyRelative("min");
SerializedProperty maxProperty = property.FindPropertyRelative("max");
EditorGUI.EndProperty();```

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.

```		EditorGUI.BeginProperty(position, label, property);
SerializedProperty minProperty = property.FindPropertyRelative("min");
SerializedProperty maxProperty = property.FindPropertyRelative("max");
float minValue = minProperty.floatValue;
float maxValue = maxProperty.floatValue;
minProperty.floatValue = minValue;
maxProperty.floatValue = maxValue;
EditorGUI.EndProperty();```

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`.

```		float minValue = minProperty.floatValue;
float maxValue = maxProperty.floatValue;
FloatRangeSliderAttribute limit = attribute as FloatRangeSliderAttribute;
minProperty.floatValue = minValue;
maxProperty.floatValue = maxValue;```

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.

```		FloatRangeSliderAttribute limit = attribute as FloatRangeSliderAttribute;
EditorGUI.MinMaxSlider(
position, label, ref minValue, ref maxValue, limit.Min, limit.Max
);```

### 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`.

```		EditorGUI.MinMaxSlider(
//position, label, ref minValue, ref maxValue, limit.Min, limit.Max
position, ref minValue, ref maxValue, limit.Min, limit.Max
);```

Next, we have to draw the label separately, using `PrefixLabel` as before. Also, we don't want the indent level to mess with our layout, so set it to zero after the label, and reset it when we're done.

```		int originalIndentLevel = EditorGUI.indentLevel;
EditorGUI.BeginProperty(position, label, property);

position = EditorGUI.PrefixLabel(
position, GUIUtility.GetControlID(FocusType.Passive), label
);
EditorGUI.indentLevel = 0;
…

EditorGUI.EndProperty();
EditorGUI.indentLevel = originalIndentLevel;```

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.

```		float minValue = minProperty.floatValue;
float maxValue = maxProperty.floatValue;
position.width /= 3;
minValue = EditorGUI.FloatField(position, minValue);
position.x += position.width;
FloatRangeSliderAttribute limit = attribute as FloatRangeSliderAttribute;
EditorGUI.MinMaxSlider(
position, ref minValue, ref maxValue, limit.Min, limit.Max
);
position.x += position.width;
maxValue = EditorGUI.FloatField(position, maxValue);
minProperty.floatValue = minValue;
maxProperty.floatValue = maxValue;```

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.

```		float fieldWidth = position.width / 4f - 4f;
float sliderWidth = position.width / 2f;
position.width = fieldWidth;
minValue = EditorGUI.FloatField(position, minValue);
position.x += fieldWidth + 4f;
position.width = sliderWidth;
FloatRangeSliderAttribute limit = attribute as FloatRangeSliderAttribute;
EditorGUI.MinMaxSlider(
position, ref minValue, ref maxValue, limit.Min, limit.Max
);
position.x += sliderWidth + 4f;
position.width = fieldWidth;
maxValue = EditorGUI.FloatField(position, maxValue);```

We wrap up by enforcing that the direct input fields cannot go beyond the limits and that max is never less than min.

```		maxValue = EditorGUI.FloatField(position, maxValue);
if (minValue < limit.Min) {
minValue = limit.Min;
}
if (maxValue < minValue) {
maxValue = minValue;
}
else if (maxValue > limit.Max) {
maxValue = limit.Max;
}
minProperty.floatValue = minValue;
maxProperty.floatValue = maxValue;```

The next tutorial is More Factories.

repository PDF