Instantiate() And Cloning Private Serialized Data - And The Prototype Pattern

Howdy,

This post is mainly about the question at the end, but needs some context first.

I recently started reading Robert Nystrom’s Game Programming Patterns book, and one of the patterns mentioned which caught my attention was the “Prototype” pattern. Nystrom summarises the key idea of the pattern as:

An object can spawn other objects similar to itself… Any monster can be used as a prototypal monster used to generate other versions of itself.

This instantly reminded me of the Object.Instantiate() method, which I’ve probably called a million times. However, reading the chapter made me think of other ways of using it. Especially when it comes to instantiating based on Prefabs.

When I use Instantiate() to create a new GameObject in a Scene, I would usually pass in a Prefab asset as the object to be cloned. It’s then often necessary for me to use several lines of code or a method to set values on the new GameObject using data and references from the Scene. (I’m sure as beginners we’ve all erroneously tried to avoid this by dragging Scene GameObject references into a Prefab, and been frustrated before learning why it doesn’t work)

My realisation while reading the book is that there could be a much simpler way. You could drag a Prefab instance into the Scene, initialise its references in the Inspector, and disable it to prevent it from doing anything. Then you pass that instance into Instantiate() instead, so that any new GameObjects already have Scene data references. You still have the convenience of the Prefab connection, but you no longer have to code the fetching of references from the Scene if all the instances are expected to share some reference.

Probably this is not a new idea to many of you, but it’s completely flown past me thus far. However, in trying this idea out I struggled with a bug that was tough to track down - my code only worked as intended if I applied the [SerializeField] attribute to a particular private (MonoBehaviour) variable. Without that attribute, the cloning produced weird results.

It was a nightmare to find the cause, partly because I thought I was only adding and removing this attribute so I could see data in the Inspector for my own debugging purposes. I did not consider that this attribute itself could be the cause of such a massive behavioural change in Play mode. I perhaps somewhat naively think of that attribute as more of an Editor thing, even if it does live in the UnityEngine namespace.


So, the question is basically, why exactly does this happen?

The obvious guess to me is that if the variable is private, and it is not serialized, then its value cannot be included in the cloning process, right? Are there any other important details, or reasons to be wary of the pattern described above? My use case involved cloning some GameObjects, then giving them children via Instantiate() too. I realised sometimes the children were cloned, sometimes they weren’t.

Finally, I had a look at the documentation pages for both Instantiate() and [SerializeField], but couldn’t find anything on either that might’ve forewarned me of this behaviour. I also had a quick reread of the Script Serialization manual page, but couldn’t find this detail. Would it be possible to get something about this in there?

Yeah, it’s a fun pattern, SUPER good to speed up initial quick implementations, especially good for GameJam contexts, and honestly it’s can scale up quite nicely if you have lots of scenes additively loaded.

I sometimes make a pure Assets scene and embed a script that is the central accessing point to get at these prefabs, almost from a service standpoint: instead of reaching into the serialized fields, instead I make an API where I can say “Give me the current player bullet” and instantiate that.

I’ll take your word on it but I swear at some point reading official docs that stated exactly what Instantiate() would pick up and what it wouldn’t…

EDIT: my working thought of it is that Instantiate will copy exactly what you could see in the inspector window, no other values, but honestly there could be edge-casey stuff what with how ScriptableObjects actually persist private values unless you tag them as [NonSerialized] . I suppose he moral of the story is to do quick mini-validation tests, as well as to be antagonistic towards your test assumptions, eg, test to success, test to failure, then test back to success another way to make sure you’re not seeing a false serialization ghost.

1 Like

I wouldn’t be surprised if Unity only copies over explicitly user-serialized data, not including data that it would soft-serialize during the usual re-serialisation process.

Can’t find any literature on this however.

Can’t find any literature on this however.

This is my main concern. It was a surprise at first to realise that Instantiate() and [SerializeField] might interact, but it wasn’t too shocking after some thought.

What is more unpleasant is being unable to find any precise information about Instantiate() and serialisation, because I feel like one day it’s going to find some other way to take me by surprise yet again.