AnyLearn
All lessons
Programmingintermediate

Unity: prefabs, assets, and serialization

How a Unity object survives being saved to disk and loaded back exactly as configured. Covers what serialization actually accepts, why GUIDs in .meta files matter more than filenames, prefabs and variants with their override rules, ScriptableObjects as shared data, and the field that keeps reverting to zero.

Updated · AI-authored, review-gated · how lessons are made

Not signed in: your progress and quiz score won't be saved.
Progress1 / 9

The Inspector is a serializer

Drag a value into the Inspector, press play, stop, and it is still there. That is not the Editor remembering. It is serialization: Unity walks your object, finds every field it knows how to store, and writes them into the scene or prefab file on disk.

While editing, those files are YAML text, which is why Unity projects can live in git and produce readable diffs. At runtime in a build they are packed binary for speed. Same data, different encoding.

This one system underpins far more than saving. The Inspector renders whatever is serialized. Prefabs are serialized objects. Undo works by serializing state. Entering play mode reloads your scene from serialized data, which is exactly why changes made during play are discarded when you stop.

Once you see the Inspector as a view onto a serializer, most of Unity's strange behavior becomes predictable.

Full lesson text

All 9 steps on one page, for reading, reference, and search.

Show

1. The Inspector is a serializer

Drag a value into the Inspector, press play, stop, and it is still there. That is not the Editor remembering. It is serialization: Unity walks your object, finds every field it knows how to store, and writes them into the scene or prefab file on disk.

While editing, those files are YAML text, which is why Unity projects can live in git and produce readable diffs. At runtime in a build they are packed binary for speed. Same data, different encoding.

This one system underpins far more than saving. The Inspector renders whatever is serialized. Prefabs are serialized objects. Undo works by serializing state. Entering play mode reloads your scene from serialized data, which is exactly why changes made during play are discarded when you stop.

Once you see the Inspector as a view onto a serializer, most of Unity's strange behavior becomes predictable.

2. What Unity will and will not serialize

The rules are strict and unforgiving, and breaking one fails silently.

A field is serialized when it is public, or private marked [SerializeField], and its type is supported: primitives, string, enums, Unity structs like Vector3, anything deriving from UnityEngine.Object, plus arrays and List<T> of those.

public int health = 100;                 // serialized
[SerializeField] private float speed;    // serialized, still private
[System.Serializable]                    // required for your own classes
public class Loadout { public string name; }
public Loadout kit;                      // serialized

private int internalCounter;             // NOT: private, no attribute
public Dictionary<string,int> map;       // NOT: dictionaries unsupported
public int? maybe;                       // NOT: nullables unsupported
[System.NonSerialized] public int temp;  // opted out explicitly

[SerializeField] private is the idiom worth adopting: the Inspector can edit it, other code cannot. Public fields expose your internals to the whole project just to get a slider.

3. The dictionary problem, and the field that resets

Two consequences of those rules bite constantly.

Dictionaries do not serialize. Populate one in Awake and it works; expect it to persist in the Inspector and it will not. The standard workaround is to serialize two parallel lists and rebuild the dictionary at load, which is what ISerializationCallbackReceiver is for.

The initializer that keeps losing. This is the classic:

public int health = 100;   // you set 25 in the Inspector

You later "fix" the default to 100 in code and nothing changes in the scene. The field still shows 25. That is correct behavior: the serialized value wins over the initializer. Once the field is saved on disk, the code default is only used for objects created fresh from that script. Editing the initializer cannot retroactively change objects already carrying a stored value.

The fix is to change it in the Inspector, or use the context menu's Reset.

4. GUIDs: why .meta files matter

Every asset in the project gets a sibling .meta file, and inside it is a GUID, a stable random identifier. The Asset Database maps GUID to file path.

This is the crucial part: when a scene references a texture, it does not store "Art/hero.png". It stores the texture's GUID plus a fileID for the sub-object. The filename is not the identity.

That design is why you can rename hero.png to protagonist.png, move it into another folder, and every reference still resolves. The GUID never moved.

It is also why two rules exist:

  • Commit .meta files. Gitignore them and every reference in the project breaks for your teammates, because their Unity generates fresh GUIDs.
  • Move and rename assets inside the Editor, not in Explorer or Finder. The Editor keeps the file and its .meta together. Moving the file alone orphans the meta and Unity mints a new GUID, silently turning every reference into a missing one.

5. Prefabs: one definition, many instances

A prefab is a GameObject, with all its components and children and their serialized values, saved as an asset. Drop it into a scene and you get an instance that keeps a link back to the source.

Edit the prefab asset and every instance in every scene updates. That is the payoff: one enemy definition, four hundred instances, one place to change.

[SerializeField] private GameObject enemyPrefab;   // the asset

private void SpawnWave()
{
    for (int i = 0; i < 10; i++)
        Instantiate(enemyPrefab, RandomPoint(), Quaternion.identity);
}

Instantiate deserializes the prefab into a live object, so it is a real cost: at scale, pool objects instead of spawning and destroying them.

Note what a prefab is not: a class. It is data. Two prefabs sharing a script are the same code with different serialized values, which is precisely how designers tune a game without touching C#.

6. Overrides and variants

Change a value on an instance and it becomes an override: shown bold in the Inspector, stored in the scene, and from then on that property stops tracking the prefab. Change the prefab's value later and the overridden instance ignores it. This is the source of the eternal "why did only some enemies update".

The Inspector's Overrides dropdown lets you Apply an override back to the prefab, or Revert it to resume tracking.

A prefab variant formalizes the same idea. It inherits from a base prefab and stores a deliberate set of overrides, so an ArmoredEnemy variant carries only its differences and inherits every other fix made to Enemy.

instance overridevariantseparate prefab
tracks base changesexcept overridden fieldsexcept overridden fieldsno
lives inthe sceneits own assetits own asset
reusablenoyesyes
use forone-off tweaka recurring flavora genuinely different thing

7. ScriptableObjects: data without a scene

A MonoBehaviour must live on a GameObject in a scene. A ScriptableObject is data that exists as its own .asset file, attached to nothing.

[CreateAssetMenu(menuName = "Game/Weapon")]
public class WeaponData : ScriptableObject
{
    public string displayName;
    public int damage = 10;
    public AudioClip fireSound;
}

public class Gun : MonoBehaviour
{
    [SerializeField] private WeaponData data;   // dragged in, shared
    private void Fire() => target.Damage(data.damage);
}

Create Rifle.asset and Shotgun.asset from the menu, drag either into any Gun. Balance changes happen in one file, not across forty prefabs.

The difference that matters: prefabs holding the same values duplicate them per instance; a ScriptableObject reference is shared, one copy in memory for all users. That saves real memory for shared config, and it is a trap for mutable state, since writing to it at runtime edits the asset for everyone, and in the Editor those writes persist after you stop playing.

8. Where teams actually lose time

Three failure modes account for most serialization pain, and all three are avoidable.

Merge conflicts in scenes. Scenes are one big YAML file, so two people editing the same scene produces a conflict that is close to unmergeable by hand. The mitigation is structural: keep scenes thin and put content in prefabs, so people edit separate files.

Renaming a field wipes its data. Serialization matches on the field name. Rename speed to moveSpeed and Unity looks for moveSpeed, finds nothing, and gives you the default. The saved value is not gone, it is just orphaned under the old key. [FormerlySerializedAs("speed")] tells Unity where to look and preserves it.

Missing script references. Delete or move a .cs file outside the Editor and every object using it shows "Missing (Mono Script)" with its data stranded, because the reference was to the script's GUID.

The pattern: serialization keys off names and GUIDs, so anything that breaks a name or a GUID breaks data.

9. What points at what

References are stored as GUIDs, not paths, which is why renaming is safe. A scene holds instances that link back to a prefab asset, and several prefabs can share one ScriptableObject.

flowchart TD
  A["Scene .unity (YAML)"] --> B["Enemy instance + its overrides"]
  B --> C["Enemy.prefab asset"]
  C --> D["Enemy.prefab.meta holds the GUID"]
  C --> E["ArmoredEnemy variant: only the differences"]
  C --> F["Gun component"]
  F --> G["Rifle.asset (ScriptableObject), shared"]
  H["another prefab"] --> G

Check your understanding

The lesson ends with a 5-question quiz. Take it in the player above to see your score.

  1. Which field will Unity serialize and show in the Inspector?
    • `private int score;`
    • `[SerializeField] private float speed;`
    • `public Dictionary<string,int> lookup;`
    • `[System.NonSerialized] public int temp;`
  2. You change `public int health = 100;` to `= 200` in code, but the scene object still shows 100. Why?
    • The script needs recompiling before defaults apply
    • Initializers are ignored by Unity entirely
    • The serialized value stored on disk wins over the code initializer
    • health must be marked [SerializeField] to update
  3. Why must `.meta` files be committed to version control?
    • They contain the asset's GUID, which every reference points to
    • They store the compiled shader cache
    • They hold the asset's import time for the profiler
    • They are needed only for textures
  4. You change an enemy instance's speed in a scene, then later change speed on the prefab. What does that instance show?
    • The new prefab value, overwriting the instance
    • Its own overridden value, ignoring the prefab change
    • Whichever was edited most recently
    • It throws a prefab conflict error
  5. Ten Gun prefabs all reference the same `Rifle.asset` ScriptableObject. You change `damage` on it at runtime. What happens?
    • Only the gun that wrote it changes; each holds a copy
    • Nothing; ScriptableObjects are read-only at runtime
    • Unity throws a shared-asset write exception
    • All ten guns see the new damage, since the asset is shared

Related lessons

Programming
intermediate

Unity: the script lifecycle and execution order

When your Unity code actually runs. Covers the Awake, OnEnable, Start ordering guarantees, the split between Update, FixedUpdate, and LateUpdate, why deltaTime is mandatory, how coroutines fit the frame, and the allocation habits that turn a smooth game into a stuttering one.

9 steps·~14 min
Programming
intermediate

Unity: the GameObject and component model

Unity is built on composition, not inheritance: a GameObject is an empty container, and everything it can do comes from components bolted onto it. Learn the Transform and scene graph, how components find each other, the active-versus-enabled trap, and why composition beats a deep class hierarchy for games.

9 steps·~14 min
Programming
intermediate

Unity: physics and the fixed timestep

Why Unity runs physics on its own 50Hz clock instead of the frame rate, and what follows from that. Covers rigidbodies and colliders, moving things correctly with forces rather than the Transform, interpolation for smooth motion, tunneling and continuous collision detection, and where physics cost actually comes from.

9 steps·~14 min
Programming
advanced

The Failure Modes the Design Creates

Reconciliation buys robustness and charges for it in a specific currency: nothing is ever definitely finished, commands succeed without meaning anything worked, and manual fixes are silently undone. This lesson covers the failures that come from the model rather than from bugs, how to debug them, and when the whole thing is the wrong tool.

8 steps·~12 min