Skip to content

BabylonJS - Animations

Babylon.js - Animations, Sprites, and Particles

Section titled “Babylon.js - Animations, Sprites, and Particles”

Animation brings your 3D scenes to life. Babylon.js provides a powerful and flexible animation system capable of animating almost any property of an object over time, including position, rotation, scaling, material colors, light intensity, and more. This section also covers Sprites (2D images in 3D space) and Particle Systems (for effects like fire, smoke, etc.), which often involve animation principles.

The core of the Babylon.js animation system involves:

  • BABYLON.Animation Object: Defines what property to animate (e.g., position.x), the type of data (e.g., float, vector, color), the animation speed (framePerSecond), and looping behavior.
  • Keyframes: An array defining the value of the animated property at specific frames (points in time).
  • Target: The object whose property is being animated (e.g., a mesh, light, camera).
  • scene.beginAnimation: Starts the animation on a target object.
import * as BABYLON from '@babylonjs/core';
// Assuming 'scene' and 'targetMesh' are defined
// 1. Create Animation object
const frameRate = 30;
const propertyToAnimate = "position.y"; // Animate vertical position
const yAnimation = new BABYLON.Animation(
"yAnim", // Animation name
propertyToAnimate, // Property to animate (can be nested, e.g., 'material.alpha')
frameRate, // Frames per second
BABYLON.Animation.ANIMATIONTYPE_FLOAT, // Data type (FLOAT, VECTOR3, COLOR3, QUATERNION, etc.)
BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE // Loop behavior (CYCLE, RELATIVE, CONSTANT)
);
// 2. Create Keyframes
const keys = [];
// At frame 0, position.y = 0
keys.push({ frame: 0, value: 0 });
// At frame 30 (1 second), position.y = 5
keys.push({ frame: 30, value: 5 });
// At frame 60 (2 seconds), position.y = 0
keys.push({ frame: 60, value: 0 });
// 3. Assign Keys to Animation
yAnimation.setKeys(keys);
// 4. Link Animation to Target Mesh
// Ensure the mesh has an 'animations' array initialized
if (!targetMesh.animations) {
targetMesh.animations = [];
}
targetMesh.animations.push(yAnimation);
// 5. Start the Animation
// Parameters: target, start frame, end frame, loop (boolean), speed ratio, onAnimationEnd callback
const animatable = scene.beginAnimation(targetMesh, 0, 60, true, 1.0);
// animatable allows controlling the running animation (pause, stop, etc.)
// animatable.pause();
  • ANIMATIONLOOPMODE_CYCLE: Restarts the animation from the beginning value.
  • ANIMATIONLOOPMODE_RELATIVE: Uses the end value as the starting point for the next loop iteration (good for continuous rotation or movement).
  • ANIMATIONLOOPMODE_CONSTANT: Stops at the end value and holds it.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Babylon.js - Basic Animation</title>
<script src="https://cdn.babylonjs.com/babylon.js"></script>
<style>
html, body { overflow: hidden; width: 100%; height: 100%; margin: 0; padding: 0; }
#renderCanvas { width: 100%; height: 100%; touch-action: none; }
</style>
</head>
<body>
<canvas id="renderCanvas"></canvas>
<script type="text/javascript">
const canvas = document.getElementById("renderCanvas");
const engine = new BABYLON.Engine(canvas, true);
const createScene = function () {
const scene = new BABYLON.Scene(engine);
scene.clearColor = new BABYLON.Color4(0.1, 0.2, 0.1, 1.0);
const camera = new BABYLON.ArcRotateCamera("Camera", -Math.PI / 2, Math.PI / 2.5, 15, BABYLON.Vector3.Zero(), scene);
camera.attachControl(canvas, true);
const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
light.intensity = 0.8;
const box = BABYLON.MeshBuilder.CreateBox("box", { size: 2 }, scene);
box.position.x = -3;
const boxMat = new BABYLON.StandardMaterial("boxMat", scene);
boxMat.diffuseColor = BABYLON.Color3.Red();
box.material = boxMat;
const box2 = BABYLON.MeshBuilder.CreateBox("box2", { size: 2 }, scene);
box2.position.x = 3;
const box2Mat = new BABYLON.StandardMaterial("box2Mat", scene);
box2Mat.diffuseColor = BABYLON.Color3.Blue();
box2.material = box2Mat;
// --- Animation for Box 1 (Scaling X) ---
const scaleXAnim = new BABYLON.Animation("scaleXAnim", "scaling.x", 30,
BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
const scaleKeys = [];
scaleKeys.push({ frame: 0, value: 1 });
scaleKeys.push({ frame: 30, value: 0.2 });
scaleKeys.push({ frame: 60, value: 1.5 });
scaleKeys.push({ frame: 90, value: 1 });
scaleXAnim.setKeys(scaleKeys);
box.animations.push(scaleXAnim);
scene.beginAnimation(box, 0, 90, true);
// ---------------------------------------
// --- Animation for Box 2 (Rotation Y - Relative) ---
const rotateYAnim = new BABYLON.Animation("rotateYAnim", "rotation.y", 30,
BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_RELATIVE);
const rotateKeys = [];
rotateKeys.push({ frame: 0, value: 0 });
rotateKeys.push({ frame: 60, value: Math.PI * 2 }); // One full rotation over 2 seconds
rotateYAnim.setKeys(rotateKeys);
box2.animations.push(rotateYAnim);
scene.beginAnimation(box2, 0, 60, true);
// ----------------------------------------
return scene;
};
const scene = createScene();
engine.runRenderLoop(function () { scene.render(); });
window.addEventListener("resize", function () { engine.resize(); });
</script>
</body>
</html>

This demo shows two cubes. The red cube on the left continuously scales along its X-axis (width), shrinking, then growing larger, then returning to normal size, looping the cycle. The blue cube on the right rotates continuously around its Y-axis using relative looping, making it spin indefinitely.

The Animatable object returned by scene.beginAnimation allows control:

  • animatable.pause(): Pauses the animation.
  • animatable.restart(): Restarts the animation from the beginning.
  • animatable.stop(): Stops the animation and removes it.
  • animatable.goToFrame(frameNumber): Jumps to a specific frame.

Easier Animation with CreateAndStartAnimation

Section titled “Easier Animation with CreateAndStartAnimation”

For simple animations between two values, use the static helper:

// Animate box position.x from current value to 5 over 120 frames (4 seconds at 30fps)
BABYLON.Animation.CreateAndStartAnimation(
'boxSlide', // Animation name
box, // Target mesh
'position.x',// Property to animate
30, // FPS
120, // Total frames
box.position.x, // Starting value
5, // Ending value
BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT // Loop mode (e.g., play once)
);

Easing functions control the rate of change in an animation, making movement appear more natural (e.g., starting slow, speeding up, then slowing down). You apply an easing function to an Animation object.

// Create an easing function (e.g., quadratic ease in/out)
const easingFunc = new BABYLON.QuadraticEase();
// Set easing mode (EaseIn, EaseOut, EaseInOut)
easingFunc.setEasingMode(BABYLON.EasingFunction.EASINGMODE_EASEINOUT);
// Apply it to an animation object BEFORE setting keys or starting
myAnimation.setEasingFunction(easingFunc);

Common easing functions include: LinearEase, CircleEase, BackEase, BounceEase, CubicEase, ElasticEase, ExponentialEase, PowerEase, QuadraticEase, QuarticEase, QuinticEase, SineEase.

You can trigger code at specific frames within an animation.

// Create an event to trigger at frame 50
const animEvent = new BABYLON.AnimationEvent(
50, // Frame number
() => { console.log("Reached frame 50!"); }, // Action callback
true // Only trigger once?
);
// Add the event to the animation object
myAnimation.addEvent(animEvent);

Sprites are 2D images used within a 3D scene. They are efficient for rendering large numbers of simple objects like trees, grass, particles, or billboarded characters. Sprites always face the camera (billboarding) by default.

Sprites are managed by a SpriteManager, which optimizes rendering. You create a manager for each sprite sheet (texture) you want to use.

// 1. Create a Sprite Manager
// Parameters: name, texture URL, capacity (max sprites), cell size (width/height), scene
const spriteManager = new BABYLON.SpriteManager(
"treeManager",
"textures/tree.png", // Image containing the sprite(s)
2000, // Max number of trees this manager can handle
{ width: 64, height: 64 }, // Size of each sprite cell in the texture
scene
);
// 2. Create Sprite Instances
const treeSprite = new BABYLON.Sprite("tree", spriteManager);
// 3. Position and configure the sprite instance
treeSprite.position = new BABYLON.Vector3(5, 0.5, 10);
treeSprite.size = 2.0; // Adjust display size
// treeSprite.angle = Math.PI / 4; // Rotate the sprite plane
// treeSprite.cellIndex = 0; // If sprite sheet has multiple cells, select one
// You can create many sprites using the same manager
for (let i = 0; i < 100; i++) {
const anotherTree = new BABYLON.Sprite("tree" + i, spriteManager);
anotherTree.position = new BABYLON.Vector3(Math.random() * 50 - 25, 0.5, Math.random() * 50 - 25);
anotherTree.size = 1.5 + Math.random(); // Random size
}

If your sprite texture contains multiple frames of an animation (a sprite sheet), you can animate it using sprite.playAnimation().

// Assuming 'playerSpriteSheet.png' has animation frames in cells 0 to 7
const playerManager = new BABYLON.SpriteManager("playerMgr", "textures/playerSpriteSheet.png", 1, {width: 64, height: 64}, scene);
const player = new BABYLON.Sprite("player", playerManager);
player.position.y = 0.5;
// Play animation from cell 0 to 7, loop, at 100ms delay per frame
player.playAnimation(0, 7, true, 100);

Particle systems are used to simulate phenomena involving many small elements, such as fire, smoke, explosions, rain, sparks, or magic effects. Babylon.js has CPU-based (ParticleSystem) and high-performance GPU-based (GPUParticleSystem) particle systems.

You configure a particle system by defining properties like the emitter (where particles originate), textures, colors over lifetime, size over lifetime, emission rate, lifetime duration, speed, gravity, etc.

// 1. Create a Particle System (using GPU for better performance)
const particleSystem = new BABYLON.GPUParticleSystem("particles", { capacity: 5000 }, scene);
// For CPU: new BABYLON.ParticleSystem("particles", 5000, scene);
// 2. Set Particle Texture
particleSystem.particleTexture = new BABYLON.Texture("textures/flare.png", scene);
// 3. Set Emitter (where particles come from)
// Can be a Vector3 point or a mesh
// particleSystem.emitter = new BABYLON.Vector3(0, 1, 0); // Emit from a point
const fountainMesh = BABYLON.MeshBuilder.CreateBox("fountainEmitter", {size: 0.1}, scene);
fountainMesh.isVisible = false; // Hide the emitter mesh itself
particleSystem.emitter = fountainMesh;
// Or emit from a volume (e.g., inside a box)
particleSystem.minEmitBox = new BABYLON.Vector3(-0.5, 0, -0.5);
particleSystem.maxEmitBox = new BABYLON.Vector3(0.5, 0, 0.5);
// 4. Configure Particle Appearance & Behavior
// Colors
particleSystem.color1 = new BABYLON.Color4(1, 0.5, 0, 1.0); // Start color (e.g., orange)
particleSystem.color2 = new BABYLON.Color4(1, 0, 0, 1.0); // Mid color (e.g., red)
particleSystem.colorDead = new BABYLON.Color4(0.2, 0, 0, 0.0); // End color (e.g., dark red, transparent)
// Size
particleSystem.minSize = 0.1;
particleSystem.maxSize = 0.5;
// Lifetime
particleSystem.minLifeTime = 0.5; // seconds
particleSystem.maxLifeTime = 2.0; // seconds
// Emission Rate
particleSystem.emitRate = 1000; // Particles per second
// Emission Direction (example: upward fountain)
particleSystem.direction1 = new BABYLON.Vector3(-0.2, 0.8, -0.2);
particleSystem.direction2 = new BABYLON.Vector3(0.2, 0.8, 0.2);
// Speed
particleSystem.minEmitPower = 1;
particleSystem.maxEmitPower = 3;
particleSystem.updateSpeed = 0.01; // Simulation speed adjustment
// Gravity
particleSystem.gravity = new BABYLON.Vector3(0, -9.81, 0);
// Blending Mode (for transparent textures)
particleSystem.blendMode = BABYLON.ParticleSystem.BLENDMODE_ONEONE; // Additive blending often looks good for fire/magic
// 5. Start the system
particleSystem.start();
// To stop: particleSystem.stop();
// To dispose: particleSystem.dispose();

Imagine combining these concepts. Create a ground plane. Use a SpriteManager to scatter hundreds of tree sprites across it randomly. Create a GPUParticleSystem with a flare.png texture, configure it to look like smoke (grayish colors, slow upward movement, low gravity, longer lifetime), set its emitter position, and start it. The result would be a landscape with many trees and a smoking effect rising from a specific point.