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.
- 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(storesbjs_idon the object) - Export
That ID is written into the 3D model file attached to each node.
blender_addon/export/level.py· glTF nodeextras.bjs_id - Manifest
The same ID appears as
entities[].idin the level JSON.manifest ·
entities[].id - 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 - Runtime
You get an
Entityinlevel.entities; the 3D node remembers which entity owns it.packages/engine/src/core/Entity.ts· nodemetadata.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.
- 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 - 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) - Load
Hidden nodes are turned off; lights and cameras under them are disabled too.
packages/engine/src/core/loader/nodeResolution.ts·ApplyNodeVisibility - Load
Meshes marked “no shadow cast” only receive shadows; they do not cast them.
packages/engine/src/subsystems/shadows.ts·SetupShadows - 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.
- Blender
You add a TAG component and type a label (e.g.
Enemy).blender_addon/components/· TAG component UI - Export
Export writes one TAG row into that object’s component list.
blender_addon/export/component_serializers.py·SERIALIZERSregistry - Manifest
The JSON holds
{ "type": "TAG", "tag": "Enemy" }.manifest ·
entities[].components[] - Load
The loader sets
entity.tagand records the attachment.packages/engine/src/core/loader/componentRegistry.ts· tag handler,RegisterAttachment - Runtime
Query with
level.ByTag("Enemy")or checkentity.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.
- 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 - Export
Each enabled row serializes through the
SERIALIZERSregistry.blender_addon/export/component_serializers.py·_serialize_rendering_group,_serialize_layer_mask - Manifest
Example rows:
{ "type": "RENDERING_GROUP", "renderingGroupId": 2, "applyOwnedMeshes": true, "applyChildEntities": true }and{ "type": "LAYER_MASK", "layerMask": 268435456, … }.manifest ·
entities[].components[] - Load
Handlers record attachment rows during the entity pass; mesh values are deferred.
packages/engine/src/core/loader/componentRegistry.ts·RenderingGroupHandler,LayerMaskHandler - Load
After every entity, probe, and async asset exists,
ApplyRenderLayerswalks the manifest parent tree: owned meshes viaCollectOwnedMeshes; child propagation stops at the first descendant with its own component of that kind.packages/engine/src/subsystems/renderLayers.ts·ApplyRenderLayers; called fromLevelLoader.FinalizeLevel - 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.
- 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 - Export
Scene block →
scene.collisionLayers; component row →COLLISION_LAYERviaSERIALIZERS.blender_addon/collision_layers/serialize.py·serialize_collision_layers;export/component_serializers.py·_serialize_collision_layer - Manifest
Example scene block:
{ "layers": ["Default","Player"], "matrix": [[true,true],[true,false]] }. Component:{ "type": "COLLISION_LAYER", "layer": "Player", … }.manifest ·
scene.collisionLayers,entities[].components[] - Load
BuildPhysicsregisters each shape incontext.physicsShapesByEntity. Handler records attachment rows only.packages/engine/src/subsystems/collisionLayers.ts·RegisterPhysicsShapeForEntity;componentRegistry.ts·CollisionLayerHandler - Load
After constraints,
ApplyCollisionLayersresolves layer names → membership/collide masks and sets them on every registered shape (compound children included).packages/engine/src/subsystems/collisionLayers.ts·ApplyCollisionLayers;LevelLoader.FinalizeLevel - 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
- 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 - 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·SERIALIZERSregistry - Manifest
The level file lists
COLLIDERandRIGIDBODYentries under the entity. Multiple colliders on one object are intentional — they combine into one body.manifest ·
entities[].components[] - 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 - 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 - Load
Mass, friction, and center of mass are applied for dynamic bodies.
packages/engine/src/subsystems/physics/bodyBuild.ts·ApplyMassProperties - 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
- Blender
You pick a script file and click Sync — the panel fills with fields marked
@exposedin the TypeScript source.blender_addon/core/script_parse.py·parse_exposed - Export
Your edited values are written into the SCRIPT row’s
varsobject (entity picks become GUID strings).blender_addon/export/components.py·_serialize_vars - Manifest
Each scripted object has
{ "type": "SCRIPT", "script": "MyBehavior", "vars": { … } }.manifest ·
entities[].components[] - Load
The loader creates a behavior instance from the registry, injects
entityandscene, then copiesvarsonto the instance.packages/engine/src/core/loader/scripts.ts·InstantiateScripts - Load
Entity-reference fields wait until every entity exists, then resolve to the real object.
packages/engine/src/core/loader/entityBuilder.ts·ResolveObjectReferences - Runtime
OnStartruns 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
- 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·Main→LevelLoader.Load - Runtime
The app starts Babylon’s frame loop — without this line, nothing animates and
OnUpdatenever runs.apps/playground/src/main.ts·engine.runRenderLoop(() => scene.render()) - Runtime
At the start of each frame: particle emitters sync, then the kit runs input + all
OnUpdatehooks.packages/engine/src/core/Level.ts·RunFrameononBeforeRenderObservable - Runtime
Havok advances physics, then the GPU draws meshes, shadows, and post effects.
Babylon · physics step inside
scene.render() - 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
- Load
Behavior objects are created but none of the hooks run yet.
packages/engine/src/core/loader/scripts.ts·InstantiateScripts - Load
Cross-object references resolve; triggers, constraints, and GUI finish wiring.
packages/engine/src/core/LevelLoader.ts·FinalizeLevel - Runtime
Beginattaches input, calls everyOnStartonce, and subscribesRunFrameto the render loop.packages/engine/src/core/Level.ts·Begin - Runtime
Each frame: read input → all
OnUpdate→ camera updaters → clear input edges.packages/engine/src/core/Level.ts·RunFrame - Runtime
Triggers, GUI clicks, and
SendMessagecallOnMessageon target behaviors (any time, not only on frame boundaries).packages/engine/src/core/Entity.ts·SendMessage - Runtime
Leaving the level calls every
OnDestroyand 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
- 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 - Export
Every enabled row becomes one entry in the manifest component array.
blender_addon/export/component_serializers.py·SERIALIZERSregistry - 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 - 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.
- 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 - 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 - Manifest
vars.myTarget = "abc-123-guid"in the SCRIPT row.manifest ·
entities[].components[].vars - Load
After all entities exist, GUIDs become
Entityreferences 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
- Blender
You place a lamp and optionally enable Cast Shadows and sun angle (for soft shadows).
Blender · lamp object, shadow settings
- Export
Each lamp becomes a
lightblock in the manifest (type, color, intensity, shadow settings).blender_addon/export/datablocks.py·serialize_light - Load
The loader creates or configures the matching Babylon light on the imported node.
packages/engine/src/subsystems/lights.ts·ApplyBlenderLight - 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 - 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
- Blender
You mark one camera active and set clip range and field of view.
Blender · camera object
- Export
Basic camera settings export in the entity’s
camerablock; optional CAMERA component adds control type and speeds.blender_addon/export/datablocks.py·serialize_camera;export/components.py· CAMERA row - 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 - 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
- Blender
You tune atmosphere in the Babylon Scene panel (linked to a sun lamp for direction).
blender_addon/ui/scene_panels.py· Atmosphere section - Export
Settings serialize to
scene.atmospherein the manifest; env skybox is forced off when atmosphere is on.blender_addon/export/atmosphere.py·serialize_atmosphere - 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
- Blender
You enable and tune post effects; LUT images are copied beside the level.
blender_addon/ui/post_panels.py· Post-Processing section - Export
All settings go to
scene.postProcessingin the manifest.blender_addon/export/post_processing.py·serialize_post_processing - Load
Post stack is applied after
Beginso cameras created inOnStartstill 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.
- Blender
You add NLA strips and choose which clip should autoplay (on the armature for skinned characters).
Blender · NLA editor
- Export
Strip names and autoplay choice serialize into the entity’s
animationblock.blender_addon/export/animation.py·serialize_animation - 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
- Blender
You add an AUDIO row and pick a sound file and options (spatial, autoplay, loop).
blender_addon/components/· AUDIO component - Export
The file is copied into the level’s
audio/folder and referenced from the manifest.blender_addon/export/assets.py·copy_asset - Load
Sounds are created asynchronously; autoplay waits for browser audio unlock if needed.
packages/engine/src/subsystems/audio.ts·ApplyAudio - 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
- Blender
On any collider, add Event Messages rows: When (trigger/collision enter/exit), target, message, optional tag filter.
blender_addon/components/·event_messageson COLLIDER - Export
Rows serialize as
eventMessages: [{ when, target, message, filterTag }]on the collider row.blender_addon/export/component_serializers.py·SERIALIZERSregistry - Load
WireCollisionEventsrelays Havok collision/trigger observables to hooks and dispatches matching Event Messages viaSendMessage.packages/engine/src/subsystems/collisions.ts·WireCollisionEvents - 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
- Blender
You add a CONSTRAINT row, pick type and connected body, set pivots and limits.
blender_addon/components/· CONSTRAINT component - Export
Pivots and axes convert to game space; the connected object’s GUID is included in the export.
blender_addon/export/component_serializers.py·SERIALIZERSregistry - 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
- Blender
You author maps, actions, and bindings in the Input Actions panel; pick a scene default map.
blender_addon/input_actions/· Input Actions UI - Export
The whole input asset and default map name go into
scene.inputActionsin the manifest.blender_addon/input_actions/serialize.py·serialize_input_asset - 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 - Runtime
Each frame: poll devices → evaluate actions → scripts read values → clear “pressed this frame” edges.
packages/engine/src/input/InputManager.ts·Process,EndFrameinsideLevel.RunFrame
Live Link
Save in Blender re-exports the level and refreshes the browser so you see changes immediately.
Walk through the code: trace-livelink
- 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 - Export
Validate + write glb, manifest, and side files to stable paths under
public/levels/.blender_addon/export/·export_level - Runtime
The dev server watches that folder and reloads the page after a short debounce.
apps/playground/· ViteReloadOnLevelExport
Debug Build
A scene checkbox controls whether debug shortcuts (collider overlay, etc.) work in the running game.
- Blender
You toggle Debug Build in scene settings.
scene.bjs_debug_build - Manifest
Top-level
"debug": true/falsein the level JSON (missing means debug on).manifest ·
debug - Runtime
level.debugEnabledgates collider overlay (C) and related dev features inmain.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
- Blender
You add a GUI row and pick the exported GUI JSON and display mode.
blender_addon/components/· GUI component - Export
The JSON file is copied into the level’s
gui/folder.blender_addon/export/assets.py·copy_asset - Load
The loader builds an AdvancedDynamicTexture and attaches it when async loading finishes.
packages/engine/src/ui/gui2d.ts·ApplyGui - 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
- Blender
You add a PARTICLE row, pick the system JSON, assign texture images per slot.
blender_addon/export/particles.py·export_particle_system - 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 forParticleTextureSourceBlock - 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
- 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… - Export
Each distinct NME source JSON copies once; picked images copy to
materials/; external overrides patchurl/nameand strip embeds; inputs and gradients patchvalue/colorSteps; embedded-only slots ship as-is in the JSON.blender_addon/export/materials.py·export_node_material - Manifest
Top-level
materials[]lists file, textures, inspector inputs, and gradients per material name.manifest ·
materials[] - 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 - Finalize
After scene environment IBL is ready, every node material compiles once (
whenTexturesReadyAsyncthenbuild()) soReflectionBlockpicks upscene.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
- Blender
You add GUI3D panel/button/handle rows, layout, images, and click → target + message.
blender_addon/export/component_serializers.py· GUI3D serializers inSERIALIZERS - Load
Panels are built first, then child controls; click handlers wire to
SendMessage.packages/engine/src/ui/gui3d/build.ts·BuildGui3DControls;events.ts·WireClickEvents - Runtime
Target behaviors receive the message in
OnMessage— same path as physics triggers.packages/engine/src/core/Entity.ts·SendMessage