在场景中有两种主要的动画制作方法。第一种方法是定义关键帧的集合,并在每个关键帧处定义对象的情况。第二种方法用于更复杂的动画,即在运行时更改动画代码。
- ** Basic animation 基础动画**
动画基于名为animation的对象,动画由各种属性和一组键定义,每个键代表给定时间内动画的值。
首先创建一个带有box的基础场景:
function createScene() {
//Here... your basic scene as before: [scene, light, camera]
//Create a box
var box1 = BABYLON.Mesh.CreateBox("Box1", 10.0, scene);
box1.position.x = -20;
为了使用动画移动这个box对象,我们需要先创建一个Animation对象:
var animationBox = new BABYLON.Animation("myAnimation", "scaling.x", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
其中各个参数的含义如下:
- 对象名称
- 动画所控制的属性,可以是格网的任意一个属性,取决于我们想改变的对象属性,例如此处改变的是scaling.x属性
- 帧率,渲染可能达到的最大FPS值
- 变化的类型,决定输入的值的类型,如浮点、向量、四元组等,具体如下:
-
BABYLON.Animation.ANIMATIONTYPE_FLOAT
-
BABYLON.Animation.ANIMATIONTYPE_VECTOR2
-
BABYLON.Animation.ANIMATIONTYPE_VECTOR3
-
BABYLON.Animation.ANIMATIONTYPE_QUATERNION
-
BABYLON.Animation.ANIMATIONTYPE_MATRIX
-
BABYLON.Animation.ANIMATIONTYPE_COLOR3
-
- 决定动画执行完毕之后的行为,如继续、重新开始、停止等,具体有:
- BABYLON.Animation.ANIMATIONLOOPMODE_RELATIVE 递增
- BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE 重启循环
- BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT 保持结束时的状态
动画对象创建完成后,我们需要说明值如何修改,下面的例子中,我们将实现缩放我们的Box1,但不是线性的:当它变大时,它必须更快,当它变薄时,它必须更慢。
// An array with all animation keys 存贮关键帧的数组
var keys = [];
//At the animation key 0, the value of scaling is "1" 关键帧0,缩放比例为1
keys.push({
frame: 0,
value: 1
});
//At the animation key 20, the value of scaling is "0.2" 关键帧1,缩放比例为0.2
keys.push({
frame: 20,
value: 0.2
});
//At the animation key 100, the value of scaling is "1" 关键帧2,缩放比例为1
keys.push({
frame: 100,
value: 1
});
对于Vector2, Vector3, Quaterning 类型,可以使用outTangent, Tangent属性去使用样条插值代替线性插值,实现非线性的变化
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()
});
接下来是重要的两步:
把存储关键帧的数组与动画对象绑定到一起:
animationBox.setKeys(keys);
把动画对象连接到格网对象mesh上:
box1.animations = [];
box1.animations.push(animationBox);
最后,在一行代码中启动动画,在任何时候:
scene.beginAnimation(box1, 0, 100, true);
ps:也可以反过来运行你的动画,通过交换帧和帧:
scene.beginAnimation(box1, 100, 0, true);
scene.beginAnimation() 函数的参数含义:
Name | Type | Description | Optional |
---|---|---|---|
target | any | The target | No |
from | number | The fps starting frame | No |
to | number | The fps ending frame | No |
loop | boolean | If true, the animation will loop (dependent upon BABYLON.Animation.ANIMATIONLOOPMODE) | Yes |
speedRatio | number | default : 1. The speed ratio of this animation | Yes |
onAnimationEnd | () => void | The function triggered on the end of the animation, even if the animation is manually stopped (also dependent upon ANIMATIONLOOPMODE) | Yes |
animatable | Animatable | An optional specific animation | Yes |
stopCurrent | boolean | Should we stop the current existing animations if any? Default is yes | Yes |
这个函数返回的是一个BABYLON.Animatable对象,可以利用它来获取单独的动画,对象支持的函数有:
-
pause()
-
restart()
-
stop()
-
reset()
可以通过将动画设置为引用(变量)来控制前面的示例动画,如:
var newAnimation = scene.beginAnimation(box1, 0, 100, true);
newAnimation.pause();//暂停动画
这些控制函数可以运用到Animatable's ._animations array中的任意一个动画之中,通过scene.getAnimatableByTarget()函数也可以得到相应的动画对象
至此,一个简单的动画已经实现
- Animations and promises
从Babylon.js v3.3开始,可以利用promises对象使得程序异步执行,例如:
setTimeout(async () => {
var anim = scene.beginAnimation(box1, 0, 100, false);
console.log("before");
await anim.waitAsync();
console.log("after");
});
执行效果为:动画执行完成后才输出"after"
- ** Controlling animations 动画控制**
每一个动画都有一个叫做 currentFrame
的属性指向当前的关键帧,对于高级关键帧动画,您还可以定义用于在关键帧之间插入(转换)的函数,默认的函数有:
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);
};
- Helper function 帮助函数
可以利用已有的拓展函数快速创建一个动画,模板如下:
Animation.CreateAndStartAnimation = function(name, mesh, targetProperty, framePerSecond, totalFrame, from, to, loopMode);
- Animation blending 动画融合
可以使用enableblend = true,以启用混合模式,这个混合动画将从当前对象的状态进行,这对于用户控制的行走或对输入设备的值更改作出响应,详细例子在: https://www.babylonjs-playground.com/#IQN716#9 - Animation weights 动画权重
从Babylon.js 3.2开始,可以使用特定的权重开始动画。这意味着可以使用这个API在同一个目标上同时调用多个动画。最后的值将是所有基于权重值加权的动画的组合。
要使用权重开始动画,可以使用 new scene.beginWeightedAnimation API:
// 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);
权重函数的参数含义如下:
Name | Type | Description | Optional |
---|---|---|---|
target | any | The target | No |
from | number | The fps starting frame | No |
to | number | The fps ending frame | No |
weight | number | Weight of this animation. 1.0 by default | Yes |
loop | boolean | If true, the animation will loop (dependent upon BABYLON.Animation.ANIMATIONLOOPMODE) | Yes |
speedRatio | number | default : 1. The speed ratio of this animation | Yes |
onAnimationEnd | () => void | The function triggered on the end of the animation, even if the animation is manually stopped (also dependent upon ANIMATIONLOOPMODE) | Yes |
animatable | Animatable | An optional specific animation | Yes |
函数的返回值是一个animatable对象,可以在任何时间设置任意动画的权值,以切换到加权模式,这个值必须在0和1之间,同样的,可以将它设置为-1来关闭权重模式。如果将权重设置为0,动画将被认为是暂停的。
var idleAnim = scene.beginWeightedAnimation(skeleton, 0, 89, 1.0, true);//权重为1
var runAnim = scene.beginWeightedAnimation(skeleton, 125, 146, 0, true);//权重为0
idleAnim.weight = 0.5;//将权重设置为0.5
runAnim.weight = 0.5
如果动画大小不一样(关键帧之间的时间间隔相同),需要用以下代码开启动画同步:
// Synchronize animations
idleAnim.syncWith(runAnim);
- Overriding properties 重写属性
当一个格网由多个动画和骨架组成时,可以利用animationPropertiesOverride 函数去修改子动画中相同的那部分属性,这将会覆盖原有的属性
var overrides = new BABYLON.AnimationPropertiesOverride();
overrides.enableBlending = true;
overrides.blendingSpeed = 0.1;
skeleton.animationPropertiesOverride = overrides;
可以通过这种方式修改的属性有:enableBlending、blendingSpeed、loopMode
- Easing functions 缓动函数
缓动函数允许对动画应用自定义数学公式。例如,当我们希望一个对象实际地弹跳或表现得像在弹簧上一样时,可以使用关键帧,甚至通过动画从/到/来近似这些效果,但这将需要大量的工作,动画将不如使用数学公式准确
BABYLON中可用的缓动函数如下:
-
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()
可以使用EasingMode属性来更改缓动函数的行为方式,即更改动画插值的方式
-
BABYLON.EasingFunction.EASINGMODE_EASEIN
: 插值遵循与缓动函数相关的数学公式 -
BABYLON.EasingFunction.EASINGMODE_EASEOUT
: 插值遵循100%插值减去与缓动函数相关的公式的输出 -
BABYLON.EasingFunction.EASINGMODE_EASEINOUT
: 插值在动画的前半部分使用EaseIn,在动画的后半部分使用EaseOut
下面是一个利用缓动函数CircleEase绘制动画的例子:
//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);
- Complex animation 复杂动画
复杂的动画允许在动画的每一帧(每一秒)选择一切属性,在运行时计算的代码必须位于下面这个函数中:
scene.registerBeforeRender(function () {
//Your code here
});
这个函数对于像游戏这样的复杂动画非常有用,在游戏中角色必须根据许多参数移动。
- Attach events to animations 动画附加事件
从Babylon.js 2.3版本之后,可以将动画事件附加到动画的特定帧上,事件是将在给定帧中调用的函数
// 3 parameters to create an event: 三个参数的含义:
// - The frame at which the event will be triggered 事件被触发的时间
// - The action to execute 被执行的函数
// - A boolean if the event should execute only once (false by default) 函数是否只执行一次
var event1 = new BABYLON.AnimationEvent(50, function() { console.log("Yeah!"); }, true);
// Attach your event to your animation 事件绑定到动画对象上
animation.addEvent(event1);
- Deterministic lockstep 确定性的同步方法
有时,重要的是要确保动画、物理和游戏逻辑代码是同步的,并通过帧速率方差解耦。如果能够在给定相同初始条件和输入的情况下重播场景的演化过程,或者在多用户环境中最小化多个客户机上的差异,这可能非常有用。其原理是将状态执行时间量子化,用离散时间步长以固定频率更新状态,并保留一个累加器,以便将超过的时间延续到下一帧更新。
为了实现这一点,巴比伦引擎需要创建通过以下两个选项:
this.engine = new BABYLON.Engine(theCanvas, true, {
deterministicLockstep: true,
lockstepMaxSteps: 4
});
通过这种方式,场景将按照物理引擎中设置的时间步长离散块来呈现量化的物理和动画步骤
var physEngine = new BABYLON.CannonJSPlugin(false);
newScene.enablePhysics(this.gravity, physEngine);
physEngine.setTimeStep(1/60);
使用上面的代码,引擎将以60Hz (0.01666667s)的速度运行离散的步骤,如果帧呈现时间较晚,它将在呈现帧之前,尝试计算最多4个步骤(lockstepMaxSteps)来恢复最终累积的延迟。
参考资料: https://doc.babylonjs.com/babylon101/animations