Appearance
RigidAnim 刚体动画
这部分位于RoyBase中的roytween文件夹下,具体的类有:
- Tween:对一个属性的插值
- TweenGroup:Tween的组合,也就是对多个不同属性的插值
- Timeline:将Tween或TweenGroup排布到时间线上,执行的时候根据时间线的位置,逐个分别插值Tween或者TweenGroup。
其中Tween和TweenGroup是实际的动画对象,继承自AbstractTween。 为了解决如何插值的问题,还有一个Ease类,包含了绝大多数主流的缓动函数:
- linear:线性插值
- getPowIn: 指数插值
- getPowOut: 指数插值
- ......
插值的对象可以是任意类型的任意对象,如RoySceneNode的translate,RoyMaterialInstance的某个参数,css中的某个attribute。
- 因为插值具有任意类型:定义自定义的插值器插件,目前封装了常用的插件,这些插件需要再App的onInit方法中初始化,如果没有注册,那么都按照number来进行插值处理:
- TwQuatPlugin
- TwVec2Plugin
- TwVec3Plugin
- TwVec4Plugin
- 因为插值对象有不同的表现形式: 尤其是还有一些组合对象,所以为了灵活把控,具有自定义的插值回调函数。为了更好的理解这一点,请看以下代码:
typescript
createTween(tweenInfoList: LightTweenData[], tweenOpts: LightTweenOptions) {
if (this.m_lightMixComp === null) {
return;
}
this.removeAllTween();
this.m_currentLightParam.set(this.m_lightMixComp!.lightMapParams);
const tweenInfoMap: any = {};
if (tweenOpts.resetInit) {
this.m_targetLightParam.set(this.m_initLightParam);
}
for (const tweenInfo of tweenInfoList) {
const lightInfo: LightMixItemJsonData = this.m_lightInfoMap[tweenInfo.name];
if (lightInfo === null) {
continue;
}
tweenInfoMap[lightInfo.index] = tweenInfo;
const indexOffset: number = lightInfo.index * 4;
this.m_targetLightParam[indexOffset + 0] = tweenInfo.r;
this.m_targetLightParam[indexOffset + 1] = tweenInfo.g;
this.m_targetLightParam[indexOffset + 2] = tweenInfo.b;
this.m_targetLightParam[indexOffset + 3] = tweenInfo.mult;
}
if (!tweenOpts.enabled) {
this.m_lightMixComp!.lightMapParams.set(this.m_targetLightParam);
this.m_lightMixComp!.applyLightParam();
for (let index = 0; index < this.m_lightInfoArray.length; ++index) {
const indexOffset: number = index * 4;
this.m_lightInfoArray[index].enable = this.m_lightMixComp!.lightMapParams[indexOffset + 3];
}
return;
}
for (let index = 0; index < this.m_lightMapCount; ++index) {
const indexOffset: number = index * 4;
const proxyObj = new Proxy(
{
isProxy: true,
propNames: ['progress'],
progress: 0,
},
{
get: (target: any, prop: string) => {
return target[prop];
},
set: (target: any, prop: string, newVal: any) => {
target[prop] = newVal;
if (prop !== 'progress') {
return true;
}
const targetR = this.m_targetLightParam[indexOffset];
const targetG = this.m_targetLightParam[indexOffset + 1];
const targetB = this.m_targetLightParam[indexOffset + 2];
const targetEnable = this.m_targetLightParam[indexOffset + 3];
const currentR = this.m_currentLightParam[indexOffset];
const currentG = this.m_currentLightParam[indexOffset + 1];
const currentB = this.m_currentLightParam[indexOffset + 2];
const currentEnable = this.m_currentLightParam[indexOffset + 3];
const finalR = currentR + (targetR - currentR) * newVal;
const finalG = currentG + (targetG - currentG) * newVal;
const finalB = currentB + (targetB - currentB) * newVal;
const finalEnable = currentEnable + (targetEnable - currentEnable) * newVal;
this.m_lightParamDirty = true;
this.m_lightMixComp!.setLightMapParamIndex(index, [finalR, finalG, finalB, finalEnable]);
return true;
},
},
);
let delay = 0;
let duration = 1;
const tweenInfo = tweenInfoMap[index];
if (tweenInfo) {
delay = tweenInfo.delay;
duration = tweenInfo.duration;
}
const tween = Tween.get(proxyObj, {
loop: false,
})
.wait(delay * 1000)
.to({ progress: 1 }, duration * 1000, Ease.cubicOut);
this.m_tweenList.push(tween);
}
if (tweenOpts.totalTime) {
const operLightMode = this.m_lightModeData[this.m_lightModeIndex];
let target: any = { progress: 0 };
const tween = Tween.get(target, {
loop: false,
onChange: () => {
operLightMode.progress = (tween.target.progress as number) * 100;
tweenOpts.onChange?.((tween.target.progress as number) * 100);
},
onComplete: () => {
tweenOpts.onComplete?.();
this.m_currentLightParam.set(this.m_lightMixComp!.lightMapParams);
this.m_tweenList = [];
},
}).to({ progress: 1 }, tweenOpts.totalTime * 1000, Ease.linear);
this.m_tweenList.push(tween);
}
}
这段代码是位于plugin_lightmix组件中的一段,他的业务场景是为了服务于华为灯光模拟器项目,以缓动的方式混合多个图层。为此封装了一个用于缓动的代理对象,欢动操作是施加到这个代理对象上的,当代理对象的属性被Tween动态设置的时候,再把代理对象的属性转换为实际的业务参数,送给组件。需要说明的是,这种方式只是对"任意对象"插值的一种表达形式,把任意对象转换为比较固定的一种代理对象形式,实际上还有更多的表达形式,再实际应用中可以根据应用场景选择最合适的方式。
刚体动画的最终形式,其本身可以使用编辑器进行动画编辑,保存的内容形成一个资源(动画本身的K帧数据就是资源),然后用一个AnimationComponent来去做运行时的处理,封装所有细节,这部分暂时没有执行,而只是有一个运行时的实现,后续会对此进一步迭代。