Babylon.js-9 动画

参考链接:https://doc.babylonjs.com/babylon101/animations

基本动画

Babylon.js中,动画是一个类BABYLON.Animation。我们可以使用如下代码构建一个动画对象:

1
var animation = new BABYLON.Animation(name,targetProperty,framePerSecond,dataType,loopMode,enableBlending)

其中:

  • name string 动画的名称
  • targetProperty string 动画的目标属性,例如scaling.x表示缩放x轴
  • framePerSecond number 动画播放的速度(帧率)
  • dataType number[enum] 动画的数据类型,有如下几个选择:
    • BABYLON.Animation.ANIMATIONTYPE_FLOAT 浮点型
    • BABYLON.Animation.ANIMATIONTYPE_VECTOR2 BABYLON.Vector2类型
    • BABYLON.Animation.ANIMATIONTYPE_VECTOR3 BABYLON.Vector3类型
    • BABYLON.Animation.ANIMATIONTYPE_QUATERNION BABYLON.Quaternion类型
    • BABYLON.Animation.ANIMATIONTYPE_MATRIX BABYLON.Matrix类型
    • BABYLON.Animation.ANIMATIONTYPE_COLOR3 BABYLON.Color3类型
      默认情况下矩阵不插值,我们可以设置动画的AllowMatricesInterpolationtrue来启用矩阵插值。在这种情况下,我们可以使用Matrix.LerpMatrix.DecomposeLerp作为插值工具。可以使用动画的AllowMatrixDecomposeForInterpolation属性控制插值工具。
  • loopMode number[enum] (optional) 动画的循环模式,有如下几个选择:
    • BABYLON.Animation.ANIMATIONLOOPMODE_RELATIVE 使用之前的值并增加它
    • BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE 从初始值重新开始
    • BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT 在结束位置保持

接下来为动画设置关键帧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// An array with all animation keys
var keys = [];

//At the animation key 0, the value of scaling is "1"
keys.push({
frame: 0,
value: 1
});

//At the animation key 20, the value of scaling is "0.2"
keys.push({
frame: 20,
value: 0.2
});

//At the animation key 100, the value of scaling is "1"
keys.push({
frame: 100,
value: 1
});

对于向量(Vector2/3)或四元数(Quaternion),还可以设置inTangentoutTangent来进行样条插值(spline interpolation)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var keys = []; 

keys.push({
frame: 0,
value: BABYLON.Vector3.Zero(),
outTangent: new BABYLON.Vector3(1, 0, 0)
});

keys.push({
frame: 20,
inTangent: new BABYLON.Vector3(1, 0, 0),
value: new BABYLON.Vector3(1, 1, 1),
outTangent: new BABYLON.Vector3(-1, 0, 0)
});

keys.push({
frame: 100,
inTangent: new BABYLON.Vector3(-1, 0, 0),
value: BABYLON.Vector3.Zero()
});

接下来有两个重要步骤:

1
2
3
4
//为动画设置关键帧
animation.setKeys(keys);
//设置动画到target上
target.animations=[animation];

最后,使用scene的beginAnimation方法来播放动画:

1
scene.beginAnimation(target,from,to,loop,speedRatio,onAnimationEnd,animatable,stopCurrent);

其中:

  • target any 启用动画的目标
  • from number 动画的起始帧数
  • to number 动画的结束帧数
  • loop boolean (optional) 如果为真,动画将循环
  • speedRatio number (optional) 动画的播放速度比率(默认为1
  • onAnimationEnd () => void (optional) 动画结束的回调
  • animatable Animatable (optional) 一个可选的Animatable
  • stopCurrent boolean (optional) 是否停止当前正在播放的动画(默认为true

这个函数返回一个BABYLON.Animatable对象,可以用来访问单个动画。(如果获取实例,使用getAnimationByTargetProperty方法)

我们还可以对这个BABYLON.Animatable对象调用

  • pause ()=>void 暂停
  • restart ()=>void 重启
  • stop ()=>void 停止
  • reset ()=>void 重置

我们可以使用scene的getAnimatableByTarget获取到目标的Animatable

我们做个测试,让页面上的Banner绕y轴旋转,使用如下代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
window.addEventListener('load',()=>{
const scene = document.sceneBanner;
const mesh = scene.meshes[0];
const animation = new BABYLON.Animation("animation","rotation.y",30,BABYLON.Animation.ANIMATIONTYPE_FLOAT,BABYLON.Animation.ANIMATIONLOOP_CYCLE);
const keys=[
{
frame:0,
value:0,
},{
frame:240,
value:Math.PI*2
}
];
animation.setKeys(keys);
mesh.animations=[animation];
scene.beginAnimation(mesh,0,240,true);
})

看,它转起来了。

Promises

Babylon.js的3.3版本之后,可以使用Promise来等待动画结束,使用动画的waitAsync方法。

1
2
3
4
5
var anim = scene.beginAnimation(box1, 0, 100, false);

console.log("before");
await anim.waitAsync();
console.log("after");

Babylon示例(请开启控制台查看)

控制动画

每个动画都有一个属性currentFrame表示当前帧。
对于关键帧动画,我们还可以定义插值函数,例如:

1
2
3
4
5
6
7
8
9
10
11
BABYLON.Animation.prototype.floatInterpolateFunction = function (startValue, endValue, gradient) {
return startValue + (endValue - startValue) * gradient;
};

BABYLON.Animation.prototype.quaternionInterpolateFunction = function (startValue, endValue, gradient) {
return BABYLON.Quaternion.Slerp(startValue, endValue, gradient);
};

BABYLON.Animation.prototype.vector3InterpolateFunction = function (startValue, endValue, gradient) {
return BABYLON.Vector3.Lerp(startValue, endValue, gradient);
};

能够使用的插值函数有以下几个:

  • floatInterpolateFunction
  • quaternionInterpolateFunction
  • quaternionInterpolateFunctionWithTangents
  • vector3InterpolateFunction
  • vector3InterpolateFunctionWithTangents
  • vector2InterpolateFunction
  • vector2InterpolateFunctionWithTangents
  • sizeInterpolateFunction
  • color3InterpolateFunction
  • matrixInterpolateFunction

帮助函数

可以使用如下函数快速生成一个动画:

1
Animation.CreateAndStartAnimation = function(name, mesh, targetProperty, framePerSecond, totalFrame, from, to, loopMode);

要注意的是:

  • 该动画会根据总帧数以及起始结束的值进行插值
  • 该动画只在AbstractMesh上作用
  • 该动画自创建之时立即执行

例如:

1
BABYLON.Animation.CreateAndStartAnimation('boxscale', box1, 'scaling.x', 30, 120, 1.0, 1.5);

这使得一个立方体的x轴以每秒钟30帧的速度在4秒钟之内缩放倍数由1变为1.5。

动画混合

设置动画的enableBlendingtrue即可开启混合动画,同时设置blendingSpeed设置动画的混合速度。

动画权重

从Babylon.js的3.2版本起,可以使用动画的权重weight。这意味着可以使多个动画在同一个目标上同时进行。Babylon.js会根据权重来对动画进行混合。

我们使用scene的beginWeightedAnimation方法来启用权重动画,它也返回一个Animatable对象,但是其weight属性被设置。

1
2
3
4
5
6
// Will have a weight of 1.0
var idleAnim = scene.beginWeightedAnimation(skeleton, 0, 89, 1.0, true);
// Will have a weight of 0
var walkAnim = scene.beginWeightedAnimation(skeleton, 90, 124, 0, true);
// Will have a weight of 0
var runAnim = scene.beginWeightedAnimation(skeleton, 125, 146, 0, true);

它的参数如下:

  • target any 启用动画的目标
  • from number 动画的起始帧数
  • to number 动画的结束帧数
  • weight number (optional) 权重(默认为1
  • loop boolean (optional) 如果为真,动画将循环
  • speedRatio number (optional) 动画的播放速度比率(默认为1
  • onAnimationEnd () => void (optional) 动画结束的回调
  • animatable Animatable (optional) 一个可选的Animatable

我们可以设置Animatableweight属性为-1使动画不受权重控制,由于weight的范围为[0-1],所以设置为0会导致动画暂停。

如果动画的帧数不一致,且需要同步的情况下,使用AnimatablesyncWith方法来进行同步。

1
2
// Synchronize animations
idleAnim.syncWith(runAnim);

如果想要删除同步,使用以下代码:

1
animatable.syncWith(null);

重载属性

当一个mesh中包含了很多动画animation或者一个骨架skeleton时,可以设置一个动画属性重载animationPropertiesOverride来使改变其子动画的属性:

1
2
3
4
5
6
7
//首先创建一个BABYLON.AnimationPropertiesOverride对象
var overrides = new BABYLON.AnimationPropertiesOverride();
//设置属性
overrides.enableBlending = true;
overrides.blendingSpeed = 0.1;
//应用到对象
skeleton.animationPropertiesOverride = overrides;

可以变更的属性有:

  • enableBlending
  • blendingSpeed
  • loopMode
注意:当没有设置的时候将使用场景sceneanimationPropertiesOverride属性

缓动函数

我们可以使用以下Babylon.js提供的缓动函数:

  • BABYLON.CircleEase()
  • BABYLON.BackEase(amplitude)
  • BABYLON.BounceEase(bounces, bounciness)
  • BABYLON.CubicEase()
  • BABYLON.ElasticEase(oscillations, springiness)
  • BABYLON.ExponentialEase(exponent)
  • BABYLON.PowerEase(power)
  • BABYLON.QuadraticEase()
  • BABYLON.QuarticEase()
  • BABYLON.QuinticEase()
  • BABYLON.SineEase()
  • BABYLON.BezierCurveEase(x1,y1,x2,y2) cubic-bezier.com

首先创建一个缓动函数,接下来设置其模式,使用如下代码:

1
easingFunction.setEasingMode(mode)

其中mode可以有如下选择:

  • BABYLON.EasingFunction.EASINGMODE_EASEIN : 根据插值函数的数学公式进行插值
  • BABYLON.EasingFunction.EASINGMODE_EASEOUT : 根据100%插值减去插值函数的数学公式的输出进行插值(与BABYLON.EasingFunction.EASINGMODE_EASEIN 相反)
  • BABYLON.EasingFunction.EASINGMODE_EASEINOUT : 前一半(50%)EaseIn,后一半(50%)EaseOut

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//Create a Vector3 animation at 30 FPS
var animationTorus = new BABYLON.Animation("torusEasingAnimation", "position", 30, BABYLON.Animation.ANIMATIONTYPE_VECTOR3, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);

// the torus destination position
var nextPos = torus.position.add(new BABYLON.Vector3(-80, 0, 0));

// Animation keys
var keysTorus = [];
keysTorus.push({ frame: 0, value: torus.position });
keysTorus.push({ frame: 120, value: nextPos });
animationTorus.setKeys(keysTorus);

// Creating an easing function
var easingFunction = new BABYLON.CircleEase();

// For each easing function, you can choose between EASEIN (default), EASEOUT, EASEINOUT
easingFunction.setEasingMode(BABYLON.EasingFunction.EASINGMODE_EASEINOUT);

// Adding the easing function to the animation
animationTorus.setEasingFunction(easingFunction);

// Adding animation to my torus animations collection
torus.animations.push(animationTorus);

//Finally, launch animations on torus, from key 0 to key 120 with loop activated
scene.beginAnimation(torus, 0, 120, true);

我们还可以继承BABYLON.EaseFunction这个类,然后添加easeInCore方法实现自定义的缓动函数。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
var FunnyEase = (function (_super) {
__extends(FunnyEase, _super);
function FunnyEase() {
_super.apply(this, arguments);
;}
FunnyEase.prototype.easeInCore = function (gradient) {
// Here is the core method you should change to make your own Easing Function
// Gradient is the percent of value change
return Math.pow(Math.pow(gradient, 4), gradient);

};
return FunnyEase;
})(BABYLON.EasingFunction);

复杂动画

可以使用场景sceneregisterBeforeRender方法,来创建一个每一帧渲染之前的回调。

1
2
3
scene.registerBeforeRender(()=>{
console.log(engine.getDeltaTime())
})

添加动画事件

Babylon.js的2.3版本后,我们可以使用以下代码创建一个动画事件:

1
const event = new BABYLON.AnimationEvent(frame,callback,once)

其中:

  • frame number 播放到的帧数
  • callback ()=>void 回调函数
  • once boolean 是否之调用一次(默认为false)

确定性锁步 (帧同步)

为了使动画以及物理引擎,游戏逻辑,或者多平台多用户客户端同步,可以开启引擎的确定性同步。

1
2
3
4
const engine = new BABYLON.Engine(renderCanvas,true,{
deterministicLockstep:true,
lockstepMaxSteps:4
})

这样场景将渲染离散物理引擎和动画帧,例如使用Cannon物理引擎:

1
2
3
var physEngine = new BABYLON.CannonJSPlugin(false);//_useDeltaForWorldStep
newScene.enablePhysics(this.gravity, physEngine);
physEngine.setTimeStep(1/60);

这将使得物理引擎以60Hz(0.0166667s)的速度来进行物理计算。

注意:在使用CannonJSPlugin时,为禁止CannonJS内部累积器使能,需要设置_useDeltaForWorldStepfalse

使用以下代码进行逻辑代码与动画帧的同步处理:

1
2
3
4
5
6
7
newScene.onBeforeStepObservable.add(function(theScene){
console.log("Performing game logic, BEFORE animations and physics for stepId: "+theScene.getStepId());
});

newScene.onAfterStepObservable.add(function(theScene){
console.log("Performing game logic, AFTER animations and physics for stepId: "+theScene.getStepId());
});