Babylon.js 动画系统 #

动画概述 #

动画是 3D 场景的灵魂,Babylon.js 提供了强大的动画系统,支持关键帧动画、骨骼动画、粒子系统等多种动画类型。

text
┌─────────────────────────────────────────────────────────────┐
│                    Babylon.js 动画类型                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  关键帧动画 ─────── 属性动画                                │
│  - 位置、旋转、缩放                                         │
│  - 材质属性                                                 │
│  - 任何数值属性                                             │
│                                                             │
│  骨骼动画 ───────── 角色动画                                │
│  - 导入模型动画                                             │
│  - 骨骼变换                                                 │
│  - 蒙皮动画                                                 │
│                                                             │
│  粒子系统 ───────── 特效动画                                │
│  - 火焰、烟雾                                               │
│  - 爆炸、魔法效果                                           │
│  - 环境粒子                                                 │
│                                                             │
│  程序化动画 ─────── 代码驱动                                │
│  - 每帧更新                                                 │
│  - 物理模拟                                                 │
│  - 交互响应                                                 │
│                                                             │
└─────────────────────────────────────────────────────────────┘

关键帧动画 #

基本创建 #

javascript
// 创建动画
const animation = new BABYLON.Animation(
  'rotateAnimation',           // 名称
  'rotation.y',                // 目标属性
  30,                          // 帧率
  BABYLON.Animation.ANIMATIONTYPE_FLOAT,  // 动画类型
  BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE  // 循环模式
);

// 设置关键帧
const keyFrames = [
  { frame: 0, value: 0 },
  { frame: 60, value: Math.PI * 2 }
];
animation.setKeys(keyFrames);

// 应用动画
mesh.animations.push(animation);
scene.beginAnimation(mesh, 0, 60, true);

动画类型 #

text
┌─────────────────────────────────────────────────────────────┐
│                    动画类型                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ANIMATIONTYPE_FLOAT                                        │
│  - 浮点数动画                                               │
│  - 例:rotation.y, alpha, intensity                        │
│                                                             │
│  ANIMATIONTYPE_VECTOR3                                      │
│  - 三维向量动画                                             │
│  - 例:position, scaling                                   │
│                                                             │
│  ANIMATIONTYPE_QUATERNION                                   │
│  - 四元数动画                                               │
│  - 例:rotationQuaternion                                  │
│                                                             │
│  ANIMATIONTYPE_COLOR3                                       │
│  - 颜色动画                                                 │
│  - 例:diffuseColor                                        │
│                                                             │
│  ANIMATIONTYPE_MATRIX                                       │
│  - 矩阵动画                                                 │
│  - 例:bone matrices                                       │
│                                                             │
└─────────────────────────────────────────────────────────────┘

循环模式 #

javascript
// 循环模式
BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE;      // 循环
BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT;   // 保持最后帧
BABYLON.Animation.ANIMATIONLOOPMODE_RELATIVE;   // 相对循环

位置动画 #

javascript
const positionAnimation = new BABYLON.Animation(
  'positionAnim',
  'position',
  30,
  BABYLON.Animation.ANIMATIONTYPE_VECTOR3,
  BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
);

positionAnimation.setKeys([
  { frame: 0, value: new BABYLON.Vector3(0, 0, 0) },
  { frame: 30, value: new BABYLON.Vector3(2, 0, 0) },
  { frame: 60, value: new BABYLON.Vector3(2, 2, 0) },
  { frame: 90, value: new BABYLON.Vector3(0, 2, 0) },
  { frame: 120, value: new BABYLON.Vector3(0, 0, 0) }
]);

mesh.animations.push(positionAnimation);
scene.beginAnimation(mesh, 0, 120, true);

缩放动画 #

javascript
const scaleAnimation = new BABYLON.Animation(
  'scaleAnim',
  'scaling',
  30,
  BABYLON.Animation.ANIMATIONTYPE_VECTOR3,
  BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
);

scaleAnimation.setKeys([
  { frame: 0, value: new BABYLON.Vector3(1, 1, 1) },
  { frame: 30, value: new BABYLON.Vector3(2, 2, 2) },
  { frame: 60, value: new BABYLON.Vector3(1, 1, 1) }
]);

mesh.animations.push(scaleAnimation);
scene.beginAnimation(mesh, 0, 60, true);

颜色动画 #

javascript
const colorAnimation = new BABYLON.Animation(
  'colorAnim',
  'diffuseColor',
  30,
  BABYLON.Animation.ANIMATIONTYPE_COLOR3,
  BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
);

colorAnimation.setKeys([
  { frame: 0, value: new BABYLON.Color3(1, 0, 0) },
  { frame: 30, value: new BABYLON.Color3(0, 1, 0) },
  { frame: 60, value: new BABYLON.Color3(0, 0, 1) },
  { frame: 90, value: new BABYLON.Color3(1, 0, 0) }
]);

material.animations.push(colorAnimation);
scene.beginAnimation(material, 0, 90, true);

缓动函数 #

基本使用 #

javascript
// 创建缓动函数
const easingFunction = new BABYLON.QuadraticEase();
easingFunction.setEasingMode(BABYLON.EasingFunction.EASINGMODE_EASEINOUT);

// 应用到动画
animation.setEasingFunction(easingFunction);

缓动函数类型 #

javascript
// 线性
const linear = new BABYLON.LinearEase();

// 二次方
const quadratic = new BABYLON.QuadraticEase();

// 三次方
const cubic = new BABYLON.CubicEase();

// 四次方
const quartic = new BABYLON.QuarticEase();

// 五次方
const quintic = new BABYLON.QuinticEase();

// 正弦
const sine = new BABYLON.SineEase();

// 指数
const exponential = new BABYLON.ExponentialEase();

// 圆形
const circle = new BABYLON.CircleEase();

// 弹性
const elastic = new BABYLON.ElasticEase(1, 0.3);

// 弹跳
const bounce = new BABYLON.BounceEase(3);

// 回弹
const back = new BABYLON.BackEase(0.3);

缓动模式 #

javascript
// 缓入
easingFunction.setEasingMode(BABYLON.EasingFunction.EASINGMODE_EASEIN);

// 缓出
easingFunction.setEasingMode(BABYLON.EasingFunction.EASINGMODE_EASEOUT);

// 缓入缓出
easingFunction.setEasingMode(BABYLON.EasingFunction.EASINGMODE_EASEINOUT);

缓动效果对比 #

text
┌─────────────────────────────────────────────────────────────┐
│                    缓动效果曲线                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Linear:  ────────────────────                             │
│                                                             │
│  EaseIn:  ────                                              │
│                      ──────────                             │
│                                                             │
│  EaseOut: ──────────                                        │
│                      ────                                   │
│                                                             │
│  EaseInOut: ────                                            │
│                      ────                                   │
│                                                             │
│  Bounce:  ────  ──  ─  ─                                    │
│                                                             │
│  Elastic: ────  ──  ─  ─  ─  ─                             │
│                                                             │
└─────────────────────────────────────────────────────────────┘

动画控制 #

开始动画 #

javascript
// 开始动画
const animatable = scene.beginAnimation(
  mesh,           // 目标对象
  0,              // 起始帧
  60,             // 结束帧
  true,           // 是否循环
  1.0             // 速度比例
);

暂停和恢复 #

javascript
// 暂停
animatable.pause();

// 恢复
animatable.restart();

// 停止
animatable.stop();

动画事件 #

javascript
// 添加动画事件
const event1 = new BABYLON.AnimationEvent(
  30,  // 触发帧
  function() {
    console.log('到达第 30 帧');
  },
  true  // 是否只触发一次
);

animation.addEvent(event1);

// 多个事件
animation.addEvent(new BABYLON.AnimationEvent(0, () => console.log('开始')));
animation.addEvent(new BABYLON.AnimationEvent(60, () => console.log('结束')));

动画完成回调 #

javascript
scene.beginAnimation(mesh, 0, 60, false, 1, function() {
  console.log('动画完成');
});

动画组 #

创建动画组 #

javascript
// 创建动画组
const animationGroup = new BABYLON.AnimationGroup('group', scene);

// 添加目标动画
animationGroup.addTargetedAnimation(positionAnim, mesh1);
animationGroup.addTargetedAnimation(rotationAnim, mesh1);
animationGroup.addTargetedAnimation(positionAnim, mesh2);

// 播放
animationGroup.play(true);

// 控制
animationGroup.pause();
animationGroup.play();
animationGroup.stop();

// 设置速度
animationGroup.speedRatio = 2.0;

动画组事件 #

javascript
// 动画组完成
animationGroup.onAnimationGroupEndObservable.add(() => {
  console.log('动画组完成');
});

// 动画组循环
animationGroup.onAnimationGroupLoopObservable.add(() => {
  console.log('动画组循环');
});

// 帧变化
animationGroup.onAnimationGroupFrameEventObservable.add((frame) => {
  console.log('当前帧:', frame);
});

骨骼动画 #

加载骨骼动画 #

javascript
BABYLON.SceneLoader.ImportMesh(
  '',
  '/models/',
  'character.glb',
  scene,
  function(meshes, particleSystems, skeletons) {
    const skeleton = skeletons[0];
    
    // 播放动画
    scene.beginAnimation(skeleton, 0, 100, true);
  }
);

骨骼结构 #

text
┌─────────────────────────────────────────────────────────────┐
│                    骨骼结构示例                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│                    Root (骨骼根)                            │
│                      │                                      │
│                    Spine (脊柱)                             │
│                    /    \                                   │
│              LeftArm  RightArm                              │
│                 │        │                                  │
│            LeftHand  RightHand                              │
│                                                             │
│  每个骨骼可以:                                              │
│  - 有自己的位置、旋转、缩放                                  │
│  - 影响子骨骼                                               │
│  - 控制蒙皮网格的变形                                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

骨骼操作 #

javascript
// 获取骨骼
const bone = skeleton.bones[0];

// 获取骨骼名称
console.log(bone.name);

// 获取骨骼变换矩阵
const matrix = bone.getFinalMatrix();

// 设置骨骼旋转
bone.setRotation(BABYLON.Quaternion.RotationAxis(
  BABYLON.Axis.Y,
  Math.PI / 4
));

// 设置骨骼位置
bone.setPosition(new BABYLON.Vector3(0, 1, 0));

动画混合 #

javascript
// 获取动画范围
const idleRange = skeleton.getAnimationRange('idle');
const walkRange = skeleton.getAnimationRange('walk');

// 播放空闲动画
scene.beginAnimation(skeleton, idleRange.from, idleRange.to, true);

// 切换到行走动画
scene.stopAnimation(skeleton);
scene.beginAnimation(skeleton, walkRange.from, walkRange.to, true);

动画叠加 #

javascript
// 使用动画混合器
const animatable = scene.beginAnimation(skeleton, 0, 100, true, 1.0);

// 叠加另一个动画
const animatable2 = scene.beginAnimation(skeleton, 50, 150, true, 0.5);

粒子系统 #

基本粒子系统 #

javascript
// 创建粒子系统
const particleSystem = new BABYLON.ParticleSystem('particles', 2000, scene);

// 设置纹理
particleSystem.particleTexture = new BABYLON.Texture('/textures/flare.png', scene);

// 发射器
particleSystem.emitter = new BABYLON.Vector3(0, 0, 0);

// 发射形状
particleSystem.minEmitBox = new BABYLON.Vector3(-1, 0, -1);
particleSystem.maxEmitBox = new BABYLON.Vector3(1, 0, 1);

// 颜色
particleSystem.color1 = new BABYLON.Color4(1, 0.5, 0, 1);
particleSystem.color2 = new BABYLON.Color4(1, 0.2, 0, 1);
particleSystem.colorDead = new BABYLON.Color4(0, 0, 0, 0);

// 大小
particleSystem.minSize = 0.1;
particleSystem.maxSize = 0.5;

// 生命周期
particleSystem.minLifeTime = 0.5;
particleSystem.maxLifeTime = 2;

// 发射速率
particleSystem.emitRate = 100;

// 方向
particleSystem.direction1 = new BABYLON.Vector3(-1, 1, -1);
particleSystem.direction2 = new BABYLON.Vector3(1, 1, 1);

// 速度
particleSystem.minEmitPower = 1;
particleSystem.maxEmitPower = 3;

// 重力
particleSystem.gravity = new BABYLON.Vector3(0, -9.81, 0);

// 启动
particleSystem.start();

粒子发射器类型 #

text
┌─────────────────────────────────────────────────────────────┐
│                    粒子发射器类型                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Box Emitter(盒子发射器)                                   │
│  - 从盒子区域发射                                           │
│  - minEmitBox / maxEmitBox                                 │
│                                                             │
│  Sphere Emitter(球体发射器)                                │
│  - 从球体表面发射                                           │
│  - createSphereEmitter(radius)                             │
│                                                             │
│  Cone Emitter(锥体发射器)                                  │
│  - 从锥体发射                                               │
│  - createConeEmitter(radius, angle)                        │
│                                                             │
│  Cylinder Emitter(圆柱发射器)                              │
│  - 从圆柱发射                                               │
│  - createCylinderEmitter(radius, height)                   │
│                                                             │
│  Hemisphere Emitter(半球发射器)                            │
│  - 从半球发射                                               │
│  - createHemisphereEmitter(radius)                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

球体发射器 #

javascript
particleSystem.createSphereEmitter(1);

锥体发射器 #

javascript
particleSystem.createConeEmitter(1, Math.PI / 4);

粒子效果预设 #

javascript
// 火焰效果
function createFire(scene, position) {
  const particleSystem = new BABYLON.ParticleSystem('fire', 2000, scene);
  particleSystem.particleTexture = new BABYLON.Texture('/textures/flare.png', scene);
  particleSystem.emitter = position;
  
  particleSystem.minEmitBox = new BABYLON.Vector3(-0.5, 0, -0.5);
  particleSystem.maxEmitBox = new BABYLON.Vector3(0.5, 0, 0.5);
  
  particleSystem.color1 = new BABYLON.Color4(1, 0.5, 0, 1);
  particleSystem.color2 = new BABYLON.Color4(1, 0.2, 0, 1);
  particleSystem.colorDead = new BABYLON.Color4(0.2, 0, 0, 0);
  
  particleSystem.minSize = 0.3;
  particleSystem.maxSize = 0.8;
  
  particleSystem.minLifeTime = 0.3;
  particleSystem.maxLifeTime = 1.5;
  
  particleSystem.emitRate = 150;
  
  particleSystem.direction1 = new BABYLON.Vector3(0, 1, 0);
  particleSystem.direction2 = new BABYLON.Vector3(0, 1, 0);
  
  particleSystem.minEmitPower = 1;
  particleSystem.maxEmitPower = 2;
  
  particleSystem.gravity = new BABYLON.Vector3(0, 5, 0);
  
  particleSystem.start();
  return particleSystem;
}

// 烟雾效果
function createSmoke(scene, position) {
  const particleSystem = new BABYLON.ParticleSystem('smoke', 500, scene);
  particleSystem.particleTexture = new BABYLON.Texture('/textures/smoke.png', scene);
  particleSystem.emitter = position;
  
  particleSystem.minEmitBox = new BABYLON.Vector3(-0.5, 0, -0.5);
  particleSystem.maxEmitBox = new BABYLON.Vector3(0.5, 0, 0.5);
  
  particleSystem.color1 = new BABYLON.Color4(0.5, 0.5, 0.5, 0.8);
  particleSystem.color2 = new BABYLON.Color4(0.3, 0.3, 0.3, 0.6);
  particleSystem.colorDead = new BABYLON.Color4(0, 0, 0, 0);
  
  particleSystem.minSize = 0.5;
  particleSystem.maxSize = 2;
  
  particleSystem.minLifeTime = 2;
  particleSystem.maxLifeTime = 4;
  
  particleSystem.emitRate = 30;
  
  particleSystem.direction1 = new BABYLON.Vector3(-0.1, 1, -0.1);
  particleSystem.direction2 = new BABYLON.Vector3(0.1, 1, 0.1);
  
  particleSystem.minEmitPower = 0.5;
  particleSystem.maxEmitPower = 1;
  
  particleSystem.gravity = new BABYLON.Vector3(0, 0.5, 0);
  
  particleSystem.start();
  return particleSystem;
}

// 爆炸效果
function createExplosion(scene, position) {
  const particleSystem = new BABYLON.ParticleSystem('explosion', 500, scene);
  particleSystem.particleTexture = new BABYLON.Texture('/textures/flare.png', scene);
  particleSystem.emitter = position;
  
  particleSystem.createSphereEmitter(0.1);
  
  particleSystem.color1 = new BABYLON.Color4(1, 0.8, 0, 1);
  particleSystem.color2 = new BABYLON.Color4(1, 0.4, 0, 1);
  particleSystem.colorDead = new BABYLON.Color4(0.2, 0, 0, 0);
  
  particleSystem.minSize = 0.2;
  particleSystem.maxSize = 0.8;
  
  particleSystem.minLifeTime = 0.2;
  particleSystem.maxLifeTime = 0.5;
  
  particleSystem.emitRate = 0;
  particleSystem.manualEmitCount = 500;
  
  particleSystem.direction1 = new BABYLON.Vector3(-1, -1, -1);
  particleSystem.direction2 = new BABYLON.Vector3(1, 1, 1);
  
  particleSystem.minEmitPower = 5;
  particleSystem.maxEmitPower = 10;
  
  particleSystem.gravity = new BABYLON.Vector3(0, -5, 0);
  
  particleSystem.start();
  return particleSystem;
}

粒子控制 #

javascript
// 启动
particleSystem.start();

// 停止
particleSystem.stop();

// 暂停
particleSystem.pause();

// 恢复
particleSystem.restart();

// 设置发射速率
particleSystem.emitRate = 200;

// 设置最大粒子数
particleSystem.maxParticles = 5000;

// 手动发射
particleSystem.manualEmitCount = 100;

程序化动画 #

每帧更新 #

javascript
// 使用渲染循环
let time = 0;
scene.onBeforeRenderObservable.add(() => {
  time += engine.getDeltaTime() * 0.001;
  
  // 正弦波运动
  mesh.position.y = Math.sin(time * 2) * 0.5;
  
  // 持续旋转
  mesh.rotation.y += 0.01;
});

使用 Animatable #

javascript
// 创建自定义动画
const animatable = scene.beginDirectAnimation(
  mesh,
  [animation],
  0,
  60,
  true,
  1.0,
  () => console.log('完成')
);

实战:动画管理器 #

javascript
class AnimationManager {
  constructor(scene) {
    this.scene = scene;
    this.animations = new Map();
    this.animationGroups = new Map();
  }

  createPositionAnimation(name, path, duration = 60, loop = true) {
    const animation = new BABYLON.Animation(
      name,
      'position',
      30,
      BABYLON.Animation.ANIMATIONTYPE_VECTOR3,
      loop ? BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE : BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT
    );
    
    animation.setKeys(path.map((pos, i) => ({
      frame: i * (duration / (path.length - 1)),
      value: pos
    })));
    
    this.animations.set(name, animation);
    return animation;
  }

  createRotationAnimation(name, axis, angle, duration = 60, loop = true) {
    const animation = new BABYLON.Animation(
      name,
      `rotation.${axis}`,
      30,
      BABYLON.Animation.ANIMATIONTYPE_FLOAT,
      loop ? BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE : BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT
    );
    
    animation.setKeys([
      { frame: 0, value: 0 },
      { frame: duration, value: angle }
    ]);
    
    this.animations.set(name, animation);
    return animation;
  }

  playOnMesh(meshName, animationName, loop = true) {
    const mesh = this.scene.getMeshByName(meshName);
    const animation = this.animations.get(animationName);
    
    if (mesh && animation) {
      mesh.animations = [animation];
      return this.scene.beginAnimation(mesh, 0, 60, loop);
    }
  }

  createGroup(name, animations) {
    const group = new BABYLON.AnimationGroup(name, this.scene);
    
    animations.forEach(({ animationName, targetName }) => {
      const animation = this.animations.get(animationName);
      const target = this.scene.getMeshByName(targetName);
      
      if (animation && target) {
        group.addTargetedAnimation(animation, target);
      }
    });
    
    this.animationGroups.set(name, group);
    return group;
  }

  playGroup(name, loop = true) {
    const group = this.animationGroups.get(name);
    if (group) {
      group.play(loop);
    }
  }

  stopGroup(name) {
    const group = this.animationGroups.get(name);
    if (group) {
      group.stop();
    }
  }

  withEasing(animationName, easingType, mode = 'easeInOut') {
    const animation = this.animations.get(animationName);
    if (animation) {
      const easing = new BABYLON[easingType]();
      easing.setEasingMode(BABYLON.EasingFunction[`EASINGMODE_${mode.toUpperCase()}`]);
      animation.setEasingFunction(easing);
    }
    return this;
  }
}

// 使用
const animManager = new AnimationManager(scene);

// 创建路径动画
animManager.createPositionAnimation('pathAnim', [
  new BABYLON.Vector3(0, 0, 0),
  new BABYLON.Vector3(2, 0, 0),
  new BABYLON.Vector3(2, 2, 0),
  new BABYLON.Vector3(0, 2, 0),
  new BABYLON.Vector3(0, 0, 0)
], 120);

// 添加缓动
animManager.withEasing('pathAnim', 'QuadraticEase', 'easeInOut');

// 播放
animManager.playOnMesh('box', 'pathAnim');

下一步 #

现在你已经掌握了动画系统,接下来学习 物理引擎,了解如何为场景添加物理效果!

最后更新:2026-03-29