14 — API Guide (runtime)
Feature inventory: 13 — Feature List · Behavior quick-start: LLM_SCRIPTING_CONTEXT.md · Scripting prose: 05 — Scripting · Input: 12 — Input
Reference for behavior authors and anyone reading runtime code. Import from @bjs/engine in app behavior files. This guide covers the kit’s public surface — not every Babylon.js API.
Quick example
import { Behavior, exposed, inputMap, type Entity } from "@bjs/engine";
import type { InputActionMap } from "@bjs/engine";
import { Vector3 } from "@babylonjs/core";
export default class Patrol extends Behavior
{
@exposed({ min: 0, max: 10 }) speed = 2;
@exposed({ type: "entity" }) target!: Entity;
@inputMap("Player") player!: InputActionMap;
OnStart(): void
{
// target is resolved before this runs
}
OnUpdate(deltaSeconds: number): void
{
const move = this.player.FindAction("Move")?.ReadVector2();
if (move !== undefined)
{
this.node.position.addInPlace(new Vector3(move.x, 0, move.y).scale(this.speed * deltaSeconds));
}
}
OnMessage(message: string, source: Entity): void
{
if (message === "Alert")
{
// react to trigger or GUI click from source
}
}
}
Behavior
Base class for all scripts. One export default class per file; filename stem = Blender registry key (Patrol.ts → "Patrol").
Lifecycle (PascalCase required)
| Method | When |
|---|---|
OnStart(): void | Once, after load finishes and all @exposed entity refs resolve |
OnUpdate(deltaSeconds: number): void | Every frame, inside Level.RunFrame (before physics step) |
OnDestroy(): void | When the level is disposed |
OnMessage(message, source): void | When this entity receives a message (Event Messages, 3D GUI, SendMessage) |
OnCollisionEnter(other, contact): void | Solid collider first contacts another entity (contact is a CollisionContact: point, normal, impulse, distance) |
OnCollisionStay(other, contact): void | While solid contact continues (Havok COLLISION_CONTINUED; stops when bodies sleep) |
OnCollisionExit(other): void | Solid contact ends |
OnTriggerEnter(other): void | Trigger volume on this entity first overlapped |
OnTriggerExit(other): void | Trigger overlap ends (no OnTriggerStay — track overlap state in code if needed) |
Physics hooks are opt-in: override one and the engine enables the matching Havok callbacks automatically — no manual observable subscription. Both bodies in a solid contact receive collision hooks (Unity semantics); trigger hooks fire on the trigger entity's behaviors. Details: 06 — Physics › Collision lifecycle hooks.
Injected members
| Member | Type | When set |
|---|---|---|
entity | Entity | Before OnStart |
scene | Babylon Scene | Before OnStart |
node | TransformNode (getter) | Same as entity.node |
input? | InputActionMap | Scene default map when the script has no @inputMap fields |
Scale continuous motion by deltaSeconds. Babylon velocities are already per-second — do not multiply those by deltaSeconds.
Entity
One Blender object that exported successfully. File: packages/engine/src/core/Entity.ts.
Fields
| Field | Description |
|---|---|
id | GUID string (bjs_id) |
name | Object name at export |
node | Babylon node (mesh, empty, camera, …) |
tag | From TAG component (default "Untagged") |
attachments | All applied component rows (see Attachments) |
behaviors | Live Behavior instances from SCRIPT rows |
body? | Havok PhysicsBody when physics was built |
animations | AnimationGroups scoped to this node |
sounds | StaticSounds from AUDIO components |
guiTextures | 2D GUI textures from GUI components |
controls3D | 3D GUI controls from GUI3D_* components |
particleSystems | Particle systems from PARTICLE components |
textRenderers | MSDF renderers from MSDF_TEXT components |
reflectionProbes | Reflection probes from REFLECTION_PROBE components |
Methods
| Method | Returns | Notes |
|---|---|---|
GetAttachments() | readonly attachment rows | Full list |
GetAttachment(type) | First row of type, or undefined | e.g. GetAttachment("SCRIPT")?.behavior |
GetAttachmentsOfType(type) | All rows of type | e.g. multiple COLLIDER rows |
HasAttachment(type) | boolean | |
GetBehavior(Ctor) | First instance of class | GetBehavior(Patrol) |
GetAnimation(name) | AnimationGroup? | Exact name, then substring match |
GetSound(name) | StaticSound? | File stem match |
GetGui(name) | AdvancedDynamicTexture? | JSON file stem |
GetControl3D(name) | Control3D? | Control name |
GetParticles(name) | IParticleSystem? | System JSON stem |
GetTextRenderer(name) | TextRenderer? | Font JSON stem |
GetReflectionProbe() | ReflectionProbe? | First probe attachment on this entity |
GetReflectionProbes() | ReflectionProbe[] | All probe attachments |
SendMessage(message, source) | void | Calls OnMessage on every behavior on this entity |
Level
Runtime container returned by LevelLoader.Load. File: packages/engine/src/core/Level.ts.
Fields (common)
| Field | Description |
|---|---|
entities | Map<string, Entity> by GUID |
activeCamera? | Exported / typed active camera |
shadowGenerators | One per casting light |
punctualLightingMode | "forward" | "clustered" | "forward-expanded" |
post? | Post-processing handles |
atmosphere? | Atmosphere addon handle |
constraints | Built physics joints |
clusteredLights? | Clustered-light container when clustering ran |
debugEnabled | From manifest debug flag |
gui3DManager? | Shared 3D GUI manager |
collisionEventHandles? | Havok collision/trigger observers (removed on dispose) |
particleEmitterManager? | Empty-emitter tracking handle |
msdfTextManager? | MSDF text render-pass handle |
reflectionProbes | All built reflection probes (dispose with level) |
Methods
| Method | Description |
|---|---|
ById(id) | Look up entity by GUID |
ByTag(tag) | All entities with matching entity.tag |
ShowColliders(show?) | Toggle Havok wireframe debug view |
RefreshShadows() | Re-bake frozen shadow maps after moving casters |
AddUpdater(fn) | Per-frame callback after all OnUpdate (cameras use this) |
Dispose() | Teardown — input detach, OnDestroy, dispose assets |
Begin() and RunFrame() are called by the loader — behaviors do not call them.
Attachments
Each successfully applied component becomes one row on entity.attachments. Types match manifest component types plus GUI3D variants.
type | data | Runtime object on row |
|---|---|---|
TAG | tag string | — |
RENDERING_GROUP | group id + propagation flags | — (values applied to owned meshes in FinalizeLevel) |
LAYER_MASK | mask + propagation flags | — (values applied to owned meshes in FinalizeLevel) |
COLLISION_LAYER | layer name + propagation flags | — (Havok filter masks applied in FinalizeLevel) |
COLLIDER | collider manifest | body (shared per entity) |
RIGIDBODY | body manifest | body (same ref as colliders) |
SCRIPT | script + vars | behavior |
AUDIO | audio manifest | sound |
GUI | gui manifest | texture |
PARTICLE | particle manifest | system, optional emptyEmitter |
MSDF_TEXT | font paths + text | renderer |
REFLECTION_PROBE | probe capture + influence settings | probe (ReflectionProbe) |
CONSTRAINT | constraint manifest | constraint |
GUI3D_* | layout + events | control |
Convenience arrays on Entity (sounds, behaviors, …) mirror attachments for common access patterns.
@exposed (Blender-editable fields)
Decorator: exposed(options?) from @bjs/engine. Must stay lowercase — Blender regex-parses the token literally.
| Type / option | Blender UI | Runtime value |
|---|---|---|
float / int (+ min, max, step) | Number slider | number |
bool | Checkbox | boolean |
string | Text field | string |
type: "vector2" | XY fields | [number, number] |
vector3 or [n,n,n] default | XYZ fields | Vector3 |
color or Color3 default | Color picker | Color3 |
type: "entity" | Object picker | Entity (resolved before OnStart) |
type: "enum", options: [...] | Dropdown | string |
type: "list", of: "entity"|… | List rows | Array (entity slots resolve by index) |
Defaults in source must be single-line literals so Blender’s parser can read them. After changing fields, press Sync on the SCRIPT component.
Input (@inputMap + polling)
See 12 — Input for authoring (stick vs 1D/2D composites, control type, gamepad labels, LT/RT as axis 4/5, axisHalf, disambiguation). In behaviors:
@inputMap("Player") player!: InputActionMap; // named map
@inputMap("Vehicle") vehicle!: InputActionMap; // e.g. CarController
// Vector 2 action — Type: Value, Control Type: Vector 2 in Blender
const control = this.vehicle.FindAction("Main Control")?.ReadVector2() ?? { x: 0, y: 0 };
const throttle = control.y;
const steer = control.x;
InputActionMap
| Method / property | Description |
|---|---|
FindAction(name) | Get action by name (warns if missing) |
Enable() / Disable() | Toggle whole map (loader enables on Begin) |
InputAction (polling)
| Method | Description |
|---|---|
ReadValue() | Scalar (VECTOR2 → magnitude) |
ReadVector2() | { x, y } |
IsPressed() | True while actuated |
WasPressedThisFrame() | Edge: just pressed this frame |
WasReleasedThisFrame() | Edge: just released |
WasPerformedThisFrame() | Edge: performed callback fired |
InputAction (callbacks)
action.started.add(fn), action.performed.add(fn), action.canceled.add(fn) — same semantics as Unity Input System.
Global access (optional)
InputManager.actions · InputManager.FindAction("Player/Move") · InputManager.GetDefaultMap() — prefer @inputMap fields on behaviors.
BehaviorRegistry
Maps script name → class. Apps register before load:
const registry = new BehaviorRegistry();
const modules = import.meta.glob("./behaviors/*.{ts,js}", { eager: true });
AutoRegisterBehaviors(registry, modules);
const level = await new LevelLoader(scene, registry).Load(manifestUrl);
registry.Create("Patrol") is called internally for each SCRIPT row.
App bootstrap (not in behaviors)
Typical main.ts responsibilities — see 02 — Runtime Basics:
FetchAndValidateManifest(url)— optional; required beforeCreateLevelEnginewhen using large-world renderingCreateLevelEngine(canvas, antialias, manifest)new Scene(engine)await EnableHavokPhysics(scene, …)— beforeLoadLevelLoader.Load(manifestUrl, manifest?, options?)engine.runRenderLoop(() => scene.render())
LevelLoaderOptions
| Option | Default | Effect |
|---|---|---|
shadows | true | Create shadow generators |
shadowMapSize | 1024 | Shadow map resolution |
freezeShadows | from manifest | Static shadow bake |
cleanBoneMatrixWeights | false | Scrub imprecise skeleton bone weights on load (fixes garbled shadows on skinned meshes) |
debugColliders | false | Wireframes on load (also gated by debug flag) |
clusterPunctualLights | from manifest | Light clustering policy |
lightBudget | 8 | Max forward punctual lights |
Physics (from behaviors)
When an entity has a body: this.entity.body is a Babylon PhysicsBody. Use Havok APIs for impulses, velocities, etc. Collider overlay: app debug key → level.ShowColliders() (when level.debugEnabled).
Messages
Authored Event Messages and 3D GUI buttons call targetEntity.SendMessage(message, sourceEntity). Every behavior on the target gets OnMessage(message, source). Use the same string names you authored in Blender event rows.
Related exports
@bjs/engine also re-exports manifest types (core/types.ts), subsystem helpers (physics, lights, …), and UI loaders — mainly for app code and advanced scripts. Behavior authors usually need Behavior, exposed, inputMap, and Babylon types only.