Unity6 Game Development · Lesson 9

Serialized fields and runtime state

Treat Inspector data as stored configuration, not as a live source initializer.

Unity 6 Editor walkthrough

The Unity script needs the Editor and the setup listed below. The separate C# companion practice runs in SharpRune without UnityEngine.

Serialized configuration is stored instance data

A SerializeField exposes a supported private field to Unity’s serialization and Inspector system. Its stored scene or prefab value can differ from the source initializer because an existing instance already has serialized data. InspectorConfig uses startingAmmo as configuration and copies a validated value into currentAmmo during Awake. Those two quantities then have different owners and lifetimes. A public C# property is not automatically serialized like a supported field, and a field’s visibility alone is not a complete description of Unity’s serialization rules.

Follow configuration into the runtime copy

Set startingAmmo to three, enter Play mode and invoke Use One Round. Awake initially copied the configured value into currentAmmo; the context action decrements only currentAmmo and logs two. The configured field remains three. The Play-mode guard prevents this action from acting as an accidental Edit-mode runtime simulation. OnValidate clamps a negative configured field, while Awake also protects the runtime starting value. This makes a data-validation boundary visible without creating scene objects or performing unrelated engine work inside an Editor validation callback.

Three printed integers represent three different facts

The portable lab stores configured three, derives current three and subtracts one with a zero clamp. It prints current two, configured three and the result zero from clamping a separate negative value. This is ordinary local arithmetic and ownership, not a serializer implementation. The separate variables matter: changing a copied runtime value does not automatically rewrite the configuration from which it began. A class or shared reference could introduce a different relationship, so the example’s simple integer copy should not be generalized into an automatic deep snapshot of every serialized object.

Inspector convenience is not a complete runtime invariant

Min attributes guide Inspector entry, but data can still reach code through other paths, so important runtime rules need a real boundary. OnValidate can occur during Editor loading and other validation situations; keep it focused on its data rather than assuming every invocation is an appropriate place to create objects or perform main-thread-only work. Changing startingAmmo during Play also does not rerun Awake. If live configuration edits should update current state, provide an explicit operation with a clear policy instead of relying on an incidental Inspector repaint.

Practice: compare an existing component with a new one

Set an instance’s startingAmmo to three, then change the source initializer in Edit mode and inspect that existing component beside a newly added component. Predict why stored data may keep the original instance’s value. Next enter Play, use a round and edit startingAmmo without restarting; observe that currentAmmo follows its own already-created runtime state. Keep the tests separate so source initialization, serialization and runtime copying are not all changed simultaneously and then mistaken for one unreliable Inspector behavior.

  • Save a baseline scene before changing configuration so the intended stored value is known.
  • Use the context action’s log to inspect currentAmmo rather than assuming the visible starting field represents it.

Practice: make reset an explicit gameplay operation

For an independent C# extension, add a reset operation that copies the current configured value into runtime state again. Test using several rounds, changing configuration and resetting, with expected values written before each step. Decide whether a reset should preserve, discard or clamp the previous runtime amount. A later pooled actor can use the same kind of explicit reset boundary, but that does not mean every enable callback should blindly overwrite persistent state. The correct transition depends on the lifecycle the state is intended to survive.

  • Keep invalid configuration handling distinct from consuming a valid round.
  • If a configuration becomes a shared object, test aliasing separately from these integer-copy examples.

Name the data’s owner and lifetime

A sound serialization explanation states which fields Unity stores, where their values are edited, and when they become runtime state. The sample’s real component follows those rules while the console lab isolates the copying arithmetic. Actual import, prefab overrides and Play-mode behavior still require the Editor configuration. Avoid fixing every stale-looking value by deleting a component; first inspect whether the value belongs to source defaults, serialized data or a live runtime model. That distinction becomes increasingly important for prefabs, ScriptableObjects and save systems.

  • Portable outputs are two, three and zero.
  • The context action changes current ammo, not the configured starting amount.
  • OnValidate is focused validation; runtime rules and reset policy remain explicit responsibilities.
Runs in Unity 6 Editor

Unity Editor example

Set up in Unity 6

  1. Attach InspectorConfig.cs to an empty object and set startingAmmo to 3 in the Inspector.
  2. Enter Play mode and invoke Use One Round from the component context menu.
  3. Compare the logged runtime count with the stored startingAmmo field.
  4. Change the source initializer in a separate Edit-mode experiment and inspect an existing component versus a newly added one.
  5. Use a copy to test invalid configuration and keep OnValidate limited to validating its data.

InspectorConfig.cs

Download Unity script ↗
using UnityEngine;
public sealed class InspectorConfig : MonoBehaviour
{
    [SerializeField, Min(0)] private int startingAmmo = 3;
    private int currentAmmo;
    private void OnValidate() { if (startingAmmo < 0) startingAmmo = 0; }
    private void Awake() { currentAmmo = Mathf.Max(0, startingAmmo); }
    [ContextMenu("Use One Round")]
    public void UseOneRound()
    {
        if (!Application.isPlaying) { Debug.LogWarning("Enter Play mode first.", this); return; }
        currentAmmo = Mathf.Max(0, currentAmmo - 1);
        Debug.Log("Current ammo: " + currentAmmo, this);
    }
}

What to observe in Unity

The first action logs Current ammo: 2 while startingAmmo stays 3. Stored Inspector data and runtime state are not automatically synchronized.

The console practice below isolates the C# logic. It does not run a Unity scene or emulate Unity engine callbacks.

C# companion practice

using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

int configured = 3; int current = Math.Max(0, configured);
current = Math.Max(0, current - 1);
Console.WriteLine(current); Console.WriteLine(configured); Console.WriteLine(Math.Max(0, -4));
Open C# practice

C# practice output

2
3
0
Try this
A serialized configuration value and a copied runtime value can evolve independently.

Further reading