从 Babylon.js v3.3 开始,您可以使用 promise 来等待动画结束:
var anim = scene.beginAnimation(box1, 0, 100, false);
console.log("before");
await anim.waitAsync();
console.log("after");
控制动画
每个 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);
};
这里我还不太明白, 后面有使用到了这些再来填坑。
辅助功能
您可以使用扩展函数来创建快速动画:
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 标记时,新动画都会与框的当前位置混合:
Babylon.js PlaygroundBabylon.js playground is a live editor for Babylon.js WebGL 3D sceneshttps://playground.babylonjs.com/#IQN716%239动画权重:
启动具有特定权重的动画。这意味着您可以使用此 API 在同一目标上同时运行多个动画。最终值将是基于权重值加权的所有动画的混合。
要使用权重启动动画,您可以使用新的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);
这个API具有以下参数:
target:
from: 开始帧
to:? ? ? ? 结束帧
weight: 这个动画的权重。默认 1.0
loop :如果为 true,动画将循环播放(取决于 BABYLON.Animation.ANIMATIONLOOPMODE)
speedRatio:default : 1. 这个动画的速度比
onAnimationEnd:在动画结束时触发的函数,即使动画被手动停止(也依赖于 ANIMATIONLOOPMODE)
animatable:一个可选的特定动画
就像beginAnimation ,这个函数返回一个动画,但这次它的weight 属性设置为一个值。
您还可以随时设置weight 任何 Animatable的值以切换到加权模式。该值必须介于 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
如果您的动画大小不同(从和到关键点之间的距离相同),那么您需要使用以下代码打开动画同步:
idleAnim.syncWith(runAnim);
要禁用动画同步,只需调用animation.syncWith(null) .
添加混合动画
到目前为止,我们讨论的动画混合类型是覆盖混合。这意味着向动画添加影响会影响正在播放的其他动画。结果始终是标准化的,因此同时播放的动画越多,每个单独的动画对最终结果的影响就越小。覆盖动画中的所有关键帧都是相对于对象的父级存储的。例如,假设您有一个具有 2 个覆盖动画的对象。第一动画具有的平移值[0,0,0]在第0帧,然后将其内插于[0,2,0]上框架30,然后再返回到[0,0,0]上框架60的第二动画的平移值为[0, 0, 0]在第 0 帧,在第 30 帧插入到[0, 0, 2]?,然后在第 60 帧返回到[0, 0, 0]?。如果您同时以全权重播放这些动画,第 30 帧将产生平移值的[0,1,1]?。无论是 Y 轴还是 Z 轴都无法在两个动画播放时完全达到 2 的值。这种行为非常适合在动画之间进行转换,例如从步行到跑步的混合,但是如果您希望动作建立在彼此之上怎么办?这是附加动画变得有用的地方。
附加动画是独一无二的,因为它不使用那种类型的规范化逻辑。您可以同时播放 N 个附加动画,并且每个动画都具有指定的确切影响量。为了实现这一点,附加动画值是相对于覆盖动画的当前结果,而不是父动画的。因此,如果上面示例中的第二个动画要叠加播放,则第 30 帧的值将是[0, 2, 2],因为第二个动画的值是在第一个动画的基础上相加的。
有几种方法可以指定要对动画进行附加评估。首先,一个可选的布尔isAdditive 参数已添加到所有用于开始动画的场景方法中。查看Scene API 文档以查看每个方法的最新参数列表。默认情况下,此参数为 false 并将设置isAdditive 生成的Animatable的新布尔属性。此isAdditive 属性控制是否应附加评估 Animatable,并且可以随时更改。AnimationGroups现在也有一个isAdditive 默认为 false 的访问器。设置此访问器将设置isAdditive 该组控制的所有 Animatables的属性。
附加动画的一个问题是层次结构的创作问题。因为附加动画是相对于其他动画的结果而不是对象的父级来评估的,所以直接创建它们不是很直观。为了减轻这种负担,MakeAnimationAdditive 已将静态方法添加到AnimationGroup、Skeleton和Animation类中。这些方法允许您在现有动画中指定一个帧,并将其从动画中的其余关键帧中减去,以使它们都与该特定姿势相关。
以下示例演示了如何将动画转换为附加动画并将它们混合到覆盖动画之上。UI 按钮允许您在多个覆盖动画之间进行混合,并且滑块混合在顶部的附加动画中。
覆盖属性
当您有一个具有多个动画的网格或一个骨架(所有骨骼都可以动画)时,您可以使用 animationPropertiesOverride 为所有子动画指定一些通用属性。这些属性将覆盖本地动画属性:
var overrides = new BABYLON.AnimationPropertiesOverride();
overrides.enableBlending = true;
overrides.blendingSpeed = 0.1;
skeleton.animationPropertiesOverride = overrides;
以下是可以覆盖的属性列表:
请注意,如果动画目标不包含一个,则将使用 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);
您也可以使用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);
?
将事件附加到动画
从 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 以及旋转框的旋转值。无论帧速率如何,多次运行将始终产生相同的值。
效果类似帧同步
|