← Index · Prev · Next →

10 — Feature Traces

Every feature in this kit travels the same path: you set something up in Blender, export writes a level file, the game reads that file and builds the scene. This page lists each hop in plain language, with the exact file and function underneath so you (or an LLM) can open the source. For a single-table inventory of all features, see 13 — Feature List; for runtime method reference, see 14 — API Guide.

GUID / entity identity

Every object gets a permanent ID so the game can find it again after export — even if you rename it.

  1. Blender

    The add-on assigns a unique ID to each object the first time it needs one.

    blender_addon/core/ids.py · ensure_object_id (stores bjs_id on the object)

  2. Export

    That ID is written into the 3D model file attached to each node.

    blender_addon/export/level.py · glTF node extras.bjs_id

  3. Manifest

    The same ID appears as entities[].id in the level JSON.

    manifest · entities[].id

  4. Load

    The loader builds a lookup table from model nodes to IDs, then matches each entity row to a node.

    packages/engine/src/core/loader/nodeResolution.ts · BuildIdIndex, ProcessEntity

  5. Runtime

    You get an Entity in level.entities; the 3D node remembers which entity owns it.

    packages/engine/src/core/Entity.ts · node metadata.bjsEntity

Viewport visibility (eye icon)

Hiding an object in Blender’s viewport can hide it in the game too, without deleting it from the level.

  1. Blender

    You turn off the viewport eye icon (but leave render on) — or disable shadow casting on a mesh.

    Blender · hide_viewport, ray-visibility Shadow

  2. Export

    Export stamps temporary flags onto each node before writing the model file.

    blender_addon/export/level.py · _stamp_gltf_extras (bjs_visible, bjs_cast_shadows)

  3. Load

    Hidden nodes are turned off; lights and cameras under them are disabled too.

    packages/engine/src/core/loader/nodeResolution.ts · ApplyNodeVisibility

  4. Load

    Meshes marked “no shadow cast” only receive shadows; they do not cast them.

    packages/engine/src/subsystems/shadows.ts · SetupShadows

  5. Runtime

    Scripts can show or hide the mesh anytime by toggling entity.node.isVisible.

    Babylon · AbstractMesh.isVisible

Tag

A text label on an entity so scripts and triggers can find “all enemies” or “the player” without hard-coding names.

  1. Blender

    You add a TAG component and type a label (e.g. Enemy).

    blender_addon/components/ · TAG component UI

  2. Export

    Export writes one TAG row into that object’s component list.

    blender_addon/export/component_serializers.py · SERIALIZERS registry

  3. Manifest

    The JSON holds { "type": "TAG", "tag": "Enemy" }.

    manifest · entities[].components[]

  4. Load

    The loader sets entity.tag and records the attachment.

    packages/engine/src/core/loader/componentRegistry.ts · tag handler, RegisterAttachment

  5. Runtime

    Query with level.ByTag("Enemy") or check entity.HasAttachment("TAG").

    packages/engine/src/core/Level.ts · ByTag

Rendering group / Layer mask

Control Babylon draw order (rendering groups 0–3) or camera visibility (layer masks) on an entity’s owned meshes, with optional propagation to child entities.

  1. Blender

    Add Rendering Group and/or Layer Mask under Rendering. Set group id or mask preset; toggle Apply to Owned Meshes / Apply to Child Entities.

    blender_addon/components/constants.py · COMPONENT_TYPES; ui/component_bodies.py · body drawers

  2. Export

    Each enabled row serializes through the SERIALIZERS registry.

    blender_addon/export/component_serializers.py · _serialize_rendering_group, _serialize_layer_mask

  3. Manifest

    Example rows: { "type": "RENDERING_GROUP", "renderingGroupId": 2, "applyOwnedMeshes": true, "applyChildEntities": true } and { "type": "LAYER_MASK", "layerMask": 268435456, … }.

    manifest · entities[].components[]

  4. Load

    Handlers record attachment rows during the entity pass; mesh values are deferred.

    packages/engine/src/core/loader/componentRegistry.ts · RenderingGroupHandler, LayerMaskHandler

  5. Load

    After every entity, probe, and async asset exists, ApplyRenderLayers walks the manifest parent tree: owned meshes via CollectOwnedMeshes; child propagation stops at the first descendant with its own component of that kind.

    packages/engine/src/subsystems/renderLayers.ts · ApplyRenderLayers; called from LevelLoader.FinalizeLevel

  6. Runtime

    Query authorship with entity.GetAttachment("RENDERING_GROUP") / GetAttachment("LAYER_MASK"). Meshes already carry Babylon properties — no per-frame update.

    packages/engine/src/core/meshOwnership.ts · CollectOwnedMeshes

Not the same as Babylon’s 2D Layer overlay class. Layer-mask presets use high bits (0x10000000 …) for HUD cameras — match camera.layerMask in app code. Detail: 07 — Rendering.

Collision layers

Unity-style named collision filtering: scene-wide layer list + matrix in Blender Scene, per-object layer picker component, Havok filter masks applied after all bodies exist.

  1. Blender

    Define named layers and the collision matrix in Babylon Scene › Collision Layers. On each object, add Collision Layer under Physics; pick a layer; toggle owned / child propagation.

    blender_addon/collision_layers/ · panel + serialize; ui/component_bodies.py · layer picker

  2. Export

    Scene block → scene.collisionLayers; component row → COLLISION_LAYER via SERIALIZERS.

    blender_addon/collision_layers/serialize.py · serialize_collision_layers; export/component_serializers.py · _serialize_collision_layer

  3. Manifest

    Example scene block: { "layers": ["Default","Player"], "matrix": [[true,true],[true,false]] }. Component: { "type": "COLLISION_LAYER", "layer": "Player", … }.

    manifest · scene.collisionLayers, entities[].components[]

  4. Load

    BuildPhysics registers each shape in context.physicsShapesByEntity. Handler records attachment rows only.

    packages/engine/src/subsystems/collisionLayers.ts · RegisterPhysicsShapeForEntity; componentRegistry.ts · CollisionLayerHandler

  5. Load

    After constraints, ApplyCollisionLayers resolves layer names → membership/collide masks and sets them on every registered shape (compound children included).

    packages/engine/src/subsystems/collisionLayers.ts · ApplyCollisionLayers; LevelLoader.FinalizeLevel

  6. Runtime

    Query authorship with entity.GetAttachment("COLLISION_LAYER"). Filtered pairs skip contacts and trigger events. Entities without the component keep Havok defaults.

    packages/engine/src/core/attachments.ts

Collision layers are independent of render layers (RENDERING_GROUP / LAYER_MASK). Detail: 06 — Physics.

Collider / RigidBody

Collision shapes and physics bodies you author in Blender become one Havok body in the game when the level loads.

Walk through the code: trace-physics · Blender viewport: collider preview · CoM preview

  1. Blender

    You add collider and/or rigid-body rows in the N-panel, pick shapes, and optionally preview wireframes and center-of-mass in the 3D view.

    blender_addon/viewport/collider_preview.py, cog_preview.py

  2. Export

    Each enabled row becomes one JSON object; positions are converted from Blender’s axes to the game’s Y-up space. “Make Invisible” and “Apply Object Scale” become flags on that row.

    blender_addon/export/component_serializers.py · SERIALIZERS registry

  3. Manifest

    The level file lists COLLIDER and RIGIDBODY entries under the entity. Multiple colliders on one object are intentional — they combine into one body.

    manifest · entities[].components[]

  4. Load

    The loader gathers all colliders for the entity, hides the mesh if requested, then builds physics.

    packages/engine/src/core/loader/componentRegistry.ts · physics handler

  5. Load

    One collider → one shape (auto-fit to mesh, convex hull, or manual box/sphere). Several colliders → one compound body wrapping all shapes.

    packages/engine/src/subsystems/physics/ · BuildPhysics, BuildCompoundBody, OwnedColliderMeshes

  6. Load

    Mass, friction, and center of mass are applied for dynamic bodies.

    packages/engine/src/subsystems/physics/bodyBuild.ts · ApplyMassProperties

  7. Runtime

    The entity gets entity.body; press C in debug builds to visualize colliders.

    packages/engine/src/core/Level.ts · ShowColliders

Axis conversion detail: 06 — Physics. Scale default: packages/engine/src/core/nodeScale.ts · ApplyObjectScaleEnabled.

Script + @exposed values

TypeScript behaviors you attach in Blender get their tunable fields synced from the script file, edited per object, then applied when the game starts.

Walk through the code: trace-exposed

  1. Blender

    You pick a script file and click Sync — the panel fills with fields marked @exposed in the TypeScript source.

    blender_addon/core/script_parse.py · parse_exposed

  2. Export

    Your edited values are written into the SCRIPT row’s vars object (entity picks become GUID strings).

    blender_addon/export/components.py · _serialize_vars

  3. Manifest

    Each scripted object has { "type": "SCRIPT", "script": "MyBehavior", "vars": { … } }.

    manifest · entities[].components[]

  4. Load

    The loader creates a behavior instance from the registry, injects entity and scene, then copies vars onto the instance.

    packages/engine/src/core/loader/scripts.ts · InstantiateScripts

  5. Load

    Entity-reference fields wait until every entity exists, then resolve to the real object.

    packages/engine/src/core/loader/entityBuilder.ts · ResolveObjectReferences

  6. Runtime

    OnStart runs once when the level begins — not during the steps above.

    packages/engine/src/core/Level.ts · Begin

Runtime loop (app → Babylon → OnUpdate)

After the level loads, the browser runs frames forever; each frame the kit processes input, calls your scripts, steps physics, and draws.

Walk through the code: trace-runtime-loop · 02 — Runtime Basics · runtime-loop diagram

  1. Runtime

    The app creates the engine and scene, enables physics, and loads the level (which ends by calling level.Begin() once).

    apps/playground/src/main.ts · MainLevelLoader.Load

  2. Runtime

    The app starts Babylon’s frame loop — without this line, nothing animates and OnUpdate never runs.

    apps/playground/src/main.ts · engine.runRenderLoop(() => scene.render())

  3. Runtime

    At the start of each frame: particle emitters sync, then the kit runs input + all OnUpdate hooks.

    packages/engine/src/core/Level.ts · RunFrame on onBeforeRenderObservable

  4. Runtime

    Havok advances physics, then the GPU draws meshes, shadows, and post effects.

    Babylon · physics step inside scene.render()

  5. Runtime

    After drawing, MSDF text labels render if any exist.

    packages/engine/src/ui/msdfText.ts · onAfterRenderObservable

Behavior lifecycle (OnStart / OnUpdate / OnDestroy / OnMessage)

Scripts have four hooks: start once, update every frame, react to messages, and clean up on teardown.

Walk through the code: trace-lifecycle

  1. Load

    Behavior objects are created but none of the hooks run yet.

    packages/engine/src/core/loader/scripts.ts · InstantiateScripts

  2. Load

    Cross-object references resolve; triggers, constraints, and GUI finish wiring.

    packages/engine/src/core/LevelLoader.ts · FinalizeLevel

  3. Runtime

    Begin attaches input, calls every OnStart once, and subscribes RunFrame to the render loop.

    packages/engine/src/core/Level.ts · Begin

  4. Runtime

    Each frame: read input → all OnUpdate → camera updaters → clear input edges.

    packages/engine/src/core/Level.ts · RunFrame

  5. Runtime

    Triggers, GUI clicks, and SendMessage call OnMessage on target behaviors (any time, not only on frame boundaries).

    packages/engine/src/core/Entity.ts · SendMessage

  6. Runtime

    Leaving the level calls every OnDestroy and removes frame subscriptions.

    packages/engine/src/core/Level.ts · Dispose

Component application (all types)

The component stack in Blender is the master list of “what this object has” — colliders, scripts, audio, GUI, and so on.

Walk through the code: trace-components (runtime) · Blender trace-components

  1. Blender

    You add rows in the Babylon Components panel; each row is one feature on that object.

    blender_addon/ui/component_draw.py + component_bodies.py · draw_component, BODY_DRAWERS

  2. Export

    Every enabled row becomes one entry in the manifest component array.

    blender_addon/export/component_serializers.py · SERIALIZERS registry

  3. Load

    For each entity, the loader sends each component type to its registered handler (physics, audio, scripts, …).

    packages/engine/src/core/loader/entityBuilder.ts · ProcessEntity; componentRegistry.ts · ApplyEntityComponents

  4. Load

    Only SCRIPT rows become live TypeScript classes; everything else is applied data (bodies, sounds, textures).

    packages/engine/src/core/attachments.ts · RegisterAttachment

Entity reference field

An @exposed field can point at another object in the scene — export stores its ID, load swaps in the real entity before OnStart.

  1. Blender

    You pick a target object in an exposed “entity” field; export ensures that target is included in the level.

    blender_addon/components/component.py · BJSExposedVar.obj_val

  2. Export

    The target’s GUID is written into vars; referenced objects are force-exported even if hidden.

    blender_addon/export/components.py · iter_referenced_objects

  3. Manifest

    vars.myTarget = "abc-123-guid" in the SCRIPT row.

    manifest · entities[].components[].vars

  4. Load

    After all entities exist, GUIDs become Entity references on the behavior instance.

    packages/engine/src/core/loader/entityBuilder.ts · ResolveObjectReferences

Light / Shadows

Lamps in Blender become Babylon lights; casting lamps also get shadow maps, with a budget for how many point/spot lights run at full quality.

Walk through the code: trace-lights · trace-shadows

  1. Blender

    You place a lamp and optionally enable Cast Shadows and sun angle (for soft shadows).

    Blender · lamp object, shadow settings

  2. Export

    Each lamp becomes a light block in the manifest (type, color, intensity, shadow settings).

    blender_addon/export/datablocks.py · serialize_light

  3. Load

    The loader creates or configures the matching Babylon light on the imported node.

    packages/engine/src/subsystems/lights.ts · ApplyBlenderLight

  4. Load

    If too many point/spot lights are on, extras are clustered into a cheaper path (see light budget in Rendering chapter).

    packages/engine/src/subsystems/clusteredLights.ts · ClusterPunctualLightsIfNeeded

  5. Load

    Shadow generators are created for each casting light; meshes can opt out of casting.

    packages/engine/src/subsystems/shadows.ts · SetupShadows

07 — Rendering · punctual light budget.

Camera (+ typed override)

The active Blender camera becomes the game camera; an optional CAMERA component can replace it with orbit, follow, or geospatial controls.

Walk through the code: trace-cameras

  1. Blender

    You mark one camera active and set clip range and field of view.

    Blender · camera object

  2. Export

    Basic camera settings export in the entity’s camera block; optional CAMERA component adds control type and speeds.

    blender_addon/export/datablocks.py · serialize_camera; export/components.py · CAMERA row

  3. Load

    The imported FreeCamera gets lens settings; if a CAMERA component exists, it may be rebuilt as Arc/Follow/Offset/Geospatial.

    packages/engine/src/subsystems/cameras/apply.ts · ApplyBlenderCamera; typed.ts · BuildTypedCamera

  4. Load

    Cameras that track another object resolve the target in a second pass after all entities exist.

    packages/engine/src/subsystems/cameras/targets.ts · ResolveCameraTargets

Atmosphere

Sky and aerial perspective from the Scene › Atmosphere panel replace the environment skybox when enabled.

Walk through the code: trace-atmosphere

  1. Blender

    You tune atmosphere in the Babylon Scene panel (linked to a sun lamp for direction).

    blender_addon/ui/scene_panels.py · Atmosphere section

  2. Export

    Settings serialize to scene.atmosphere in the manifest; env skybox is forced off when atmosphere is on.

    blender_addon/export/atmosphere.py · serialize_atmosphere

  3. Load

    The engine builds the Babylon atmosphere addon, tied to the exported sun light.

    packages/engine/src/subsystems/atmosphere.ts · ApplyAtmosphere

Post-processing

Bloom, depth of field, SSAO, and similar effects from the Scene › Post-Processing panel attach to the active camera after the level starts.

Walk through the code: trace-post

  1. Blender

    You enable and tune post effects; LUT images are copied beside the level.

    blender_addon/ui/post_panels.py · Post-Processing section

  2. Export

    All settings go to scene.postProcessing in the manifest.

    blender_addon/export/post_processing.py · serialize_post_processing

  3. Load

    Post stack is applied after Begin so cameras created in OnStart still get the effects.

    packages/engine/src/subsystems/postprocess.ts · ApplyPostProcessing

Animation autoplay

NLA strips you author in Blender can auto-play one clip when the level loads.

  1. Blender

    You add NLA strips and choose which clip should autoplay (on the armature for skinned characters).

    Blender · NLA editor

  2. Export

    Strip names and autoplay choice serialize into the entity’s animation block.

    blender_addon/export/animation.py · serialize_animation

  3. Load

    The loader finds matching animation groups on the imported model and starts the chosen clip.

    packages/engine/src/subsystems/animation.ts · FindAnimationGroups, ApplyAutoPlayAnimations

Skinned characters: animate on the armature, not the mesh — 08 — Audio & Animation.

Audio

An AUDIO component plays a sound file from the object’s position (or globally) when the level loads or when triggered.

Walk through the code: trace-audio

  1. Blender

    You add an AUDIO row and pick a sound file and options (spatial, autoplay, loop).

    blender_addon/components/ · AUDIO component

  2. Export

    The file is copied into the level’s audio/ folder and referenced from the manifest.

    blender_addon/export/assets.py · copy_asset

  3. Load

    Sounds are created asynchronously; autoplay waits for browser audio unlock if needed.

    packages/engine/src/subsystems/audio.ts · ApplyAudio

  4. Runtime

    Access via entity.GetSound("stem") or the sounds list on the entity.

    packages/engine/src/core/Entity.ts · GetSound

Event Messages & collision hooks

Collider Event Messages send named messages to another object on physics phases; behaviors can also override Unity-style OnCollision* / OnTrigger* hooks.

Walk through the code: trace-trigger

  1. Blender

    On any collider, add Event Messages rows: When (trigger/collision enter/exit), target, message, optional tag filter.

    blender_addon/components/ · event_messages on COLLIDER

  2. Export

    Rows serialize as eventMessages: [{ when, target, message, filterTag }] on the collider row.

    blender_addon/export/component_serializers.py · SERIALIZERS registry

  3. Load

    WireCollisionEvents relays Havok collision/trigger observables to hooks and dispatches matching Event Messages via SendMessage.

    packages/engine/src/subsystems/collisions.ts · WireCollisionEvents

  4. Runtime

    Target behaviors handle messages in OnMessage(message, source); involved entities receive collision/trigger hooks on their own behaviors.

    packages/engine/src/core/Entity.ts · SendMessage

Constraint

Constraints join two physics bodies — hinges, sliders, locks — using pivots you place in Blender.

Walk through the code: trace-constraint

  1. Blender

    You add a CONSTRAINT row, pick type and connected body, set pivots and limits.

    blender_addon/components/ · CONSTRAINT component

  2. Export

    Pivots and axes convert to game space; the connected object’s GUID is included in the export.

    blender_addon/export/component_serializers.py · SERIALIZERS registry

  3. Load

    After both bodies exist, Havok constraints are built from live world poses (hinge motors optional).

    packages/engine/src/subsystems/constraints.ts · BuildConstraints

Input action

Input maps you define in Blender (keys, gamepad) become actions scripts read each frame via behavior.input.

Walk through the code: trace-input · 12 — Input

  1. Blender

    You author maps, actions, and bindings in the Input Actions panel; pick a scene default map.

    blender_addon/input_actions/ · Input Actions UI

  2. Export

    The whole input asset and default map name go into scene.inputActions in the manifest.

    blender_addon/input_actions/serialize.py · serialize_input_asset

  3. Load

    Input loads before behaviors; @inputMap("Name") fields get the matching map injected.

    packages/engine/src/input/InputManager.ts · LoadAsset; core/loader/scripts.ts · InjectInputMaps

  4. Runtime

    Each frame: poll devices → evaluate actions → scripts read values → clear “pressed this frame” edges.

    packages/engine/src/input/InputManager.ts · Process, EndFrame inside Level.RunFrame

Save in Blender re-exports the level and refreshes the browser so you see changes immediately.

Walk through the code: trace-livelink

  1. Blender

    You enable Live Link and set the export folder once; Ctrl+S triggers re-export.

    blender_addon/export/live_link.py · _on_save_post

  2. Export

    Validate + write glb, manifest, and side files to stable paths under public/levels/.

    blender_addon/export/ · export_level

  3. Runtime

    The dev server watches that folder and reloads the page after a short debounce.

    apps/playground/ · Vite ReloadOnLevelExport

Debug Build

A scene checkbox controls whether debug shortcuts (collider overlay, etc.) work in the running game.

  1. Blender

    You toggle Debug Build in scene settings.

    scene.bjs_debug_build

  2. Manifest

    Top-level "debug": true/false in the level JSON (missing means debug on).

    manifest · debug

  3. Runtime

    level.debugEnabled gates collider overlay (C) and related dev features in main.ts.

    apps/playground/src/main.ts · debug key handlers

2D GUI (GUI Editor JSON)

A GUI component loads a Babylon GUI Editor JSON file as a screen overlay on that entity.

Walk through the code: trace-gui

  1. Blender

    You add a GUI row and pick the exported GUI JSON and display mode.

    blender_addon/components/ · GUI component

  2. Export

    The JSON file is copied into the level’s gui/ folder.

    blender_addon/export/assets.py · copy_asset

  3. Load

    The loader builds an AdvancedDynamicTexture and attaches it when async loading finishes.

    packages/engine/src/ui/gui2d.ts · ApplyGui

  4. Runtime

    Retrieve with entity.GetGui("stem").

    packages/engine/src/core/Entity.ts · GetGui

Particles (Particle / Node Particle Editor JSON)

Particle systems authored in Babylon’s editors run from an empty or mesh emitter on the entity.

Walk through the code: trace-particles · Blender trace-particles

  1. Blender

    You add a PARTICLE row, pick the system JSON, assign texture images per slot.

    blender_addon/export/particles.py · export_particle_system

  2. Export

    JSON and textures copy into the level; texture URLs inside the JSON are patched to local paths.

    blender_addon/export/particles.py · URL patch for ParticleTextureSourceBlock

  3. Load

    The subsystem builds node or legacy particle systems on the entity’s node or a tracked empty.

    packages/engine/src/subsystems/particles.ts · ApplyParticles, WireParticleEmitterTracking

Node materials (NME JSON)

Materials authored in Babylon’s Node Material Editor export as JSON plus textures; the game binds them to imported meshes by material name.

Walk through the code: trace-materials · Blender trace-materials

  1. Blender

    On the material, link an NME JSON; optionally Extract Textures… when images are still embedded; scan slots, exposed inputs, and inspector-visible gradients; assign external images and tune parameter / color-stop values.

    blender_addon/materials/ · Scan NME · Extract Textures…

  2. Export

    Each distinct NME source JSON copies once; picked images copy to materials/; external overrides patch url / name and strip embeds; inputs and gradients patch value / colorSteps; embedded-only slots ship as-is in the JSON.

    blender_addon/export/materials.py · export_node_material

  3. Manifest

    Top-level materials[] lists file, textures, inspector inputs, and gradients per material name.

    manifest · materials[]

  4. Load

    After the glb loads, matching mesh materials are replaced with parsed node materials (parse + manifest overrides; no shader compile yet).

    packages/engine/src/subsystems/materials/apply.ts · ApplyNodeMaterials

  5. Finalize

    After scene environment IBL is ready, every node material compiles once (whenTexturesReadyAsync then build()) so ReflectionBlock picks up scene.environmentTexture.

    packages/engine/src/subsystems/materials/apply.ts · BuildNodeMaterials

3D GUI (buttons + panels)

3D GUI components build floating panels and buttons in world space; clicks can send messages to other entities.

Walk through the code: trace-gui3d · Blender trace-gui3d · 11 — UI

  1. Blender

    You add GUI3D panel/button/handle rows, layout, images, and click → target + message.

    blender_addon/export/component_serializers.py · GUI3D serializers in SERIALIZERS

  2. Load

    Panels are built first, then child controls; click handlers wire to SendMessage.

    packages/engine/src/ui/gui3d/build.ts · BuildGui3DControls; events.ts · WireClickEvents

  3. Runtime

    Target behaviors receive the message in OnMessage — same path as physics triggers.

    packages/engine/src/core/Entity.ts · SendMessage