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