This is the second tutorial in a series about Object Management. In this part we'll add support for multiple shapes with varying materials and colors, while remaining backwards compatible with the previous version of our game.

This tutorial is made with Unity 2017.4.1f1.

After giving Game a reference to our factory, it will now create random shapes each time the player spawns a new one, instead of always getting cubes.

When loading, we now also have to use the shape factory. In this case, we do not want random shapes. We have only ever worked with cubes before, so we should get cubes, which is done by invoking shapeFactory.Get(0) .

Let's also rename the instance's variable so it's very explicit that we're dealing with a shape instance, not a prefab reference that we still need to instantiate. Once again, you can use refactoring to quickly and consistently rename the variable.

Because we're now creating shapes in Game , let's be explicit and rename its list to shapes . So everywhere that objects is written, replace it with shapes . It is easiest to use your code editor's refactor functionality to change the field's name, and it will take care of renaming it everywhere that it is used.

Unity's Random.Range method with integer parameters uses an exclusive maximum. The output range is from the minimum to the maximum minus 1. This was done because the typical use case was expected to be getting a random array index, which is exactly what we're doing here.

Besides requesting a specific shape, let's also make it possible to get a random shape instance out of the factory, via a GetRandom method. We can use the Random.Range method to select an index at random.

We can directly use the identifier as the index to find the appropriate shape prefab, instantiate it, and return it. This means that 0 identifies a cube, 1 a sphere, and 2 a capsule. Even if we change how the factory works later, we have to make sure that this identification remains the same, to remain backwards compatible.

That is certainly possible, so you could do that instead. But we don't really care about identifying exact shape types in code, so an integer works fine. That makes it possible to control what shapes are supported purely by changing the factory's array contents, without having to change any code.

For the factory to be of any use, there must be a way to get shape instances out of it. So give it a public Get method. The client can indicate what kind of shape it wants via a shape identifier parameter. We'll simply use an integer for this purpose.

After the field appears in the inspector, drag all three shape prefabs onto it, so references to them get added to the array. Make sure that the cube is the first element. Use the sphere for the second element and the capsule for the third.

To let our factory know about the shape prefabs, give it a Shape[] prefabs array field. We don't want this field to be public, as its inner workings should not be exposed to other classes. So keep it private. To have the array show up in the inspector and be saved by Unity, add the SerializeField attribute to it.

You can now create our factory via Assets › Create › Shape Factory. We only need one.

We now have a custom asset type. To add such an asset to our project, we'll have to add an entry for it to Unity's menu. The simplest way to do this is by adding the CreateAssetMenu attribute to our class.

The factory's only responsibility is to deliver shape instances. It doesn't need a position, rotation, or scale, and neither does it need an Update method to change it state. So it doesn't need to be a component, which would have to be attached to a game object. Instead, it can exist on its own, not as part of a specific scene, but as part of the project. In other words, it is an asset. This is possible, by having it extend ScriptableObject instead of MonoBehaviour .

To keep Game simple, we're going to put the responsibility for what shapes are supported in its own class. This class will act like a factory, creating shapes on demand, without its client having to know how those shapes are made or even how many different options there are. We will name this class ShapeFactory .

Currently, Game can only spawn a single thing, because it only has one reference to a prefab. To support all three shapes, it would need three prefab references. This would require three fields, but that wouldn't be flexible. A better approach would be to use an array. But maybe we'll come up with a different way to create shapes later. That could make Game rather complex, as it is also responsible for user input, keeping track of objects, and triggering the saving and loading.

You could also add a cylinder object, but I omitted it because cylinders don't have their own collider type. Instead, they use a capsule collider, which doesn't really fit. That isn't an issue right now, but might be later.

Create a default sphere and capsule object, give each a Shape component, and turn them into prefabs too. These are the other shapes that our game will support.

This breaks the reference that the Game object has to the prefab. But because Shape is also a PersistableObject we can assign it again.

Remove the PersistableObject component from the Cube prefab and give it a Shape component instead. It cannot have both, because we gave PersistableObject the DisallowMultipleComponent attribute, which also applies to Shape .

We're going to be specific about what kind of things our game spawns. It spawns shapes, not generic persistable objects. So create a new Shape class, which represents geometric 3D shapes. It just extends PersistableObject , without adding anything new, at least for now.

The goal of this tutorial is to make our game more interesting, by allowing the creation of other shapes than just white cubes. Just like the position, rotation, and scale, we'll randomize what shape is created each time the player spawns a new one.

Remembering the Shapes

While it is now possible to create three different shapes, this information is not yet saved. So each time we load a saved game, we end up with nothing but cubes. This is correct for games that were saved earlier, but not for games that were saved after we added support for multiple shapes. We also have to add support for saving the different shapes, ideally while still being able to load the old save files as well.

Shape Identifier Property To be able to save which shape an object has, the object has to remember this information. The most straightforward way to do this is by adding a shape identifier field to Shape . public class Shape : PersistableObject { int shapeId; } Ideally, this field is read-only, because a shape instance is always of one type and doesn't change. But it has to be assigned a value somehow. We could mark the private field as serializable and assign it a value via the inspector of each prefab. However, this doesn't guarantee that the identifier matches the array index used by the factory. It might also be possible that we use a shape prefab somewhere else, which has nothing to do with the factory, or maybe even add it to another factory at some point. So the shape identifier depends on the factory, not the prefab. Thus, it's something to be tracked per instance, not per prefab. Private fields do not get serialized by default, so the prefab has nothing to do with it. A new instance will simply get the field's default value, which is 0 in this case, because we didn't gave it another default. To make the identifier publicly accessible, we'll add a ShapeId property to Shape . We use the same name, except the first letter is a capital. Properties are methods that pretend to be fields, so they need a code block. public int ShapeId {} int shapeId; Properties actually need two separate code blocks. One to get the value it represents, and one to set it. These are identified via the get and set keywords. It is possible to only use one of them, but we need both in this this case. public int ShapeId { get {} set {} } The getter part simply returns the private field. The setter simply assigns to the private field. The setter has an implicit parameter of the appropriate type named value for this purpose. public int ShapeId { get { return shapeId; } set { shapeId = value; } } int shapeId; By using a property it becomes possible to add additional logic to what appears to be a simple retrieval or assignment. In our case, the shape identifier has to be set exactly once per instance, when it is instantiated by the factory. Setting it again after that would be a mistake. We can check whether the assignment is correct by verifying that the identifier still has its default value at the time of assignment. If so, the assignment is valid. If not, we log an error instead. public int ShapeId { get { return shapeId; } set { if (shapeId == 0) { shapeId = value; } else { Debug.LogError("Not allowed to change shapeId."); } } } However, 0 is a valid identifier. So we have to use something else as the default value. Let's use the minimum possible integer instead, int.MinValue , which is −2147483648. Also, we should ensure that the identifier cannot be reset to its default value. public int ShapeId { … set { if (shapeId == int.MinValue && value != int.MinValue ) { shapeId = value; } … } } int shapeId = int.MinValue ; Why not just use a readonly property? A readonly field or property can only be assigned a default value, or be assigned to in a constructor method. Unfortunately, we cannot use constructor methods when instantiating Unity objects. So we have to use an approach like this. Adjust ShapeFactory.Get so it sets the identifier of the instance before returning it. public Shape Get (int shapeId) { // return Instantiate(prefabs[shapeId]); Shape instance = Instantiate(prefabs[shapeId]); instance.ShapeId = shapeId; return instance; }

Identifying the File Version We didn't have shape identifiers before, so we didn't save them. If we save them from now on, we're using a different save file format. It's fine if the old version of our game—from the previous tutorial—cannot read this format, but we should ensure that the new game can still work with the old format. We'll use a save version number to identify the format used by a save file. As we introduce this concept now, we start with version 1. Add this as a constant integer to Game . const int saveVersion = 1; What does const mean? It declares a simple value to be a constant, not a field. It cannot be changed and doesn't exist in memory. Instead, it's only part of the code and its explicit value gets used wherever it is referenced, substituted during compilation. When saving the game, start with writing the save version number. When loading, begin by reading the stored version. This tells us what version we're dealing with. public override void Save (GameDataWriter writer) { writer.Write(saveVersion); writer.Write(shapes.Count); … } public override void Load (GameDataReader reader) { int version = reader.ReadInt(); int count = reader.ReadInt(); … } However, this only works for files that contain the save version. The old save files from the previous tutorial don't have this information. Instead, the first thing written to those files is the object count. So we'd end up interpreting the count as the version. The object count stored in old save files could be anything, but it will always be at least zero. We can use this to distinguish between the save version and the object count. This is done by not writing the save version verbatim. Instead, flip the sign of the version when writing it. As we start with 1, this means that the stored save version is always less than zero. writer.Write( - saveVersion); When reading the version, flip its sign again to retrieve the original number. If we're reading and old save file, this ends up flipping the sign of the count, so it becomes either zero or negative. Thus, when we end up with a version less than or equal to zero, we know that we're dealing with an old file. In that case, we already have the count, just with a flipped sign. Otherwise, we still have to read the count. int version = - reader.ReadInt(); int count = version <= 0 ? -version : reader.ReadInt(); What does the question mark mean? It is the ternary operator, condition ? trueResult : falseResult , which is a shorthand alternative for an if-else expression. In this case, the code is equivalent to the following: int version = -reader.ReadInt(); int count; if (version <= 0) { count = -version; } else { count = reader.ReadInt(); } This makes it possible for the new code to deal with the old save file format. But the old code cannot deal with the new format. We cannot do anything about that, because the old code has already been written. What we can do is make sure that from now on the game will refuse to load future save file formats that it doesn't know how to deal with. If the loaded version is higher than our current save version, log an error and return immediately. int version = -reader.ReadInt(); if (version > saveVersion) { Debug.LogError("Unsupported future save version " + version); return; }

Saving the Shape Identifier A shape should not write its own identifier, because it has to be read to determine which shape to instantiate, and only after that the shape can load itself. So it's the responsibility of Game to write the identifiers. Because we're storing all shapes in a single list, we have to write each shape's identifier before the shape saves itself. public override void Save (GameDataWriter writer) { writer.Write(-saveVersion); writer.Write(shapes.Count); for (int i = 0; i < shapes.Count; i++) { writer.Write(shapes[i].ShapeId); shapes[i].Save(writer); } } Note that this is not the only way to save the shape identifiers. For example, it is also possible to use a separate list for each shape type. In that case, it would only be necessary to write each shape identifier once per list.