Scenes
The scene system provides the visual foundation for a game. Powered by BabylonJS, SceneEngine wraps common scene creation tasks while leaving the underlying BabylonJS API reachable.
SceneEngine
const sceneEngine = engine.engines.sceneEngine;Scene methods
| Method | Signature | Description |
|---|---|---|
createScene | () => Scene | Create a new BabylonJS scene |
enablePhysics | (scene, gravity?, floatingOriginWorldRadius?) => void | Enable Havok physics on a scene |
Camera methods
| Method | Signature | Description |
|---|---|---|
createFreeCamera | (name, position, scene, canvas?) => FreeCamera | A free camera aimed at the origin, with controls attached when a canvas is given |
Light methods
| Method | Signature | Description |
|---|---|---|
createHemisphericLight | (name, direction, scene, intensity?) => HemisphericLight | Ambient light from a hemisphere |
createDirectionalLight | (name, direction, scene, intensity?) => DirectionalLight | Directional light |
createPointLight | (name, position, scene, intensity?) => PointLight | Omnidirectional point light |
createSpotLight | (name, position, direction, angle, exponent, scene, intensity?) => SpotLight | Spot light |
createRectAreaLight | (name, position, width, height, scene, intensity?) => RectAreaLight | Rectangular area light |
Mesh methods
| Method | Signature | Description |
|---|---|---|
createSphere | (name, options, scene) => Mesh | Sphere |
createBox | (name, options, scene) => Mesh | Box |
createGround | (name, options, scene) => Mesh | Ground plane |
createCylinder | (name, options, scene) => Mesh | Cylinder |
Physics methods
| Method | Signature | Description |
|---|---|---|
addPhysics | (mesh, shapeType, options, scene) => PhysicsAggregate | Add a physics body to a mesh |
Model loading
| Method | Signature | Description |
|---|---|---|
loadModel | (path, scene) => Promise<AssetContainer> | Load a GLB/glTF into an asset container |
importMeshes | (meshNames, path, scene) => Promise<ImportMeshResult> | Import meshes from a file straight into the scene. An empty meshNames imports all of them. |
Creating a scene
In most cases, scenes are created inside a Level's buildScene() method:
const scene = sceneEngine.createScene();
sceneEngine.enablePhysics(scene);
const camera = sceneEngine.createFreeCamera('mainCamera', new Vector3(0, 5, -10), scene, canvas);
sceneEngine.createHemisphericLight('ambient', new Vector3(0, 1, 0), scene, 0.4);
sceneEngine.createDirectionalLight('sun', new Vector3(0.5, -0.6, 0.5), scene);
const ground = sceneEngine.createGround('ground', { width: 50, height: 50 }, scene);
sceneEngine.addPhysics(ground, PhysicsShapeType.BOX, { mass: 0 }, scene);A level that spawns entities also binds a simulation to its scene with this.gameEngine.rebuildSimulation(scene, meshSource). GameLevel does this itself; a custom Level does it in buildScene. See Levels.
Cameras
| Camera | Use |
|---|---|
FreeCamera | First-person, 6 degrees of freedom |
ArcRotateCamera | Third-person, orbits around a point |
FollowCamera | Follows a mesh |
import { ArcRotateCamera, FreeCamera, Vector3 } from '@babylonjs/core';
const fps = new FreeCamera('fps', new Vector3(0, 5, -10), scene);
fps.setTarget(Vector3.Zero());
fps.attachControl(canvas, true);
const orbit = new ArcRotateCamera('orbit', Math.PI / 4, Math.PI / 3, 10, Vector3.Zero(), scene);
orbit.attachControl(canvas, true);A camera whose keyboard controls would compete with SAGE's own bindings can drop them:
orbit.inputs.removeByType('ArcRotateCameraKeyboardMoveInput');Lighting
import { DirectionalLight, HemisphericLight, PointLight, Vector3 } from '@babylonjs/core';
const ambient = new HemisphericLight('ambient', new Vector3(0, 1, 0), scene);
ambient.intensity = 0.4;
const sun = new DirectionalLight('sun', new Vector3(0.5, -0.6, 0.5), scene);
sun.intensity = 0.7;
const torch = new PointLight('torch', new Vector3(5, 3, 0), scene);
torch.intensity = 0.8;A scene that contains Blender-exported content uses PBR materials and physical light units. See the Blender Workflow page for the intensities that work there.
Meshes and materials
import { Color3, MeshBuilder, PBRMaterial, StandardMaterial } from '@babylonjs/core';
const sphere = MeshBuilder.CreateSphere('sphere', { diameter: 2, segments: 32 }, scene);
sphere.position.y = 1;
const mat = new StandardMaterial('mat', scene);
mat.diffuseColor = new Color3(0.2, 0.4, 0.8);
sphere.material = mat;
const pbr = new PBRMaterial('pbr', scene);
pbr.albedoColor = new Color3(0.8, 0.2, 0.1);
pbr.metallic = 0.7;
pbr.roughness = 0.3;An entity's mesh is built by its mesh source, keyed by the entity's name, or declared through meshConfig and the standard mesh source. See Authoring.
Model loading
const container = await sceneEngine.loadModel('assets/models/character.glb', scene);
container.addAllToScene();
import { ImportMeshAsync } from '@babylonjs/core';
const result = await ImportMeshAsync('assets/models/character.glb', scene);AssetManager
For production games, use the AssetManager for centralized loading with caching, reference counting, and GLB fragment extraction:
const assetManager = engine.managers.assetManager;Loading
const container = await assetManager.load('models/environment.glb');
container.addAllToScene();Fragment syntax
Extract individual meshes from a multi-object GLB with path#meshName:
const chestLid = await assetManager.load('models/props.glb#chest_lid');
const torch = await assetManager.load('models/props.glb#wall_torch');The underlying container is loaded once, however many fragments are extracted. The standard mesh source uses the same syntax for a meshConfig whose source names a glb.
Instancing and cloning
// GPU instances share geometry and material
const barrel1 = assetManager.instance('models/props.glb#barrel');
const barrel2 = assetManager.instance('models/props.glb#barrel');
// An independent clone, for a mesh that needs its own geometry or material
const unique = assetManager.clone('models/props.glb#barrel');
unique.scaling.y = 1.5;Preloading
engine.eventBus.subscribe('asset:progress', (event) =>
{
const { path, loaded, total } = event.payload;
console.log(`Loaded ${ loaded }/${ total }: ${ path }`);
});
await assetManager.preload([
'models/environment.glb',
'models/props.glb#barrel',
'models/props.glb#crate',
]);A level config's preload list does the same before buildScene runs.
Reference counting and disposal
Each load() call increments a reference count. dispose() decrements it, and the container is freed when the count reaches zero:
await assetManager.load('models/props.glb#barrel');
await assetManager.load('models/props.glb#barrel');
assetManager.dispose('models/props.glb#barrel');
assetManager.dispose('models/props.glb#barrel');
assetManager.disposeAll();Picking
Resolving what a ray or a screen position hits to the entity that owns it is NodeSimulation's. See Picking.
Environment effects
Fog
scene.fogEnabled = true;
scene.fogColor = new Color3(0.8, 0.9, 0.8);
scene.fogDensity = 0.01;Particle systems
import { Color4, ParticleSystem, Texture, Vector3 } from '@babylonjs/core';
const fire = new ParticleSystem('fire', 2000, scene);
fire.particleTexture = new Texture('assets/textures/flame.png', scene);
fire.emitter = new Vector3(5, 0, 10);
fire.minEmitBox = new Vector3(-0.2, 0, -0.2);
fire.maxEmitBox = new Vector3(0.2, 0, 0.2);
fire.color1 = new Color4(1, 0.9, 0.3, 1);
fire.color2 = new Color4(1, 0.5, 0.2, 1);
fire.minSize = 0.3;
fire.maxSize = 1.5;
fire.minLifeTime = 0.2;
fire.maxLifeTime = 0.8;
fire.emitRate = 500;
fire.direction1 = new Vector3(-0.5, 4, -0.5);
fire.direction2 = new Vector3(0.5, 4, 0.5);
fire.minEmitPower = 1;
fire.maxEmitPower = 3;
fire.start();The inspector
import '@babylonjs/inspector';
window.addEventListener('keydown', (event) =>
{
if(event.key === 'F12')
{
if(scene.debugLayer.isVisible())
{
scene.debugLayer.hide();
}
else
{
scene.debugLayer.show();
}
}
});