Babylon.js provides a powerful animation system that enables smooth transitions and movements for all scene objects. From simple property changes to complex character animations, the framework offers comprehensive tools for bringing your 3D world to life.

Create animations by defining keyframes for any object property:

// Create a box to animate
const box = BABYLON.MeshBuilder.CreateBox("box", {size: 1}, scene);

// Create an animation for position
const animation = new BABYLON.Animation(
    "positionAnimation",   // Name
    "position.y",          // Property to animate
    30,                     // Frames per second
    BABYLON.Animation.ANIMATIONTYPE_FLOAT, // Type of value
    BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE // Loop mode
);

// Define keyframes
const keyFrames = [];
keyFrames.push({ frame: 0, value: 0 });     // Start at y=0
keyFrames.push({ frame: 30, value: 3 });    // Move to y=3 at frame 30
keyFrames.push({ frame: 60, value: 0 });    // Back to y=0 at frame 60

// Assign keyframes to animation
animation.setKeys(keyFrames);

// Attach animation to the box
box.animations = [animation];

// Start the animation
scene.beginAnimation(box, 0, 60, true); // true = loop

Add natural motion with easing functions:

// Create an easing function
const easingFunction = new BABYLON.CircleEase();
easingFunction.setEasingMode(BABYLON.EasingFunction.EASINGMODE_EASEINOUT);

// Apply to animation
animation.setEasingFunction(easingFunction);

Available easing types: BABYLON.BackEase, BABYLON.BounceEase, BABYLON.CircleEase, BABYLON.CubicEase, BABYLON.ElasticEase, BABYLON.ExponentialEase, BABYLON.PowerEase, BABYLON.QuadraticEase, BABYLON.QuarticEase, BABYLON.QuinticEase, BABYLON.SineEase

Smoothly transition between animations:

// Create a second animation
const animation2 = new BABYLON.Animation(
    "scaleAnimation",
    "scaling",
    30,
    BABYLON.Animation.ANIMATIONTYPE_VECTOR3,
    BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
);

// Define keyframes for scaling
const scaleKeyFrames = [];
scaleKeyFrames.push({ frame: 0, value: new BABYLON.Vector3(1, 1, 1) });
scaleKeyFrames.push({ frame: 30, value: new BABYLON.Vector3(2, 0.5, 2) });
scaleKeyFrames.push({ frame: 60, value: new BABYLON.Vector3(1, 1, 1) });
animation2.setKeys(scaleKeyFrames);

// Start with position animation
const animatable = scene.beginAnimation(box, 0, 60, true);

// Later, blend to scale animation
setTimeout(() => {
    box.animations = [animation2];
    scene.beginAnimation(box, 0, 60, true, 1.0); // 1.0 = blend speed
}, 3000);

Synchronize multiple animations:

// Create an animation group
const animationGroup = new BABYLON.AnimationGroup("myGroup");

// Add animations to the group
animationGroup.addTargetedAnimation(animation, box);
// Create and add rotation animation
const rotationAnimation = new BABYLON.Animation(
    "rotationAnimation",
    "rotation.y",
    30,
    BABYLON.Animation.ANIMATIONTYPE_FLOAT,
    BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
);

const rotationKeys = [];
rotationKeys.push({ frame: 0, value: 0 });
rotationKeys.push({ frame: 60, value: Math.PI * 2 });
rotationAnimation.setKeys(rotationKeys);

animationGroup.addTargetedAnimation(rotationAnimation, box);
// Control the animation group
animationGroup.play(true); // true = loop
// animationGroup.pause();
// animationGroup.stop();
// animationGroup.reset();

// Adjust speed
animationGroup.speedRatio = 0.5; // Half speed

// Animation group events
animationGroup.onAnimationEndObservable.add(() => {
    console.log("Animation group completed");
});

Work with rigged character animations:

// Import a rigged model
BABYLON.SceneLoader.ImportMesh("", "models/", "character.glb", scene, (meshes, particleSystems, skeletons, animationGroups) => {
    const character = meshes[0];
    const skeleton = skeletons[0];
    
    // Play an animation by name
    const idleAnim = animationGroups.find(a => a.name === "Idle");
    const walkAnim = animationGroups.find(a => a.name === "Walking");
    const runAnim = animationGroups.find(a => a.name === "Running");
    
    if (idleAnim) idleAnim.play(true);

Switch between animations with blending:

    // Later, switch animations with blending
    document.getElementById("walk").addEventListener("click", () => {
        idleAnim.stop();
        walkAnim.play(true);
        walkAnim.speedRatio = 1.0;
    });
    
    document.getElementById("run").addEventListener("click", () => {
        walkAnim.stop();
        runAnim.play(true);
        runAnim.speedRatio = 1.5;
    });

Access skeleton bones for fine control:

    // Access skeleton bones for fine control
    const rightArm = skeleton.bones.find(b => b.name === "RightArm");
    if (rightArm) {
        // Override bone rotation
        rightArm.rotation.z += Math.PI / 8;
    }
});

Animate between different mesh shapes:

// Import a model with morph targets
BABYLON.SceneLoader.ImportMesh("", "models/", "face.glb", scene, (meshes) => {
    const face = meshes[0];
    
    // Access morph targets
    const morphTargetManager = face.morphTargetManager;
    
    if (morphTargetManager) {
        // Get specific morph targets
        const smileTarget = morphTargetManager.getTarget(0);
        const frownTarget = morphTargetManager.getTarget(1);
        const blinkTarget = morphTargetManager.getTarget(2);
        // Animate morph target influences
        let time = 0;
        scene.onBeforeRenderObservable.add(() => {
            time += scene.getEngine().getDeltaTime() / 1000;
            
            // Smile-frown cycle
            smileTarget.influence = Math.sin(time * 0.5) * 0.5 + 0.5;
            frownTarget.influence = Math.sin(time * 0.5 + Math.PI) * 0.5 + 0.5;
            
            // Occasional blink
            blinkTarget.influence = Math.pow(Math.sin(time * 3), 16);
        });
    }
});

Trigger events at specific points in animations:

// Create an animation with events
const jumpAnimation = new BABYLON.Animation(
    "jumpAnimation",
    "position.y",
    60,
    BABYLON.Animation.ANIMATIONTYPE_FLOAT,
    BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
);

const jumpKeys = [];
jumpKeys.push({ frame: 0, value: 0 });
jumpKeys.push({ frame: 30, value: 5 });
jumpKeys.push({ frame: 60, value: 0 });
jumpAnimation.setKeys(jumpKeys);
// Add events at specific frames
jumpAnimation.addEvent(new BABYLON.AnimationEvent(
    10, // Frame number
    function() { 
        // Event callback
        console.log("Starting to jump"); 
        // Play sound
        const jumpSound = new BABYLON.Sound("jump", "sounds/jump.mp3", scene);
        jumpSound.play();
    }
));

jumpAnimation.addEvent(new BABYLON.AnimationEvent(
    50, // Frame number
    function() { 
        console.log("Landing"); 
        // Play landing sound
        const landSound = new BABYLON.Sound("land", "sounds/land.mp3", scene);
        landSound.play();
    }
));

// Apply and play
box.animations = [jumpAnimation];
scene.beginAnimation(box, 0, 60, true);

Create animations directly in code:

// Animate in the render loop
let time = 0;
scene.onBeforeRenderObservable.add(() => {
    time += scene.getEngine().getDeltaTime() / 1000;
    
    // Circular motion
    sphere.position.x = Math.cos(time) * 5;
    sphere.position.z = Math.sin(time) * 5;
    
    // Bobbing motion
    sphere.position.y = 2 + Math.sin(time * 2) * 0.5;
    
    // Continuous rotation
    box.rotation.y += 0.01;
    box.rotation.x = Math.sin(time) * 0.2;
});

Babylon.js includes additional animation tools and techniques:

Use animation weights to smoothly blend between multiple animations simultaneously. This is essential for creating natural character movement transitions:

// Enable animation blending on the skeleton
scene.animationPropertiesOverride = new BABYLON.AnimationPropertiesOverride();
scene.animationPropertiesOverride.enableBlending = true;
scene.animationPropertiesOverride.blendingSpeed = 0.05;

// Control animation weights for blending
const idleAnim = animationGroups[0];
const walkAnim = animationGroups[1];

idleAnim.play(true);
walkAnim.play(true);

// Blend from idle to walk
idleAnim.setWeightForAllAnimatables(1.0);
walkAnim.setWeightForAllAnimatables(0.0);

// Gradually transition
let blendFactor = 0;
scene.onBeforeRenderObservable.add(() => {
    if (isWalking && blendFactor < 1) {
        blendFactor = Math.min(1, blendFactor + 0.02);
        idleAnim.setWeightForAllAnimatables(1 - blendFactor);
        walkAnim.setWeightForAllAnimatables(blendFactor);
    }
});

While Babylon.js does not include a built-in animation state machine, you can implement one using animation groups and weight blending:

// Simple animation state manager pattern
class AnimationStateManager {
    constructor(animationGroups) {
        this.animations = new Map();
        this.currentState = null;
        for (const group of animationGroups) {
            this.animations.set(group.name, group);
            group.play(true);
            group.setWeightForAllAnimatables(0);
        }
    }
    
    transition(stateName, blendDuration = 0.3) {
        const target = this.animations.get(stateName);
        if (!target || this.currentState === stateName) return;
        
        // Fade out current, fade in target
        if (this.currentState) {
            const current = this.animations.get(this.currentState);
            current.setWeightForAllAnimatables(0);
        }
        target.setWeightForAllAnimatables(1);
        this.currentState = stateName;
    }
}

// Usage
const stateManager = new AnimationStateManager(result.animationGroups);
stateManager.transition("Idle");
// Later: stateManager.transition("Walking");