10. 动画-让3D元素动起来

本教程翻译自官方教程Animations,各位也可以进入本站镜像站点查看

本文目录

  1. 动画Animation
    1. 基本动画
    2. 动画和异步promises
    3. 控制动画-差值函数Controlling animations
    4. 动画辅助创建函数Helper function
    5. 动画混合Animation blending
    6. 动画权重Animation weights
    7. 叠加混合动画Additive animation blending
    8. 属性重写Overriding properties
    9. 缓动函数Easing functions
    10. 复杂动画
    11. 为动画添加事件
    12. 帧同步Deterministic lockstep
    13. 下一步
  2. 延伸阅读
  3. 贡献者

动画Animation

    我们的场景已经越来越有模有样了,但它还是没有灵魂,因为里面的元素都是静态的。本章将会学习如何把动力注入到你的物体中,选择你想要的方式来移动物体。

1-07
最终结果

    在场景中添加动画主要有2种方法。第一种方法:定义一组keys,同时在每一个key中设置物体的状态,例如位置、旋转、缩放等。第二种方法:往往是对于更加复杂的动画,例如按照sin曲线运动,那就是通过在场景运行的循环中(runRenderLoop、onBeforeStepObservable)直接设置和改变物体的状态值。

基本动画

    动画的创建基于一个Animation对象,由各种属性和一组keys组成。每个key代表其在给定时间的动画状态值。为了完成本章介绍的动画场景,咱们需要创建一个场景并添加若干元素:

//记得先创建: [scene, light, camera] 
//创建一个立方体,name为Box1
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);

    让我们对Animation的参数进行详细解读:

  • 参数1 - 动画的名称
  • 参数2 - 这个与对象的属性相关,可以是任何物体的属性,具体取决于需要更新的内容,上例中我们要在X轴的方向缩放 Box1,所以这里设置为 scaling.x
  • 参数3 - 每秒请求的帧数:动画中可能达到的最高FPS。
  • 参数4 - 数值变化类型。根据参数3的配置,决定要修改的值类型:浮点数(例如x轴位置Position.x),矢量(例如位置Position)还是四元数(例如旋转rotationQuaternion)。 确切的值是:
    • 浮点数: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.ANIMATIONTYPE_MATRIX 的情况下,即使两个key之间的值都是矩阵,也不会进行插值计算,而是把前后两个矩阵中的每个值进行单独的插值计算,这可能不符合咱们的要求。如果需要开启矩阵的插值计算,请使用 Animation.AllowMatricesInterpolation=true 来启用此功能。如果启用了矩阵插值,系统默认使用 matrix.Lerp 方法来进行插值计算,但是这个方法精度不够,所以也可以设置 Animation.AllowMatrixDecomposeForInterpolation=true 来选择 matrix.DecomposeLerp 作为插值计算方法,这个方法精度高,但是效率低,请各位做好取舍。
    另外还请注意,如果 animation.loopMode === BABYLON.Animation.ANIMATIONLOOPMODE_RELATIVE 也就是第五个参数的值,则不会开启矩阵插值。

Playground 这是一个虚拟人物切换姿势的动画案例

  • 参数5 - 动画的在执行完一个周期后,需要执行的行为模式,例如继续运动、从头开始执行还是立即停止,可选三种模式:
    • 相对,相对运动,即:执行完一次,在接着最后的状态,继续执行:BABYLON.Animation.ANIMATIONLOOPMODE_RELATIVE
    • 循环,动画循环执行,即:执行完一次,从头开始再执行:BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
    • 常量,动画执行一次就停止不动了:BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT

    现在我们学习了新建Animation对象,是时候来点实际应用了。在下面的案例中,我们想做一个立方体缩放的动画,但是不只是简单的均速缩放,需要做一些修饰:放大的过程应该快一些,而缩小的过程应该慢一些。所以我们可以这么做:

// An array with all animation keys
var keys = []; 

//在动画key==0时, 缩放scaling的值是"1"
  keys.push({
    frame: 0,
    value: 1
  });

//在动画key==20时, 缩放scaling的值是"0.2"
  keys.push({
    frame: 20,
    value: 0.2
  });

//在动画key==100时, 缩放scaling的值是"1"
  keys.push({
    frame: 100,
    value: 1
  });

请注意!加入keys中的frame有顺序要求,必须是升序。

    对于二维向量Vector2、 三维向量Vector3 和 四元数Quaternion,动画默认是使用线性插值的计算方法,即均速;我们可以在key中设置正切 inTangent 和 外切outTangent属性,以此来使用spline插值,即可以由正切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()
  });

    接下来是两个重要的步骤:

  • 把之前定义的keys动画组加入到Animation对象中
animationBox.setKeys(keys);
  • 把动画和物体关联起来
box1.animations = [];
box1.animations.push(animationBox);

    最后,我们需要一行代码来启动自己的动画:

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

    我们还能够直接逆向运行动画,也就是从尾到头的顺序执行动画:

//只需要交换第二个和第三个参数
scene.beginAnimation(box1, 100, 0, true);

    以上的函数beginAnimation,它所包含的参数如下表所示:

名称 类型 描述 可选参数
target any 执行动画的对象,例如物体 必选
from number 开始的帧 必选
to number 结束的帧 必选
loop boolean 假如为true,则动画循环 (依赖BABYLON.Animation.ANIMATIONLOOPMODE) 可选
speedRatio number 默认 : 1,动画执行的速度比例,可以减慢或加快动画 可选
onAnimationEnd () => void 在动画执行结束时触发的回调函数, 如果动画是手动停止也会触发 (也依赖于 ANIMATIONLOOPMODE) 可选
animatable Animatable 指定执行动画的实例Animatable 可选
stopCurrent boolean 调用执行函数时,如果该对象的动画还在运行中,是否停止这个运行中的动画,默认为true 可选

    beginAnimation函数返回一个 BABYLON.Animatable 对象,代表 执行动画实例 (例如使用getAnimationByTargetProperty函数)。

    而BABYLON.Animatable对象,支持以下的方法:

  • pause()暂停
  • restart()重新开始
  • stop()停止
  • reset()重置
        下面的例子展示了在执行beginAnimation方法时,用变量把执行动画的实例保存了下来,之后再做进一步的动画控制。
var newAnimation = scene.beginAnimation(box1, 0, 100, true);

    然后暂停正在运行的动画

newAnimation.pause();

    各位可能已经发现立方体的 属性animations 是一个数组, box1.animations.push(animationBox); ,这意味着一个物体时可以加入多个动画形成动画组合,所以执行 beginAnimation 方法时,形成的 BABYLON.Animatable 对象也可能包含一组动画,它们被存储在 Animatable's ._animations 数组中,当我们执行BABYLON.Animatable 对象的pause、restart、stop、reset方法时,Animatable's ._animations 中的所有动画都会响应这些命令。总之,各位可以发现BABYLON.Animatable 对象非常有用,我们控制动画时常都要访问这些它们,可以通过 **scene.getAnimatableByTarget() **这个场景的方法来枚举所有的这些对象。

    现在,我们已经完成了对于物体的x轴缩放动画 box1.scaling.x ,但这有点单调,可以再加一个y轴的缩放动画,或者为了更有趣可以使它动起来。可以通过加入更多的动画到物体的animations属性中 box1.animations.push(animationBox);,来实现多个动画的组合,快试试吧!

动画和异步promises

    自从Babylon.js3.3版本以后,我们能使用promises等待动画实例animatable执行完成:

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

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

Playground promise使用示例

控制动画-差值函数Controlling animations

    每一个BABYLON.Animation对象都有一个被称为 当前帧 的属性,对应当前动画中keys数组中的一个key。
    对于高级的关键帧动画,我们可以在keys中的两个key之间自定义插值函数,也就是两个关键帧之间定义一个过渡的算法函数,实现特殊的动画过渡效果。在默认情况下的插值函数如下所示:

//表示浮点数的插值,例如scaling.x,position.y
BABYLON.Animation.prototype.floatInterpolateFunction = function (startValue, endValue, gradient) {
  return startValue + (endValue - startValue) * gradient;
};

//表示四元数的插值,例如matrix
BABYLON.Animation.prototype.quaternionInterpolateFunction = function (startValue, endValue, gradient) {
  return BABYLON.Quaternion.Slerp(startValue, endValue, gradient);
};

//表示三维向量Vector3的插值,例如scaling,postion,rotation
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

动画辅助创建函数Helper function

    各位可能觉得上面创建动画的步骤很繁琐,所以这里介绍一个扩展函数来快速创建动画:

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

    我们来详细剖析一下这个函数:

  • 函数只能定义2个关键帧,也就是对应之前的keys数组,只能插入两个key对象。
  • 函数只能针对物体起作用。
  • 函数一执行,动画就马上生效,新建和执行两个步骤被统一在一起了。
    这是使用 CreateAndStartAnimation() 函数的简单示例:
BABYLON.Animation.CreateAndStartAnimation('boxscale', box1, 'scaling.x', 30, 120, 1.0, 1.5);

    以上等价于

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

var keys = []; 

keys.push({
	frame: 30,
	value: 1
});
 
keys.push({
	frame: 120,
	value: 1.5
});

animationBox.setKeys(keys);

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

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

    是不是简单粗暴?

动画混合Animation blending

    使用 enableBlending = true 来开启动画混合模式,混合模式就是说动画的执行时以当前物体的状态为准,例如物体初始位置是在(0, 0, 0)原点,先执行一个动画改变位置,当运动到(0, 20, 0)的时候,停止原有动画,加入一个新动画,新动画的第一帧从(0,0,0)开始,默认情况下物体会回到(0,0,0)从头执行新动画,但是如果设置了 enableBlending = true ,则新动画会从(0, 20, 0)的当前位置执行。这个特性对动画改变时的平滑过渡,非常有效。

    在下面的Playground演示中,新动画都会与框的当前位置混合在一起,可以点击右上角的FPS数字标记看到效果。Playground 动画混合演示

    以上的例子是把相同的动画混合在一起,但是将不同的动画混合到一起更加常见,例如当步行角色变为跑步时:Playground 步行跑步动画切换示例,这样的效果需要自己用代码实现。

动画权重Animation weights

    自从Babylon.jsv3.2版本开始,我能使用指定的权重来执行动画,这意味着可以使用这个方法在同一对象上同时执行多个动画。最终的效果是根据所有动画的权重混合而来。
    要使用权重执行动画,可以使用新的scene.beginWeightedAnimation API:

// 权重是1,全部效果
var idleAnim = scene.beginWeightedAnimation(skeleton, 0, 89, 1.0, true);
//权重是0,等于没效果
var walkAnim = scene.beginWeightedAnimation(skeleton, 90, 124, 0, true);
// 权重是0,等于没效果
var runAnim = scene.beginWeightedAnimation(skeleton, 125, 146, 0, true);

    beginWeightedAnimation函数接受一下的参数内容:

参数名称 参数类型 描述 是否可选
target any 动画作用的对象 必填
from number 开始的帧 必填
to number 结束的帧 必填
weight number 动画执行的权重,默认为1,0为暂停状态 可选
loop boolean 假如为true,则动画循环 (依赖BABYLON.Animation.ANIMATIONLOOPMODE) 可选
speedRatio number 默认 : 1,动画执行的速度比例,可以减慢或加快动画 可选
onAnimationEnd () => void 在动画执行结束时触发的回调函数, 如果动画是手动停止也会触发 (也依赖于 ANIMATIONLOOPMODE) 可选
animatable Animatable 指定执行动画的实例Animatable 可选

    和 beginAnimation 方法一样, beginWeightedAnimation 也返回一个BABYLON.Animatable 对象,不过会多出一个weight属性,来决定执行的权重。

    我们可以随时对BABYLON.Animatable 对象的 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

    加入在设置动画时长度不一致(一致是指keys包含的frame长度一致),则需要开启动画同步:

//使idleAnim动画的长度与runAnim相同
idleAnim.syncWith(runAnim);

    要禁用动画同步,只需调用 animation.syncWith(null) 。完整的演示可以在这里找到:Playground 权重动画综合示例

叠加混合动画Additive animation blending

    目前为止,我们讨论过的动画混合类型是 覆盖混合。这意味着添加一个动画到物体时,这个物体正在执行的动画影响力将会减弱。执行的结果总是标准化的,所以同时播放的动画越多,每个动画对最终结果的影响力就越小,覆盖混合动画的所有关键帧都是存储于对象的父元素上。我们举一个栗子,有一个对象带有2个覆盖混合动画,第一个动画的关键帧是:

[
	{
		frame: 0,
		value: [0, 0 ,0]
	},
	{
		frame: 30,
		value: [0, 2 ,0]
	},
	{
		frame: 60,
		value: [0, 0, 0]
	},
]

第二个动画的关键帧是:

[
	{
		frame: 0,
		value: [0, 0 ,0]
	},
	{
		frame: 30,
		value: [0, 0 ,2]
	},
	{
		frame: 60,
		value: [0, 0, 0]
	},
]

区别在frame是第30帧,如果我们同时播放这两个动画,并且权重都为1的情况下,第0帧和第60帧相同,但是第30帧会移动到[0, 1, 1]的位置,可见虽然两个动画在的第30帧的Y和Z轴都是2,但是如果同时执行它们永远到不了2。这样的行为很适合在动画之间进行切换,例如从步行到跑步的混合动画,但是如果我们不希望这样,想要它们都达到2怎么办?所以这就是叠加混合动画发挥作用的时候了。

    叠加混合动画是唯一的,因为它不会进行标准化。我们可以同时播放N个叠加动画,而且它们各自都有确切的影响权重,所以,叠加动画的值是相对于覆盖动画的当前结果值,而不是其父类,因此如果上面的例子不是使用覆盖动画,而时使用叠加动画,则第30帧的结果将是[0, 2, 2],也就是第二个动画的值会在第一个动画的基础上相加。

    有几种方法可以指定叠加动画。首先,是可以在beginAnimation中指定isAdditive参数。可以看看场景Scene的API文档。默认情况下,isAdditive参数都为false。isAdditive属性决定Animatable对象是否应该进行叠加评估,而且能够在任何时候被修改。 AnimationGroups 动画组对象,可以添加多个对象的多个动画到一个动画分组中,也有一个isAdditive属性访问器,默认为false。设置isAdditive属性访问器将修改动画组中所有Animatables对象的isAdditive属性。

    叠加动画的一个难题是层级的管理问题,因为叠加动画的评估不是根据其对象的父层级,而是其他动画的相对结果,因此直接创建叠加动画不是很直观。为了让使用变得容易,在AnimationGroup动画组、 Skeleton骨骼动画 、Animation动画中添加了一个MakeAnimationAdditive静态方法,它允许你指定现有动画中的某一个帧,并将其从动画的关键帧中减去,以便于使其他动画都相对这一帧的特定效果起作用。

Playground 叠加动画示例

    以上的例子展示了如何把动画转换为叠加动画,然后将其混合到替代动画中。点击示例中的按钮,可以在多个替代动画中切换,使用滑块可以在叠加动画的基础上进行动画混合。

属性重写Overriding properties

    当有一个物体,物体上存在多个动画或者一个骨骼动画(所有的骨骼都能运动),我们可以使用animationPropertiesOverride方法来为物体中的所有子动画指定一个默认属性。这些属性将重写子动画的属性值:

var overrides = new BABYLON.AnimationPropertiesOverride();

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

skeleton.animationPropertiesOverride = overrides;

    以下是能够被重写的属性清单:

  • enableBlending
  • blendingSpeed
  • loopMode
        请注意,如果animation的对象没有包含以上属性,scene.animationPropertiesOverride还是能够生效。

缓动函数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%插值减去与缓动函数关联的公式输出值,可以理解为最大值减去运行中的值,相当于EASEIN倒过来。
  • BABYLON.EasingFunction.EASINGMODE_EASEINOUT:插值将EaseIn用于动画的前半部分,将EaseOut用于后半部分。

    以下是一个简单的实例,在CircleEase缓动函数中为圆环设置动画:

//创建一个位置的三维向量Vector3动画, FPS为30
var animationTorus = new BABYLON.Animation("torusEasingAnimation", "position", 30, BABYLON.Animation.ANIMATIONTYPE_VECTOR3, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);

// 圆环的目标位置
var nextPos = torus.position.add(new BABYLON.Vector3(-80, 0, 0));

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

// 穿件缓动函数CircleEase
var easingFunction = new BABYLON.CircleEase();

//对于每个缓动函数,可以选择 EASEIN (默认), EASEOUT, EASEINOUT三种
easingFunction.setEasingMode(BABYLON.EasingFunction.EASINGMODE_EASEINOUT);

// 添加缓动函数到动画中
animationTorus.setEasingFunction(easingFunction);

// 向圆环中的动画集合中添加动画实例
torus.animations.push(animationTorus);

// 最后,执行圆环动画,从0到120帧,循环播放
scene.beginAnimation(torus, 0, 120, true);

    也可以用BezierCurveEase(x1, y1, x2, y2) 函数,用 贝塞尔曲线BezierCurve 算法来实现一个缓动函数。为此,有一个创建曲线算法很好的小工具,推荐给大家。

    下面是一个非常酷的代码实现,使用了贝塞尔曲线算法:

2-bezier

//创建一个贝塞尔曲线函数
var bezierEase = new BABYLON.BezierCurveEase(0.32, -0.73, 0.69, 1.59);
//为动画对象设置一个缓动函数
animationTorus.setEasingFunction(bezierEase);

    最后,我们可以扩展EasingFunction函数来创建自己的缓动函数,如下所示:

var FunnyEase = (function (_super) {
  __extends(FunnyEase, _super);
  function FunnyEase() {
    _super.apply(this, arguments);
  ;}
  FunnyEase.prototype.easeInCore = function (gradient) {
    //这里是一个核心方法,你可以在这里修改代码,定义自己的缓动函数
    // gradient参数是值改变的百分比,如果总体是100,现在运行到20,那么gradient就是20%
    return Math.pow(Math.pow(gradient, 4), gradient);
  };
  return FunnyEase;
})(BABYLON.EasingFunction);

    你可以在下面的Playground中查看缓动函数行为的完整示例。

Playground 缓动函数示例

复杂动画

    复杂的动画可以在每一帧进行内容的修改,所以运行时计算的代码必须在此函数中:

scene.registerBeforeRender(function () {
  //在这里实现动画计算
});

     registerBeforeRender() 设置的功能在每帧之前运行(通常fps是60),所以可以通过对对象的属性进行更改,来实现动画效果。

    这里是一个关于复杂动画的简单例子:
复杂动画示例

    此功能对于复杂动画(例如游戏)非常有用,因为游戏中角色的移动,需要依靠许多参数来决定。

    将所有动画结合起来,如果做得很好,这个功能将发挥很大作用。

    请别忘了访问我们的API文档,这里可以学习更多关于Babylon.js动画类动画执行实例类Animatable的相关知识。

为动画添加事件

    从Babylon.js V2.3版本开始,我们可以添加一个事件到动画的特定帧。

    事件是一个函数,它将在特定的帧时被执行。

    使用起来非常简单:

// 用3个参数来创建事件:
// - 事件触发时的帧,一个数值
// - 执行的动作,在这里写代码
// - 事件是否只执行一次,默认是false,每次都执行
var event1 = new BABYLON.AnimationEvent(50, function() { console.log("Yeah!"); }, true);
// 把事件添加到动画中
animation.addEvent(event1);

帧同步Deterministic lockstep

    有时确保动画,物理和游戏逻辑代码的同步,是非常重要的事,而且需要通过 帧速率方差frame-rate variance 解耦。这在两方面是非常有用的,首先是能够重放场景的演变过程,其次是在多用户环境下,让各个客户端之间的差异状态最小化,就像王者荣耀、魔兽、星际、CS等游戏一样,双方联机,两边的状态必须是一致的内容。

    帧同步的原理是量化状态的执行时间,具体方法为:在固定的频率下,以离散的时间步长来更新状态,并且保留一个累加器,以便将超过的时间带入下一帧更新。

    为此,需要通过以下两个选项来初始化Babylon引擎:

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

    这样做的话,场景将以离散的 时间步长量timeStep amount chunks,来渲染量化的物理和动画步骤。就像在物理引擎中设置的那样,例如:

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

    使用上面的代码,引擎将以60Hz的频率,来运行离散步骤,如果帧渲染时间较晚,引擎将在渲染帧之前,尝试计算最多4个步骤(lockstepMaxSteps)以恢复最终累积的延迟。也就是在用户的客户端保持一致的状态,慢的人会接受最新的状态进行更新。

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

    为了与步骤同步运行逻辑代码,场景中有以下两种observables方法:

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

    以上方法在动画和物理运行之前或之后,允许运行任意的逻辑代码。

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

Playground 帧同步案例

下一步

   &nbsp现在我们已经知道如何创建一个完整的简单动态场景,所以讨论动画运行时的物体碰撞以及重力等,是很重要的!请阅读下一章FPS游戏必备-相机、形状碰撞、重力

延伸阅读

动画概述

贡献者

翻译/校对: Cow&Sheep
Playground: 天才
技术支持: Dushusir github

参与贡献? 联系alexads@foxmail.com