babylon101

动画


动画

你的场景看起来很棒,但它非常静态。为了掌握动态,我们将学习如何告诉您的计算机以您选择的任何方式移动网格。

Elements

最后结果

在场景中有两种主要的动画方式。第一种是定义一组键并在每个键上定义对象的情况。第二种方法是在运行时更改动画代码时使用更复杂的动画。

基本动画

动画基于名为Animation(!!)的对象。动画由各种属性和一组键定义。每个键表示该键在给定时间的动画值。

为了实现今天的动画场景,我们首先创建我们的环境:

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;

我们的目标:移动这个“box1”。首先,创建我们的Animation对象:

var animationBox = new BABYLON.Animation("myAnimation", "scaling.x", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);

参数中有很多信息:

参数1 - 此动画的名称,仅此而已。

参数2 - 有关财产。这可以是任何网格属性,具体取决于您要更改的内容。这里我们想要在X轴上缩放一个对象,因此它将是“scaling.x”。

参数 3 - 请求的每秒帧数:此动画中可能的最高FPS。

参数 4 - 更改类型。在这里,您决定并输入将要修改的值:是浮点数(例如转换),向量(例如方向)还是四元数。确切的值是:

  • BABYLON.Animation.ANIMATIONTYPE_FLOAT
  • BABYLON.Animation.ANIMATIONTYPE_VECTOR2
  • BABYLON.Animation.ANIMATIONTYPE_VECTOR3
  • BABYLON.Animation.ANIMATIONTYPE_QUATERNION
  • BABYLON.Animation.ANIMATIONTYPE_MATRIX
  • BABYLON.Animation.ANIMATIONTYPE_COLOR3

请注意,默认情况下,Matrix值不会在键之间进行插值,这意味着即使我们位于两个键之间,值也只是键值中的值。您可以通过致电打开此功能Animation.AllowMatricesInterpolation = true . 如果启用了矩阵插值,则可以使用Matrix.Lerp或Matrix.DecomposeLerp作为插值工具。你可以can then either use Matrix.Lerp or Matrix.DecomposeLerp as interpolation tool. You can use Animation.AllowMatrixDecomposeForInterpolation 用来挑选你想要的那个。

你可以在这里找到一个演示https://www.babylonjs-playground.com/frame.html#DMLMIP#1

参数 5 - 最后,您必须决定并输入此动画将在其上限处进行的行为类型(例如,它将继续,将再次开始,是否将停在最后一个键值等):

  • 使用以前的值并递增它: BABYLON.Animation.ANIMATIONLOOPMODE_RELATIVE
  • 从初始值重新启动: BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
  • 保持最终价值: BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT

现在我们有了Animation对象,现在是时候说出如何修改这些值了。在我们的例子中,我们想要缩放我们的盒子,但不是以线性的方式:它变大时必须更快,当它变得更薄时它必须更慢。所以:

// 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, Vector3 and Quaternion,您还可以提供具有inTangent和outTangent值的键,以使用样条插值而不是线性插值:

  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);
    
  • 将此动画链接到我们的框:

    box1.animations = [];
    box1.animations.push(animationBox);
    

最后,您可以在应用程序中随时在一行代码中启动动画:

scene.beginAnimation(box1, 0, 100, true);

您还可以通过交换“从”和“到”框架反向运行动画:

scene.beginAnimation(box1, 100, 0, true);

scene.beginAnimation的参数:

名称 类型 描述 可选的
目标 任何 目标 没有
fps起始帧 没有
fps结束帧 没有
布尔 如果为true,则动画将循环(取决于BABYLON.Animation.ANIMATIONLOOPMODE)
转速比 默认值:1。此动画的速度比
onAnimationEnd ()=>无效 在动画结束时触发的功能,即使手动停止动画(也取决于ANIMATIONLOOPMODE)
动画 动画 可选的特定动画
stopCurrent 布尔 我们应该停止当前现有的动画吗?默认为是

此函数返回一个BABYLON.Animatable对象,您可以使用该对象来访问单个动画(例如使用getAnimationByTargetProperty函数)。

该BABYLON.Animatable对象还支持以下功能:

  • pause()
  • restart()
  • stop()
  • reset()

通过将动画设置为引用(如变量)来控制上一个示例动画:

var newAnimation = scene.beginAnimation(box1, 0, 100, true);

然后暂停:

newAnimation.pause();

这些命令将应用于Animatable的._animations数组中包含的每个动画对象。您还可以BABYLON.Animatable使用scene.getAnimatableByTarget()提供目标对象来访问当前运行的对象。

你完成了!我们现在已经完成了box1.scaling.x的动画。也许现在你想为box1.scaling.y构建一个动画,并且真的让box1开玩笑。不要犹豫为一个网格对象组合多个动画......通过创建更多动画并将它们推入网格的动画属性。;)

动画和承诺

从Babylon.js v3.3开始,您可以使用promises等待anmatable结束:

var anim = scene.beginAnimation(box1, 0, 100, false);

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

你可以在这里找到一个例子:https://www.babylonjs-playground.com/#HZBCXR -


控制动画

每个Animation都有一个名为的属性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);
};

以下是您可以更改的功能列表:

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

辅助功能

您可以使用扩展功能来创建快速动画:

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

为了能够使用此功能,您需要知道:

  • 您的动画将具有预定义的关键帧(仅生成2个关键帧:开始和结束)
  • 动画仅适用于AbstractMesh对象。
  • 动画在方法调用之后立即开始。

以下是使用CreateAndStartAnimation()函数的简单示例:

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

快速而简单 :)

动画混合

您可以使用enableBlending = true 启动动画以启用混合模式。此混合动画将插入当前对象的状态。这对于用户控制的行走角色或对输入设备的值变化做出反应非常方便。

在下面的游乐场演示中,每次单击FPS标记时,新动画都会与框的当前位置混合:https://www.babylonjs-playground.com/#2BLI9T#174 -


虽然这个游乐场将相同的动画混合到自身中,但更常见的是,将不同的动画混合到原始动画中,例如当行走角色变为跑步时:https://www.babylonjs-playground.com/frame.html#IQN716#9

动画重量

从Babylon.js 3.2开始,您可以使用特定权重开始动画。这意味着您可以使用此API在同一目标上同时运行多个动画。最终值将是基于其权重值加权的所有动画的混合。

要使用权重启动动画,您可以使用新scene.beginWeightedAnimationAPI:

// 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);

此函数接受以下参数:

名称 类型 描述 可选的
目标 任何 目标 没有
fps起始帧 没有
fps结束帧 没有
重量 这个动画的重量。默认为1.0
布尔 如果为true,则动画将循环(取决于BABYLON.Animation.ANIMATIONLOOPMODE)
转速比 默认值:1。此动画的速度比
onAnimationEnd ()=>无效 在动画结束时触发的功能,即使手动停止动画(也取决于ANIMATIONLOOPMODE)
动画 动画 可选的特定动画

就像beginAnimation,这个函数返回一个动画,但这次将其weight属性设置为一个值。

您还可以随时设置weight任何动画的值以切换到加权模式。该值必须介于0和1之间。同样,您可以将其设置为-1以关闭重量模式。如果将权重设置为0,则动画将被视为暂停。

var idleAnim = scene.beginWeightedAnimation(skeleton, 0, 89, 1.0, true);
var runAnim = scene.beginWeightedAnimation(skeleton, 125, 146, 0, true);

idleAnim.weight = 0.5;
runAnim.weight = 0.5

如果您的动画大小不同(从键和键之间的距离相同),则需要使用以下代码打开动画同步:

// Synchronize animations
idleAnim.syncWith(runAnim);

要禁用动画同步,只需调用即可animation.syncWith(null)。

可在此处找到完整的演示: https://www.babylonjs-playground.com/#IQN716#9 -


覆盖属性

如果网格具有多个动画或骨架(可以对所有骨骼进行动画处理),则可以使用animationPropertiesOverride为所有子动画指定一些常规属性。这些属性将覆盖本地动画属性:

var overrides = new BABYLON.AnimationPropertiesOverride();

overrides.enableBlending = true;
overrides.blendingSpeed = 0.1;

skeleton.animationPropertiesOverride = overrides;

以下是可以覆盖的属性列表:

  • enableBlending
  • blendingSpeed
  • loopMode

请注意,如果动画目标不包含scene.animationPropertiesOverride,则将使用它。

缓解功能

您可以使用缓动函数为动画添加一些行为。如果您想了解有关缓动函数的更多信息,请参阅以下链接:

所有这些缓动函数都在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属性来更改缓动函数的行为方式,即更改动画插值的方式。您可以为EasingMode提供三种可能的值:

  • BABYLON.EasingFunction.EASINGMODE_EASEIN : 插值遵循与缓动函数关联的数学公式。
  • BABYLON.EasingFunction.EASINGMODE_EASEOUT : 插值遵循100%插值减去与缓动函数关联的公式的输出。
  • BABYLON.EasingFunction.EASINGMODE_EASEINOUT : Interpolation在动画的前半部分使用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);

您也可以使用Bezier曲线算法,使用BezierCurveEase(x1,y1,x2,y2)函数。为此,这里有一个很好的参考来创建您的曲线算法: http://cubic-bezier.com

这是一个使用贝塞尔曲线算法的非常酷的实现:

var bezierEase = new BABYLON.BezierCurveEase(0.32, -0.73, 0.69, 1.59);

最后,您可以扩展EasingFunction基本函数以创建自己的缓动函数,如下所示:

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);

您将在操场上找到缓动功能行为的完整演示: 缓动功能操场

复杂的动画

复杂动画允许您选择动画每帧的所有内容(每个刻度)。运行时计算的代码必须位于此函数中:

scene.registerBeforeRender(function () {
  //Your code here
});

此功能对于像游戏这样的复杂动画非常有用,其中角色必须根据许多参数移动。

不要犹豫,结合所有这些类型的动画。如果做得好,它非常强大。

不要忘记 访问我们的API文档 以了解有关Babylon.js动画Babylon.js 动画 类的更多信息。

将事件附加到动画

从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);

确定性的锁步

有时,重要的是确保动画,物理和游戏逻辑代码同步并通过帧速率方差解耦。这可能有助于在给定相同的初始条件和输入的情况下重放场景的演变方式,或者在多用户环境中最小化多个客户端的差异。

原理是通过以离散时间步长更新固定频率的状态来量化状态执行时间,从而使累加器保持超过下一帧更新的时间。

为此,需要通过以下两个选项创建Babylon引擎:

this.engine = new BABYLON.Engine(theCanvas, true, {
  deterministicLockstep: true,
  lockstepMaxSteps: 4
});

这样,场景将通过物理引擎中设置的timeStep量的离散块来渲染量化物理和动画步骤。例如:

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

使用上面的代码,引擎将以60Hz(0.01666667s)运行离散步骤,并且在晚帧渲染时间的情况下,它将尝试计算最多4步(lockstepMaxSteps)以恢复最终累积延迟,然后再渲染帧。

请注意,在显式创建CannonJSPlugin时,必须在其构造函数中将false作为_useDeltaForWorldStep参数传递,以禁用CannonJS内部累加器。

要与步骤同步运行逻辑代码,场景中有以下两个可观察对象:

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());
});

使用它们可以在动画和物理更新之前和之后运行任意逻辑代码。

在下面的示例中,您可以在控制台中看到其中球体被视为静止的stepId和旋转框的旋转值。无论帧速率如何,多次运行始终会产生相同的值。

https://www.babylonjs-playground.com/#DU4FPJ#3 -


就是这样!

下一步

现在你知道如何创建一个完整的简单动态场景,谈论碰撞是很重要的。.

进一步阅读

动画概述