Adam's blog formerly game development, now just casual madness

Clone Wars

Tightly connected to Prefabs is the ability to clone GameObjects. If a Prefab is just a boxed and isolated GameObject hierarchy, instantiating a GameObject from a Prefab means to create a deep copy of the internal GameObject and pass the cloned version as a return value. Cloning an object might also come in handy for actual ingame usage, but either way, the possibility to do that is absolutely necessary.

But how?

There are several ways to clone objects. The first thing that came into my mind was “Why do the same work twice? I can already serialize GameObjects!”, so I abused the binary Serialization to clone an object: Serialize it into a MemoryStream and right after that, rewind the stream and deserialize from it. And there it was! A deep clone in a few lines:

using (MemoryStream str = new MemoryStream())
	BinaryFormatter formatter = new BinaryFormatter();
	formatter.Serialize(str, this);
	str.Seek(0, SeekOrigin.Begin);
	return formatter.Deserialize(str);

Great, isn’t it? Well, not at second glance. This here is not what my cloning implementation looks like by now and there is a reason for it. First of all, it’s slow as hell. In a game environment where instancing an object means cloning another one, it has to be really fast. This approach here gives me a duration of 2000 ms for cloning 5000 objects, which is clearly too much. The second downside is: There is almost no way to customize anything and all referenced objects are pulled into the serialization tree. When cloning a GameObject, it will clone the whole hierarchy both up- and downwards and you can’t do much about it.

Time for a different approach. My next idea was to add a virtual Clone method to GameObject and Component. However, it is part of the concept to keep away fiddling with data structure details from the end user. As he will derive his own Components from the base class, this approach would force him to take care of each and every little field he might add to his custom Component. In most cases, these fields will contain int, string, collections and other relatively simple objects.. which is a fact that renders this extra work pretty unnecessary. I didn’t like the idea.

The other extreme would be fully automatic cloning using reflection. Though, deep-copying everything would pull objects into the reference tree that clearly do not belong there. On the other hand, shallow-copying everything would lead to problems with “two Components referencing the same List<int>”. So, a fully automated clone machine won’t work out because of the systems inability to handle all possible special cases properly without getting really ugly – and probably breaking on behalf of its own complexity.

Approach #3: Failed. However, I think I’m getting somewhere now. My current implementation is a mix between #2 and #3: GameObject and all Components I do define in the engine assembly itsself (e.g. all common / basic Components) implement a virtual cloning method explicitly in which I manually assign values and create sub-objects. This happens behind the curtain and someone who uses the library will never notice it – he won’t even be able to override the virtual method himself, because it is flagged internal and thus invisible outside.

All GameObjects and Components I will define myself in the engine core have a well-defined and fast cloning behaviour; if a user wants to specify the cloning behaviour for one of his custom Components he may override an additional method the Component class exposes to him. If he does not, the default implementation of it kicks in and serializes his custom Component’s fields automatically. The default algorithm copies all primitive or object reference fields directly by assignment operation and deep-copies anything that implement ICollection – but not its contents. Consider the following data:

private int myInt;
private string myString;
private GameObject myObjectRef;
private List myIntList;
private Dictionary<object,List> myStuff;

The fields myInt, myString and myObjectRef are just assigned and not handled specially. If we did the same with myIntList, we’d have a problem. Fortunately, it implements ICollection, so myIntList is deep-copied. When processing myStuff, we will get a new Dictionary containing references to (not cloned) objects and cloned Lists of references to (not cloned) Components. This way, the default behaviour should be sufficient for most cases and almost never need to be overwritten.

71 of 74