Prefabs and Serialization
Component-based GameObjects are a really cool concept. However, when it comes to setting one up, you’ll have a lot more to do than in a classic inheritance tree approach. In the latter one, something like that might be sufficient:
// Create and register a new Tank
Tank myNewTank = new Tank("/blueprints/some_tank_data.ini");
SomeManager.Instance.Register(myNewTank);
When trying to do the same with a component-based approach, there’s a lot more to do: Create a GameObject, then create needed Components one by one, setting their properties to the appropriate values.. wouldn’t a shortcut be handy? This is where Prefabs come in.
Prefab stands for “prefabricated” and is just that: An already set-up, ready-to-use GameObject with all its Components and all values as they are intended to be. The idea is to set up a GameObject as needed once, then save it in some form and instantiate others by cloning it. In our example, we might create a “Tank”-Prefab which we use as a shortcut to create a new tank.
But prefabs need to be a little bit more than just cloneable game objects. Imagine you are developing a game, have set up some Prefabs and build some levels using instantiated versions of them. Just after finishing the last one, you decide to modify a property of an enemy type. In a poor Prefab implementation, you’d have to modify every single instance of that enemy in every level by hand, in addition to the Prefab itsself. Why? Because they all were just clones and existed on their own after instantiating them.
A possible solution to this problem is to use “Replacement dummies” in each level, instead of actual GameObject instances: When loading the level, a replacement dummy does the only thing it is able to and replaces itsself with a specified Prefab. This way, you can modify a Prefab later on and any change will be propagated to all levels and object instances automatically. Though, if you planned to do a WYSIWYG-Editor, there will be additional hassle making it work with those dummy objects. But there’s something worse: Imagine you want some of the placed enemies to be in a different color and some of them having a special particle effect. There’s no way to customize a GameObject properly if it doesn’t really exist yet i.e. is only present as a replacement dummy.
The solution I have chosen seems to be a little better: GameObjects will be instantiated as clones, but maintain a PrefabLink object that will be saved instead of the actual object. Once it is loaded, it instantiates the base Prefab and applies overwritten values to the GameObject. That way, it is possible to instantiate GameObjects using Prefabs, modify them as needed but still maintain a connection in order to globally update its properties. As Prefabs are considered constant at runtime, the PrefabLink may destroy itsself after resolving, so there should be no ingame overhead.
I’m not there yet. Prefabs are mostly a pure concept right now as I’m still experimenting with object serialization in general. If you are developing in .Net and considering to write your own serializer, you should take a look at System.Runtime.Serialization first. Especially the BinaryFormatter rocks. When I evaluated it, I expected a Reflection-driven something that walks through an objects fields and sequentially saves or loads them. Well, that is apparently true. What I didn’t expect was it to automatically detect multiple references to one same object (won’t clone the object when deserializing for each reference) and is even able to cope with a completely changed object data structure. You can save an object, then remove some of its fields in code, add some others, recompile and still deserialize the old data into a working object without any problems. The only downside of the BinaryFormatter is, that it’s not so easy to customize. All you can do is tell it to “ignore this field” or completely override a classes whole serialization at once. There is nothing inbetween. I’m especially missing a “consider this a weak reference” field attribute to prevent it from just pulling any object into the serialization tree that is unlucky enough to be randomly referenced from somewhere inside.