Appearance
Levels & scene loading
A project is one main map plus any number of sub-maps. They are full, independent maps — the Sub-maps window adds, renames and switches between them while you build. At runtime, a script moves the game from one to the next.
Switching at runtime
js
class Goal extends Behaviour {
onTriggerEnter(other) {
if (other.tag === 'Player') game.loadScene('Level 2')
}
}game.loadScene takes a map name or a build index (0 is the main map, 1..N the sub-maps in order). game.getActiveScene() gives the current name and game.getActiveSceneIndex() its index. SceneManager.LoadScene(...) and SceneManager.GetActiveScene() work too, and mean the same thing.
The switch happens at the end of the current tick, not in the middle of your function, so it is safe to call from onUpdate, a trigger, or a HUD button.
This works the same in the editor play-test, in a standalone build and in published multiplayer — in a multiplayer room the server rebuilds physics and scripts on the new map and every client rebuilds with it.
What a scene load destroys
Everything. The old map's objects, their scripts, their physics bodies and the HUD those scripts built are all torn down, and the new map is built from scratch. That is usually what you want — but it means a score counter, a music player or a player rig you carried through the level has nothing to hold on to.
Keeping an object across the load
DontDestroyOnLoad opts an object out of that teardown:
js
class GameManager extends Behaviour {
onStart() {
// A second copy? The one that already survived wins; this one removes itself.
if (game.findEntities('GameManager').length > 1) { Destroy(this.gameObject); return }
DontDestroyOnLoad(this)
this.score = 0
}
addScore(n) { this.score += n }
}Any other script can then reach it across levels:
js
class Coin extends Behaviour {
onTriggerEnter(other) {
if (other.tag !== 'Player') return
const mgr = game.findEntities('GameManager')[0]
if (mgr) game.getScript(mgr.id, 'GameManager').addScore(1)
Destroy(this.gameObject)
}
}(this.getScript('X') looks on the object's own entity; reaching another object's script is the two-argument game.getScript(entityId, 'X').)
What survives, and what does not
| The object and its whole subtree | Survives, at the pose it had reached |
| Its script instances | The same instances — fields, storage, timers and coroutines intact |
onAwake / onStart | Do NOT run again |
| A clip it was playing | Keeps playing, from the frame it was on |
| The HUD its scripts created | Survives, click handlers and all |
| Its physics body | Rebuilt in the new scene, at its current transform |
The lifecycle row is the one to remember. A persisted object is never re-initialized — a score keeper whose onStart ran again would zero the score it exists to keep. Put per-level setup in the new level's own scripts, not in the persistent one.
The rules
Root objects only. Passing a child logs a warning and does nothing, because a child's lifetime belongs to its parent. Mark the top-level parent instead and the whole subtree comes with it.
Destroy still works. The mark promises to outlive scene loads, not destruction.
Reloading the same level does not duplicate the survivor. It keeps its identity and the incoming copy of it is skipped — so restarting a level gives you one manager holding its score, not two to sort out. The singleton guard in the example above is still worth writing: it costs nothing, it documents the intent, and it catches the case where a second manager arrives from somewhere else entirely.
The mark lasts for the rest of the game, across any number of later loads. Stopping the play-test clears it, so nothing leaks back into the map you are editing.
Calling it by id
DontDestroyOnLoad accepts this inside a Behaviour, a GameObject, or a bare entity id. Outside a Behaviour, use the game API directly:
js
const ok = game.dontDestroyOnLoad(someEntity.id) // true if marked, false if refusedIt returns false when the target does not exist or is not a root object, so you can check rather than assume.
Common uses
A music player that keeps playing between levels: give it an audioSource set to loop and play on awake, mark it, and never touch it again.
A player rig that carries health and inventory: mark the top-level rig object so its model, collider and camera mount all come along. Remember the spawn point belongs to each level — move the rig into place from the new level's own script, since the rig's onStart will not run again.
A run timer or score display: mark the manager, and let its HUD survive with it.