"s&box component lifecycle in practice"
- Spawn helper sets
[Property]values afterCreate<T>(), butOnAwakealready ran on defaults — seeds, waypoint indices, hashes are wrong. - Session statics (SFX loop registry, caches) survive Play→Stop→Play and either leak old state or get wiped after bootstrap already started the new session.
- Managers aren't visible when world/state components start.
OnAwake() fires synchronously inside Components.Create<T>(), before the calling code's next line.
var c = go.Components.Create<Foo>(); // OnAwake already ran here
c.SomeProperty = x; // too late for OnAwakeOnStart runs on the first enabled frame, after synchronous property assignments from the spawn helper have landed.
Runtime creation order matters: create manager singletons first (their OnAwake publishes .Instance), world next, state-holding components last so their OnStart can see the world.
Session-reset trap: a boot singleton's OnAwake runs synchronously at the Create line inside Bootstrap's own OnStart — before Bootstrap's later lines start loops/registrations. That singleton's OnStart is deferred to frame 1 — after Bootstrap already started them — so resetting statics in OnStart wipes the fresh session.
public sealed class Workbench : Component
{
[Property, Group("Tuning")] public float X { get; set; } = 1f;
[RequireComponent] public Rigidbody Body { get; set; }
protected override void OnAwake() { /* Create-time: publish singletons HERE */ }
protected override void OnStart() { /* first enabled frame: children/visuals, derive from Properties */ }
protected override void OnUpdate() { /* input, visuals */ }
protected override void OnFixedUpdate() { /* 50 Hz: physics, sim */ }
protected override void OnDestroy() { }
}Rules:
- Publish singletons in
OnAwake(see singleton-no-reference-wiring). - Derive per-instance state (hashes, seeded timers, waypoint indices) from
[Property]values inOnStart, notOnAwake. - Bootstrap order: managers → world → state.
- Reset static-only facades (
ResetForNewSession()) from a boot singleton'sOnAwake, never itsOnStart. Reset related flags and shared registries at the same boundary so they don't disagree. - Also hard-reset static input gates in
OnStartandOnDestroy— they leak across play sessions in one editor process.
Create is synchronous by design so the object is usable on the next line — but that means Awake cannot see properties assigned after Create. Start is deferred until the GO is enabled in the scene graph, which is after the spawn helper finished wiring. Matching reset hooks to that ordering keeps session statics cleared before the new session registers work, not after.