weight以及相关API
与动画播放相关的核心属性有两个:time和weight。有关time的内容介绍,请看我的另一篇文章Three.js Animation time和timeScale - 掘金 (juejin.cn)
time用来驱动动画的播放进度,而weight则表示动画的影响程度。例如,一个动画是线性的从1到2(对应time为0和1)改变一个属性。则当time === 0.5时,这个属性的值为1.5。这是在time那篇文章中了解到的内容,但是这里有一个前提没有说,那就是weight === 1。如果weight === 0.5,上面time === 0.5时,属性的值为0.5 * 1.5 = 0.75。这就是weight的作用,用来控制一个动画对属性的影响。
_effectiveWeight
这个属性和_effectiveTimeScale很类似,是真正起作用的weight。
同样是在AnimationAction._update()中(建议先看一下上面提到的time相关的文章),接着timeScale下方的代码:
deltaTime *= this._updateTimeScale(time);
const clipTime = this._updateTime(deltaTime);
// note: _updateTime may disable the action resulting in
// an effective weight of 0
const weight = this._updateWeight(time);
在这个方法中,使用了_updateWeight()方法:
先不管其他的代码,只关注最后两句。
_updateWeight(time) {
let weight = 0;
if (this.enabled) {
weight = this.weight;
const interpolant = this._weightInterpolant;
if (interpolant !== null) {
const interpolantValue = interpolant.evaluate(time)[0];
weight *= interpolantValue;
if (time > interpolant.parameterPositions[1]) {
this.stopFading();
if (interpolantValue === 0) {
// faded out, disable
this.enabled = false;
}
}
}
}
this._effectiveWeight = weight;
return weight;
}
最后两句,就是将this._effectiveWeight的值返回,而更新动画时,使用的就是这个值,所以说,真正影响动画的,是_effectiveWeight。
再仔细看上面的代码,在以下两种情况都满足时,weight === _effectiveWeight:
-
enabled === true -
_weightInterpolant === null
反之,在enabled === false时,_effectiveWeight === 0,这就导致了,动画对属性没有影响。
当_weightInterpolant !== null时,也就是说,_effectiveWeight被线性插值,与timeScale几乎一摸一样的逻辑。
对_effectiveWeight进行线性插值,就是让动画有一定的“过渡”,如果动画的某个属性的起始值和这个属性的当前值差距比较大,动画开始播放时,这个值瞬间变化到动画的初始属性,感觉是瞬间移动过去的。为了让过渡阶段平滑一些,可以把weight(_effectiveWeight)从0线性变化到1。这就是AnimationAction.fadeIn()方法的作用。
fadeIn(duration) {
return this._scheduleFading(duration, 0, 1);
}
fadeIn()的实现也就是通过_scheduleFading()方法设置一个线性插值,在duration的时间,把weight从0变化到1。接着看_scheduleFading()方法的代码:
_scheduleFading(duration, weightNow, weightThen) {
const mixer = this._mixer, now = mixer.time;
let interpolant = this._weightInterpolant;
if (interpolant === null) {
interpolant = mixer._lendControlInterpolant();
this._weightInterpolant = interpolant;
}
const times = interpolant.parameterPositions,
values = interpolant.sampleValues;
times[0] = now;
values[0] = weightNow;
times[1] = now + duration;
values[1] = weightThen;
return this;
}
和对timeScale插值一样,通过mixer_lendControlInterpolant()创建一个LinearInterpolant对象,然后设置插值的参数。最终,在AnimationAction._updateWeight()获得某一个时刻_effectiveWeight的值。
除了有fadeIn,还有fadeOut方法,在动画结束时进行过度,将weight从0线性变化到1:
fadeOut(duration) {
return this._scheduleFading(duration, 1, 0);
}
还有另外两个过度的方法:crossFadeFrom()和crossFadeTo()
crossFadeFrom(fadeOutAction, duration, warp) {
fadeOutAction.fadeOut(duration);
this.fadeIn(duration);
if (warp) {
const fadeInDuration = this._clip.duration,
fadeOutDuration = fadeOutAction._clip.duration,
startEndRatio = fadeOutDuration / fadeInDuration,
endStartRatio = fadeInDuration / fadeOutDuration;
fadeOutAction.warp(1.0, startEndRatio, duration);
this.warp(endStartRatio, 1.0, duration);
}
return this;
}
crossFadeFrom让“自己”过渡开始,同时使指定的action过渡结束。如果指定了warp = true,还会对timeScale进行线性变化。
crossFadeTo刚好是反过来的,“自己”过渡结束,同时让另一个过度进入。
crossFadeTo(fadeInAction, duration, warp) {
return fadeInAction.crossFadeFrom(this, duration, warp);
}
上述的API都是通过线性插值的方式改变_effectiveWeight,还有一个setEffectiveWeight()方法,用来直接设置:
setEffectiveWeight(weight) {
this.weight = weight;
// note: same logic as when updated at runtime
this._effectiveWeight = this.enabled ? weight : 0;
return this.stopFading();
}
当然,它也有对应的getter:
getEffectiveWeight() {
return this._effectiveWeight;
}
总结
和timeScale类似,weight在内部实现时,也分成了Animation.weight和Animation._effectiveWeight,在enable === false或者对_effectiveWeight进行线性插值时(调用了各种fade方法),weight和_effectiveWeight是不相等的。