This is the eighth tutorial in a series about Object Management. It introduces the concept of working with more than one factory, along with more complex shapes.

This tutorial is made with Unity 2017.4.12f1.

More Shapes

Cubes, spheres, and capsules aren't the only shapes that we can use. We could import any mesh. Also, shapes need not consist of a single object. A shape can have an object hierarchy of its own, with multiple meshes, animations, behaviors, and other things. To illustrate this, we'll create a few composite shapes by combining multiple default meshes.

Cube With Sphere We begin by simply combining a single cube with a sphere. First create a cube object, followed by a sphere, both positioned at the origin. Then make the sphere a child of the cube. At their default scales the sphere is hidden inside the cube. Increase the sphere's scale so it intersects the cube's faces. At scale √2 the sphere would touch the edges of the cube. Using a smaller scale—like 1.35—gives us bulges on each of the cube's faces.

A cube with sphere poking out. To turn it into a proper shape, add the Shape component to the root cube object. Also set the materials of both objects to the same white material used by all other shapes. Then turn it into a prefab.

Composite Capsule A more complex shape can be made by combining three rotated capsules. Begin with a default capsule, then give it two child capsules. Rotate the children 90°, one around its X axis and the other around its Z axis. The result is a rounded shape with six protrusions along the main axes, somewhat like the previous shape but without the cube.

A composite capsule. Again add the Shape component to the root capsule and set the materials, and then turn it into a prefab.

Composite Cube For our final composite shape, we do the same but now with a cube and two cube children. In this case, rotate the children 45° along two axes, XY for one and YZ for the other. This creates one of the cube 3-compound variants, a complex shape with cross-like extrusions.

A composite cube. Add the Shape component to the root cube and turn it into a prefab too.

Spawning the New Shapes To make it possible to spawn these new shapes, all we have to do is add them to our factory. Shape factory with six shape prefabs. From now on the new shapes can be spawned along with the old ones. But they appear mostly white, because only the root object with the Shape component gets a random material and color. The child objects are unaffected. New shapes remain mostly white.

Configuring Which Renderers to Adjust To change the color and material of all objects that are part of a composite shape, Shape needs to access all the relevant MeshRenderer components. Let's give it a configurable array for this purpose. [SerializeField] MeshRenderer[] meshRenderers; Now we have to go through all shape prefabs and manually include all renderers that need to be affected. Note that this makes it possible to exclude some on purpose, so some parts of a shape can have a fixed material. You can directly drag an object onto the array, Unity will turn it into a reference to its renderer. Mesh renderers for composite capsule. Shape no longer needs to retrieve a single renderer component when it awakens, so the meshRenderer field and the Awake method can be removed. //MeshRenderer meshRenderer; //void Awake () { // meshRenderer = GetComponent<MeshRenderer>(); //} In SetMaterial , we must loop through all renderers and set their material to the provided one. public void SetMaterial (Material material, int materialId) { for (int i = 0; i < meshRenderers.Length; i++) { meshRenderers[i] .material = material; } MaterialId = materialId; } And the same goes for SetColor . public void SetColor (Color color) { this.color = color; if (sharedPropertyBlock == null) { sharedPropertyBlock = new MaterialPropertyBlock(); } sharedPropertyBlock.SetColor(colorPropertyId, color); for (int i = 0; i < meshRenderers.Length; i++) { meshRenderers[i] .SetPropertyBlock(sharedPropertyBlock); } } Fully colored composite shapes.

Nonuniform Color We now end up with uniformly colored composite shapes, assuming all their renderers were setup to be affected. But we don't have to limit ourselves to a single color per shape. Let's make it possible for each part of a composite shape to have its own color. To support multiple colors per shape while still being able to save it correctly, we have to replace the color field with a colors array. The array should be created when the shape awakens, its length being the same as the length of the meshRenderers array. So we again need an Awake method. //Color color; Color[] colors; void Awake () { colors = new Color[meshRenderers.Length]; } When a color is configured via SetColor , all elements of the colors array have to be set too. public void SetColor (Color color) { //this.color = color; if (sharedPropertyBlock == null) { sharedPropertyBlock = new MaterialPropertyBlock(); } sharedPropertyBlock.SetColor(colorPropertyId, color); for (int i = 0; i < meshRenderers.Length; i++) { colors[i] = color; meshRenderers[i].SetPropertyBlock(sharedPropertyBlock); } } But that still makes all colors the same. To support different colors per renderer, add a variant SetColor method that only adjusts a single color element, identified via an index parameter. public void SetColor (Color color, int index) { if (sharedPropertyBlock == null) { sharedPropertyBlock = new MaterialPropertyBlock(); } sharedPropertyBlock.SetColor(colorPropertyId, color); colors[index] = color; meshRenderers[index].SetPropertyBlock(sharedPropertyBlock); } That requires outside knowledge of how many colors there are, so add a public ColorCount getter property, which simply returns the length of the colors array. public int ColorCount { get { return colors.Length; } }

Saving all Colors Our code doesn't compile yet, because we also have to change how the color data is saved. First, increase the save version in Game to 5. const int saveVersion = 5 ; Then adjust Shape.Save so it writes all its colors instead of the old color field. public override void Save (GameDataWriter writer) { base.Save(writer); //writer.Write(color); for (int i = 0; i < colors.Length; i++) { writer.Write(colors[i]); } writer.Write(AngularVelocity); writer.Write(Velocity); } When loading, we now have to read a color and invoke SetColor for each element, if we're loading a version 5 or higher file. Otherwise, we set a single color, as before. public override void Load (GameDataReader reader) { base.Load(reader); if (reader.Version >= 5) { for (int i = 0; i < colors.Length; i++) { SetColor(reader.ReadColor(), i); } } else { SetColor(reader.Version > 0 ? reader.ReadColor() : Color.white); } AngularVelocity = reader.Version >= 4 ? reader.ReadVector3() : Vector3.zero; Velocity = reader.Version >= 4 ? reader.ReadVector3() : Vector3.zero; }

Optional Uniform Color Whether shapes should have a uniform color or not is something that can be decided per spawn zone. So add a uniformColor toggle to SpawnZone.SpawnConfiguration . public struct SpawnConfiguration { … public bool uniformColor; } When we configure a freshly-spawned shape and we don't want uniform colors, instead pick a random color for each color index. public virtual void ConfigureSpawn (Shape shape) { Transform t = shape.transform; t.localPosition = SpawnPoint; t.localRotation = Random.rotation; t.localScale = Vector3.one * spawnConfig.scale.RandomValueInRange; if (spawnConfig.uniformColor) { shape.SetColor(spawnConfig.color.RandomInRange); } else { for (int i = 0; i < shape.ColorCount; i++) { shape.SetColor(spawnConfig.color.RandomInRange, i); } } shape.AngularVelocity = Random.onUnitSphere * spawnConfig.angularSpeed.RandomValueInRange; … }

Shapes with nonuniform color. Is it possible to use the same hue per shape? Yes, you could randomly pick hue once for the entire shape, while saturation and value remain random. You could control this with another configuration option. In fact, instead of a single uniform color toggle, you could use three separate toggles for hue, saturation, and value. Of course that would make the code to set the colors a bit more complex.