RxJS在CocosCreator中的应用——封装tween与其可中断用法

开头

CocosCreator中有个Tween类,控制缓动动画。既然是动画,就是一个时间过程,可以看成一个异步过程。而rxjs在管理异步管理方面有非常强大的功能。因此,习惯将任何异步过程都封装为rxjs的observable,就能使用rxjs强大的管理能力进行操作。

封装

// 可随时中断的Tween
export function TweenToStartWithSkip<T>(target: T, duration: number, props?: __private._cocos_tween_tween__ConstructorType<unknown>, opts?: ITweenOption)
    : { observable: Observable<{ target: T, ratio?: number, status: string }>, skip: () => void } {
    return _TweenToStart(target, duration, props, opts);
}
function _TweenToStart<T>(target: T, duration: number, props?: __private._cocos_tween_tween__ConstructorType<unknown>, opts?: ITweenOption): { observable: Observable<any>, skip: () => void } {
    let needSkip = ReactiveProperty.Create(false);
    let skip = () => needSkip.value = true;
    const observable = new Observable((observer: Observer<{ target: T, ratio?: number, status: string }>) => {
        if (opts == null) opts = {};
        opts.onStart = (target: unknown) => observer.next({ target: <T>target, status: 'start' });
        opts.onUpdate = (target: unknown, ratio: number) => observer.next({ target: <T>target, ratio, status: 'update' });
        opts.onComplete = (target: unknown) => {
            observer.next({ target: <T>target, status: 'complete' });
            observer.complete();
        }
        let _t = needSkip.value ? null : tween(target).to(duration, props, opts).start();
        needSkip.subscribeWhenTrue(() => {
            observer.next({ target: <T>target, status: 'skip' });
            observer.complete();
        });
        return () => {
            _t && _t.stop();
        }
    });
    return { observable, skip };
}

如何使用/如何中断一个过程?

方式1:直接unsubscribe即可

let subscription = TweenToStartWithSkip(this.pi0, 2, { progress: 1 }).observable.subscribe({
	complete: () => log('正常结束才会调用')
})
await lastValueFrom(Timer(1));
subscription.unsubscribe();

不用管里面的skip,复制的代码,这个方式里面没用,也没有影响。
此时的unsubscribe会调用到_TweenToStart里面的new Observable 的 return () ⇒ {…}的函数,因此相当于上面的tween被stop了。
这种情况,observer.complete()没有机会调用,外部subscribe里面complete也不会被调用。

方式2:利用withSkip模式的写法

如同上面代码,提供TweenToStartWithSkip的方法,封装好后,不但得到observable,而且同时得到一个skip函数,只要调用这个函数,内部就会触发一个标记skip的next,紧接着complete也会执行。

let { observable, skip } = TweenToStartWithSkip(this.pi0, 2, { progress: 1 });
// 一般在此处进行持有,不过此处示例没有这么做
observable.subscribe({
	complete: () => log('正常结束与手动skip都会执行')
});
await lastValueFrom(Timer(1));
skip(); // 一般会将其持有,在需要关闭时调用

方式3:利用takeUntil

let skipCommand = ReactiveCommand.Create();
TweenToStartWithSkip(this.pi0, 2, { progress: 1 }).observable.pipe(takeUntil(skipCommand._subject)).subscribe({
    next: _ => { this.log('next', _); },
    complete: () => { this.log('正常结束与手动skip都会执行'); }
});
await lastValueFrom(Timer(1));
skipCommand.execute();

takeUntil里面加入另一个observable,当这个observable有next后,就结束原observable被subscribe的过程,并且,这种用法会调用到其complete方法。虽然能加入defaultIfEmpty或者endWith来确保至少有个最后的next值,但是原subscribe很难分辨出来是手动skip还是本身完成的。
另外,takeUntil参数为另一个observable。也就是说,如果业务逻辑正好有rx输出来中断时,应当可以直接使用。

总结

几种方式都能够合理的调用到observable中的return的函数。因此都可以中断动画的播放。
其中第一种方式无skip的next,无complete,是最基础的逻辑,很简单;
第二种方式有skip的next,有complete,但是封装过程时代码多,需要处理skip函数等功能
第三种方式无skip的next,有complete,利用到rx本身的操作符takeUnitl,外部subscribe时代码多。
另外,几种方式均能正确中断concat连接的异步过程。
如果是await形式的异步过程,则await后需要额外进行中断。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值