Writing & Compiling
The Code panel is a full in-browser TypeScript workspace - write a ScriptBehavior, press Compile, and it is instantly runnable in Play.
Everything here runs in your browser tab. There is no toolchain to install: TypeScript is transpiled by esbuild-wasm and the editor is a real Monaco instance (the same editor as VS Code) with full IntelliSense over the Awaken API.
The Code panel
Open the Code tab (or click + New Game Script → Code from a Script component in the Inspector). In a project with no scripts yet the Code tab isn't shown as a first-class tab - find it under the … menu on the tab bar, or create your first script from the Asset Browser; see when a panel is shown. The panel has three regions:
- Script list (left) - every script asset in the project, by display name, in the shared asset-editor sidebar: click to open, double-click or right-click to Rename, right-click to Delete, and collapse it to an icon strip to reclaim width. The footer offers + New Game Script, + New Editor Script, and + New Plugin, each creating a script from its template and dropping you straight into naming it.
- Editor (centre) - the Monaco editor for the open script's source.
- Toolbar (top) - the display-name field, the Compile button, and a status line (
Compiling…,Compiled ✓, orError - see console).
📸 Screenshot - save as img/scripting-code-panel.png
The Code panel with a script open: the script list on the left, Monaco in the centre showing the template, and the Compile button + "Compiled ✓" status in the top bar.
Stable ids, renamable names
Each script has a stable id (a UUID) that never changes, and a display name you can rename freely. Components reference the id, so renaming a script in the list - or in the top bar's name field - never breaks the objects running it. This is why the list key and the on-object reference are decoupled. See Scripting Overview for the data model.
Deleting a script unregisters it: any component still referencing that id shows a ⚠ missing script marker in its picker, and the behaviour simply stops running.
The ScriptBehavior template
Every new script starts from this template (SCRIPT_TEMPLATE):
ts
// Component behavior - runs during Play. Type 'api.' for autocomplete.
export default class implements ScriptBehavior {
onUpdate(api: ScriptApi, dt: number) {
api.translate(dt * 1.5, 0, 0); // drift along +X
if (api.input.isDown(" ")) api.setField("RigidBody", "velocity", { x: 0, y: 6, z: 0 });
}
}The rules a script must follow:
export defaulta class. The compiler expects the module's default export to be a class it cannew. Anything else fails withscript must export default a class.implements ScriptBehavioris optional but recommended - it makes Monaco check your hook signatures.- Implement only the hooks you need -
onStart,onUpdate,onDestroy,onCollision,onCollisionEnter,onCollisionExit. See the ScriptApi Reference for the full set. - Public class fields become Inspector controls - see Inspector Parameters.
Monaco IntelliSense
The editor ships the full Awaken scripting API as an ambient type library, so api. autocompletes every method with its doc comment, and mistyped calls are underlined before you ever compile. The bundled definitions cover:
ScriptApi- theapiargument every hook receives.ScriptBehavior- the hook signatures.Input,UIApi, and a lightweightWorldfor advanced use.Vec3,Quat,Entity, and the entity-reference alias types (Camera,Light,MeshRenderer,MeshMaterial,RigidBody,Collider,AudioSource,Animator,UINode,Tag,GameObject).
You do not import anything - these types are global in the script scope. The alias types (like camera: Camera) double as Inspector hints; a field typed Camera becomes an object picker filtered to cameras (see Inspector Parameters).
The editor uses a 2-space tab size and a dark theme, and lays out automatically as you resize the panel.
Compiling
Pressing Compile runs the pipeline that makes your class runnable:
Under the hood:
compileTstranspiles your TypeScript to ES2022 JavaScript (ESM) with esbuild-wasm. Types are erased; only the JS remains.loadBehaviordynamically imports that JS as a module - normally via ablob:URL, falling back to adata:URL whereblob:is unsupported - and returns a factory that constructs a fresh instance.- The class is registered into the shared
ScriptRegistryunder the script's stable id. - The script's public fields are re-discovered so the Inspector's param controls stay in sync with the code.
On success the status reads Compiled ✓ and the compile is logged to the Console. On failure the error message is logged and no class is registered - the old one (if any) keeps running.
The ScriptRegistry - one registry, two consumers
The registry is shared: the editor compiles into it and the PlayController runs from it. When you press Play, the controller's ScriptHost looks up each attached script by id and instantiates it. This is why compiling is a prerequisite to seeing behaviour - an attached-but-never-compiled script shows Compile the script to expose its fields in the Inspector and does nothing in Play.
Loading a project recompiles every script on open, so a freshly opened .awaken file is immediately playable.
Live source mirroring
You do not need to Compile to save your work. Every keystroke mirrors the current buffer into the project and marks it dirty, so Save Project always captures exactly what is on screen - even a half-written script you never compiled. Compile is only about producing the runnable class; it is orthogonal to persistence.
The practical consequence: your source is never lost, but your running behaviour only updates when you Compile.
ESM in the editor, IIFE for ship
The editor and a shipped game load compiled scripts differently, for a security reason worth knowing:
| Context | Format | Why |
|---|---|---|
| Editor Play / Preview | ESM via blob:/data: import (compileTs) | Works from any real browser origin |
Exported game.html | Self-contained IIFE (compileTsGlobal) | A double-clicked file:// page cannot import() a blob:/data: URL - Chrome blocks it as a unique origin |
When you export a game, every runtime script is recompiled as an IIFE that attaches its class to a global, so the game runs offline from a plain double-click. You do not do anything to trigger this - it is part of the export pipeline. Editor-kind scripts are excluded entirely (see Editor-Only Scripts). See Export game.html for the full pipeline.
See also
- Code Panel - the panel's UI in the editor interface docs
- ScriptApi Reference - every method IntelliSense autocompletes
- Inspector Parameters - how public fields become controls
- Console - where compile errors and
api.logappear - Export game.html - how scripts are packaged for shipping