Skip to content

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来去做运行时的处理,封装所有细节,这部分暂时没有执行,而只是有一个运行时的实现,后续会对此进一步迭代。