动画使您的游戏或应用程序变得生动起来。在 BABYLON.js 中有两种主要的方法或制作动画。您可以在渲染周期期间更改场景中对象的属性,我们将在描述Animation方法后查看该属性。
动画导论
动画的实现必须考虑到所需的动作、时间、产生所需流动性所需的帧数和序列中的关键点。帮助理解如何Babylon.js 处理动画动画是如何实现的。
动画是由一系列图像、帧产生的,这些图像一个接一个地显示。这一系列的帧可以是单独的图画,也可以是定格动画、逐帧轻微移动的模型照片
在设计阶段,创作者需要考虑一个序列需要多长时间以及它需要有多平滑。运动越流畅,需要的帧数就越多。一旦知道帧数,就可以找到每秒的动画帧数。此外,了解对象的开始位置和结束位置以及需要多少帧将决定对象每帧的运动。
在Babylon.js 中,就像在定格动画中一样,必须逐个移动单个对象。虽然我们可能会在 Babylon.js 中将整个成品作为动画来讨论,但动画也是一个特定的对象,它详细说明了可以应用于任何网格、相机或灯光的转换、计时和循环。向此数据添加关键帧处的值,Babylon.js 从中计算要为中间帧发生的转换。
动画文档中使用的术语
以下术语将具有 How_To 中关于动画的给定含义。
-
Performer可以动画的项目,例如可以是网格、灯或相机。
-
Frame - 动画帧而不是场景的渲染帧。
-
Animation - 类似于戏剧或电影剧本,但仅适用于表演者的一个属性。它包括
- 要更改的属性,例如位置、强度或旋转
- 以每秒帧数为单位的属性变化率,
- 被改变的属性的类型,例如向量、浮点数或矩阵,
- 循环条件,
- 关键帧处属性的关键值。
-
Scripted Performer- 表演者加上表演者要承担的所有动画。
-
Performance- 脚本执行者和执行者按照脚本执行的操作。在Babylon.js 中,这是可动画对象。
-
Clip - 表演的可见结果。实际上有两种类型的剪辑: 游戏剪辑和电影剪辑。在影片剪辑中,用户无法控制摄像机,并且根据剪辑创建者设置的摄像机动画查看剪辑。在游戏剪辑中,用户可以根据场景中使用的相机类型来移动相机。除非它可能引起任何混淆,否则在编写动画时将在整个文档中使用术语剪辑。
-
Cartoon -以定时间隔播放的一系列剪辑。
设计剪辑
第一步是决定您想在剪辑中看到什么,这就是要表现的内容。这给表演者和它的动画。
在这个游戏中剪辑一个盒子,表演者,每秒在两个地方之间滑动一次。盒子将能够从任何角度观看。
第一阶段的设计是勾勒出关键时间点需要的东西,有点像gif动画设计。
一秒钟后,盒子应该在它的新位置,一秒钟后在它的起始位置。然后不断重复这个序列。
在 Babylon.js 中,动画沿着 x 轴改变盒子的位置,它的 x 位置是一个浮点数,并且
该动画应该循环。在代码中,沿 x 方向滑动项目的动画变为
const frameRate = 10;
const xSlide = new BABYLON.Animation("xSlide", "position.x", frameRate, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
关键帧位于 0、1 和 2 秒。要找到 t 秒后的帧数,请将时间乘以帧速率,即 tx frameRate。
在这种情况下,关键帧的帧数为 0、1 x frameRate 和 2 x frame Rate。
在 x = 2 处启动框并将其滑动到 x = -2,分别将框在 0、1 和 2 秒后的 x 位置值分别设为 2、-2 和 2。
关键帧被设置为具有帧(编号)和值属性的 JavaScript 对象数组,并添加到动画中,如:
const keyFrames = [];
keyFrames.push({
frame: 0,
value: 2,
});
keyFrames.push({
frame: frameRate,
value: -2,
});
keyFrames.push({
frame: 2 * frameRate,
value: 2,
});
xSlide.setKeys(keyFrames);
动画现在已经完成,可以应用到盒子上并播放:
box.animations.push(xSlide);
scene.beginAnimation(box, 0, 2 * frameRate, true);
反转动画
有趣的提示,beginAnimation 方法的第二个和第三个参数是关键帧列表中的起始帧和结束帧。如果您反转这两个值,动画将反转播放!
scene.beginAnimation(box, endFrame, startFrame, false);
动画API:
创建动画:
name - string 动画名称
property - string, 动画将应用到的对象的属性。例如 Vector3 属性(例如 position)或浮点数属性(例如 position.x)
frames per second - number, 每秒动画帧数(独立于场景每秒渲染帧数)
property type - number, 属性参数的属性类型。这可以使用以下常量设置:
BABYLON.Animation.ANIMATIONTYPE_COLOR3
BABYLON.Animation.ANIMATIONTYPE_FLOAT
BABYLON.Animation.ANIMATIONTYPE_MATRIX
BABYLON.Animation.ANIMATIONTYPE_QUATERNION
BABYLON.Animation.ANIMATIONTYPE_VECTOR2
BABYLON.Animation.ANIMATIONTYPE_VECTOR3
loop mode - number 这可以使用以下参数设置
BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE - 从初始值重新开始动画
BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT - 在最终值处暂停动画
BABYLON.Animation.ANIMATIONLOOPMODE_RELATIVE - 使用键值梯度重复动画递增。以这种方使用键值梯度重复动画递增。以这种方式,例如,可以循环显示角色在步行运动中的腿的_剪辑*,以显示角色在场景中前进。
const myAnim = new BABYLON.Animation(name, property, frames per second, property type, loop mode)
设置关键帧:
myKeys :[] 这是一个数组,对象的myKeys。每个对象具有两个属性
frame - 帧数
值- 正在更改的属性
构建完成后,将其添加到动画中
myAnim.setKeys(myKeys);
开始动画:
//为了运行动画,它被推送到网格的动画数组属性上。
//并从这些必需的参数开始
mesh.animations.push(myAnim);
//target -动画的Babylon.js 对象 如: mesh
//from - number, 开始动画的帧
//to - number, 动画结束的帧
//loop , boor, 默认false
scene.beginAnimation(target, from, to);
你也可以将多个动画应用到同一个目标物体上:
//target - BabylonJS Object, 需要播动画的Babylon.js 对象
//animations - array, 应用于目标的所有动画的数组
//from - number, 开始动画的帧
//to - number, 动画结束的帧
loop - boolean, 可选,默认false,当true重复你的动画
scene.beginDirectAnimation(target, animations, from, to, loop)
动画状态控制器:
启动动画的两种方法都返回一个Animatable对象,它支持如下方法:
- pause()
- restart()
- stop()
- reset()
在播放时,可以获得这个动画对象:
const myAnimatable = myscene.beginAnimation(target, from, to, true)
myAnimatable.pause()
myAnimatable.restart()
myAnimatable.stop()
myAnimatable.reset()
排序动画
组合多个剪辑以形成卡通片的一种直接方法是为每个动画剪辑指定开始时间。
设计
概述
相机显示一栋带门的建筑物。摄像机靠近门并停止。门打开,摄像机进入房间。当摄像机进入房间时,房间里的灯亮了。门关上,摄像机扫过房间。
由于目的是展示影片剪辑是如何制作的,房间和门将简单地用没有纹理的网格制作。
表演者
相机 - 通用
门 - 铰链在右手边,向内打开
聚光灯 - 用球体显示位置
序列时间表
对于每个表演者,您可以为每个定时事件创建一个带有关键点的动画。
动画
对于相机
移动,相机会改变相机的位置(vector3)。扫描相机是围绕 y 轴的旋转(float)。
由于动画只能更改一个属性,因此相机需要两个动画。
移动相机,关键点将在时间 0 相机将开始远离建筑物并向下移动,直到它在时间 3 秒时刚好在门外。
当门打开时,摄像机将保持其位置 2 秒,然后在 5 秒时以远离门的角度向前移动到房间内,在 8 秒时停止。
9 秒内不会旋转摄像头,然后摄像头将需要 14 秒的时间旋转 180 度以面向门。
相机的关键值将是它在第 0、3、5 和 8 秒帧的位置以及它在 0、9 和 14 秒的旋转。
相机动画帧如下:
//for camera move forward
var movein = new BABYLON.Animation(
"movein",
"position",
frameRate,
BABYLON.Animation.ANIMATIONTYPE_VECTOR3,
BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT
);
var movein_keys = [];
movein_keys.push({
frame: 0,
value: new BABYLON.Vector3(0, 5, -30)
});
movein_keys.push({
frame: 3 * frameRate,
value: new BABYLON.Vector3(0, 2, -10)
});
movein_keys.push({
frame: 5 * frameRate,
value: new BABYLON.Vector3(0, 2, -10)
});
movein_keys.push({
frame: 8 * frameRate,
value: new BABYLON.Vector3(-2, 2, 3)
});
movein.setKeys(movein_keys);
//for camera to sweep round
var rotate = new BABYLON.Animation(
"rotate",
"rotation.y",
frameRate,
BABYLON.Animation.ANIMATIONTYPE_FLOAT,
BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT
);
var rotate_keys = [];
rotate_keys.push({
frame: 0,
value: 0
});
rotate_keys.push({
frame: 9 * frameRate,
value: 0
});
rotate_keys.push({
frame: 14 * frameRate,
value: Math.PI
});
rotate.setKeys(rotate_keys);
对于门
门将围绕 y 轴的铰链扫过一个浮点旋转。打开和关闭旋转各需要 2 秒。
关键点将是 0、3、5、13 和 15 秒的时间。
扫描的关键值将是它在帧处围绕 y 轴的旋转。
//for door to open and close
var sweep = new BABYLON.Animation(
"sweep",
"rotation.y",
frameRate,
BABYLON.Animation.ANIMATIONTYPE_FLOAT,
BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT
);
var sweep_keys = [];
sweep_keys.push({
frame: 0,
value: 0
});
sweep_keys.push({
frame: 3 * frameRate,
value: 0
});
sweep_keys.push({
frame: 5 * frameRate,
value: Math.PI / 3
});
sweep_keys.push({
frame: 13 * frameRate,
value: Math.PI / 3
});
sweep_keys.push({
frame: 15 * frameRate,
value: 0
});
sweep.setKeys(sweep_keys);
对于灯光 灯光的强度会有所不同(浮动)。这些将是一组聚光灯。
灯的关键点保持关闭 7 秒,在 10 秒时达到最大强度,直到 14 秒熄灭。
//for light to brighten and dim
var lightDimmer = new BABYLON.Animation(
"dimmer",
"intensity",
frameRate,
BABYLON.Animation.ANIMATIONTYPE_FLOAT,
BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT
);
var light_keys = [];
light_keys.push({
frame: 0,
value: 0
});
light_keys.push({
frame: 7 * frameRate,
value: 0
});
light_keys.push({
frame: 10 * frameRate,
value: 1
});
light_keys.push({
frame: 14 * frameRate,
value: 1
});
light_keys.push({
frame: 15 * frameRate,
value: 0
});
lightDimmer.setKeys(light_keys);
组合动画片
现在只需同时运行所有剪辑:
scene.beginDirectAnimation(camera, [movein, rotate], 0, 25 * frameRate, false);
scene.beginDirectAnimation(hinge, [sweep], 0, 25 * frameRate, false);
scene.beginDirectAnimation(
spotLights[0],
[lightDimmer],
0,
25 * frameRate,
false
);
scene.beginDirectAnimation(
spotLights[1],
[lightDimmer.clone()],
0,
25 * frameRate,
false
);
这样一整套相机,门和灯光的组合动画就会按照既定的顺序执行。
分组动画
AnimationGroup
允许您将动画和网格链接在一起并作为一个组播放、暂停和停止它们。
组合:
按照创建动画的教程设置一个或多个动画。
例如具有创建animation1,animation2和animation3和也网格mesh1,mesh2,mesh3和mesh4可以形成以下基团的动画。
var animationGroup1 = new BABYLON.AnimationGroup("Group1");
var animationGroup2 = new BABYLON.AnimationGroup("Group2");
然后使用addTargetedAnimation方法将动画与网格连接起来并将它们添加到组中
animationGroup1.addTargetedAnimation(animation1, mesh1);
animationGroup1.addTargetedAnimation(animation3, mesh1);
animationGroup1.addTargetedAnimation(animation2, mesh2);
animationGroup2.addTargetedAnimation(animation2, mesh3);
animationGroup2.addTargetedAnimation(animation1, mesh4);
animationGroup2.addTargetedAnimation(animation2, mesh4);
animationGroup2.addTargetedAnimation(animation3, mesh4);
由于动画可能是使用不同的时间线创建的,因此必须使用normalize对齐。
标准化组
可能是animation1、animation2和animation3都是使用不同数量的帧创建的。例如动画1可以从第0帧到第80帧,animation2从帧0到75和动画3从第0帧到第100帧。可以使用归一化方法以使帧的数量为所有动画相同,如在
animationGroup2.normalize(0, 100);
通常,normalize的参数是数字beginFrame和endFrame。
所述beginFrame数必须小于或等于最小开始所有动画的帧,对于上面的实施例不大于0
的endFrame数目必须大于或等于所有动画的最大端部框架,对于上述的实施例不小于 100。
控制播放速比
控制动画播放速度的缩放因子:
animationGroup1.speedRatio = 0.25;
animationGroup2.speedRatio = 3;
这样可以加速或者减缓播放动画的速度。
从现有的动画创建一个组
您可以通过枚举动画中包含的动画从动画中创建一个新的 AnimationGroup:
var animationGroup = new BABYLON.AnimationGroup("my-animation-group");
for (anim of idleAnim.getAnimations()) {
animationGroup.addTargetedAnimation(anim.animation, anim.target);
}
组动画循环
有一个onAnimationLoop可观察对象,可用于在动画循环时触发函数。
还有一个onAnimationGroupLoop可观察对象,可用于在组的所有动画都循环时触发一个函数:
//特定某个动画
animationGroup1.onAnimationLoopObservable.add(function(targetAnimation) {
console.log(targetAnimation.animation.name);
});
//动画组
animationGroup1.onAnimationGroupLoopObservable.add(function(group) {
console.log("Group looped!");
});
组合动画:
只需设置更多动画并添加到Babylon.js 对象的动画数组。
例如,向非常简单的幻灯片动画添加旋转动画以获得:
var yRot = new BABYLON.Animation(
"yRot",
"rotation.y",
frameRate,
BABYLON.Animation.ANIMATIONTYPE_FLOAT,
BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
);
var keyFramesR = [];
keyFramesR.push({
frame: 0,
value: 0
});
keyFramesR.push({
frame: frameRate,
value: Math.PI
});
keyFramesR.push({
frame: 2 * frameRate,
value: 2 * Math.PI
});
yRot.setKeys(keyFramesR);
滑动和更快的旋转速度
将旋转值更改为更大的数字会增加旋转速率
var yRot = new BABYLON.Animation(
"yRot",
"rotation.y",
frameRate,
BABYLON.Animation.ANIMATIONTYPE_FLOAT,
BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
);
var keyFramesR = [];
keyFramesR.push({
frame: 0,
value: 0
});
keyFramesR.push({
frame: frameRate,
value: 4 * Math.PI
});
keyFramesR.push({
frame: 2 * frameRate,
value: 8 * Math.PI
});
yRot.setKeys(keyFramesR);
滑动和变化的旋转速度
将第二个关键帧位置更改为更接近帧的末尾会产生不同的旋转速率。
var yRot = new BABYLON.Animation(
"yRot",
"rotation.y",
frameRate,
BABYLON.Animation.ANIMATIONTYPE_FLOAT,
BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
);
var keyFramesR = [];
keyFramesR.push({
frame: 0,
value: 0
});
keyFramesR.push({
frame: 1.5 * frameRate,
value: 4 * Math.PI
});
keyFramesR.push({
frame: 2 * frameRate,
value: 8 * Math.PI
});
yRot.setKeys(keyFramesR);
其实这就是在单位时间内, 一个增大角度, 一个减小时间来增快。
连续动画
为了让一个动画跟随另一个动画,需要向 beginDirectAnimation 函数添加另一个参数。这个参数本身就是一个函数,当beginDirectAnimation开始的动画结束时调用。
实际上需要两个新参数,因为要调用的函数是第六个参数,所以需要填充第五个参数位置。
beginDirectAnimation 和参数
Scene.beginAnimation(目标,开始帧,结束帧,循环,速度,动画结束);
-
target - BabylonJS Object,要动画化的Babylon.js 对象
-
动画-应用于目标的所有动画的数组
-
开始帧-编号,开始动画的帧
-
结束帧-编号,动画结束的帧
-
loop - boolean : optional , true 当动画的循环模式被激活时, false 只运行一次动画
-
speed - number : 可选,默认 1 匹配动画帧速率,数字越大动画速度越快,数字越小速度越慢
-
动画结束-函数:可选,动画结束时调用的函数,要求循环为假
例子:
以下是对滑动和旋转示例的更改
在第一个例子中,盒子旋转了 5 秒钟,然后进入一个循环的幻灯片。
对 beginDirectAnimation 的代码更改是 looping 变为 false,速度保持默认为 1,并且函数 nextAnimation 在第一个结束时被调用。
scene.beginDirectAnimation(
box,
[yRot],
0,
2 * frameRate,
false,
1,
nextAnimation
);
在此之前添加的附加功能是
var nextAnimation = function() {
scene.beginDirectAnimation(box, [xSlide], 0, 2 * frameRate, true);
};
在第二个例子中,随着盒子进入循环幻灯片,旋转继续。
var nextAnimation = function() {
scene.beginDirectAnimation(box, [yRot, xSlide], 0, 2 * frameRate, true);
};
这样在播放完第一个动画以后, 会播放下一个动画。
这期的Babylon动画基础就到这里了。
总结一下:
1.我们知道帧动画的形成原理
2.如何创建动画
3.如何将动画添加到某个物体上
4.如何播放动画,暂停,和停止,重置动画。
5.如何组合动画,如何将动画分组
6.动画组件的相关API
7.如何加快动画速率,
8.如何让动画连续起来。