Laya 实现一个轻量高效好用的Tween库并支持Laya.Ease方法

Gitee仓库地址: https://gitee.com/welcome2jcSpace/h5-utils

更新

  • 2022.06.27
    增加了贝塞尔曲线
    优化了一些功能
    干掉了一些bug
    模块名改成了 es

更多

傻瓜式 lib库下载 https://download.csdn.net/download/qq_39162566/86216347

实现自己的Tween 并 支持Laya自带的 Ease库

支持:

  • 缓动: 缩放,位移,透明度,旋转 … 等所有的数值缓动 ( 包含 2D,3D )
  • ease曲线: 支持 Laya 自带的所有 Ease曲线缓动
  • 回调方法: 动作结束 动作中间 动作开始 回调。 您可以在任意位置插入你的回调方法 并且支持的是闭包和类方法 不需要 Laya.Handler.create(this,this…). 巴拉巴拉
  • 动作延时: 在动作之前,多个动作中间,动作结束,插入等待延时 秒。
  • 循环: 支持单个或多个动作循环

使用案例

在这里插入图片描述

缓动案例

        //3D缓动
        box.transform.localScaleX
        es.tween(box.transform).to(1.0, {
            localScaleX: 1.4,
            localScaleY: 1.4,
            localScaleZ: 1.4
        }).to(1.0, {
            localScaleX: 1.0,
            localScaleY: 1.0,
            localScaleZ: 1.0
        }).delay(1.0).to(1.0, {
            localRotationEulerY: box.transform.localRotationEulerY + 90
        }).loop(-1).start();

        //2D缓动
        es.tween(this.btn_test).to(1.0,{
            scaleX:1.2,
            scaleY:1.2,
            alpha:0
        }).to(1.2,{
            scaleX:1.0,
            scaleY:1.0,
            alpha:1.0
        }).loop(-1).start();

        
        //ease缓动
        let src = box_clone.transform.localPosition.clone();
        es.tween(box_clone.transform).to(1.0,{
            localPositionY:1.0
        },Laya.Ease.bounceOut).delay(1.0).call(()=>{
            //等待一秒后 回调设置位置到起始位置
            box_clone.transform.localPosition = src.clone();
        }).loop(-1).start();

源码

module es {
    /** 新建一个tween动作 */
    export function tween(target): tween_action {
        let act = new tween_action();
        if (!target) {
            es.Debug.error("You trying to create a es.tween for an nullptr")
            return act;
        }
        act['init'](target);
        return act;
    }
    /**
     * 获取未完成的tween动作数量
     * @param target 执行tween的对象
     * @returns
     */
    export function getUndoneActionCount(target): number {
        if (target && target['gk7_tweenCount'])
            return target['gk7_tweenCount'].invoke();
        return 0;
    }
    /**
     * 清除action
     * @param target 执行tween的对象
     * @param quickComplete 是否立即完成剩余动作,会顺序执行每个阶段最后一次update,包括回调方法
     */
    export function killTween(target, quickComplete = false) {
        target && target['gk7_kill'] && target['gk7_kill'].invoke(quickComplete);
    }
    /** tween工厂 */
    export class tween_core {

        private _actions: tween_action[] = [];

        //大时钟 调度
        private update() {
            const acts = this._actions;
            let act = null;
            let delta = Laya.timer.delta * 0.001;
            for (let i = 0; i < acts.length; i++) {
                act = acts[i];
                if (act.isDone()) {
                    --act._times;
                    if (act._times === 0) {
                        acts.splice(i, 1);
                        --i;
                        continue;
                    }
                    act.resume();
                }
                act.update(delta);
            }
        }

        //@friend tween_action 隐藏 不允许被外部调用 仅供 tween_action 类调度
        private add(act: tween_action) {
            this._actions.push(act);
        }
        //@friend tween_action 隐藏 不允许被外部调用 仅供 tween_action 类调度ß
        private sub(act: tween_action) {
            let index = this._actions.indexOf(act);
            index != -1 && this._actions.splice(index, 1);
        }

        private _timerID = -1;
        /** 停止tween所有tick  PS: 此操作会停下所有的动作 */
        public stop() {
            if (this._timerID == -1)
                return;
            this._timerID = -1;
            Laya.timer.clear(this, this.update);
            // window.clearInterval(this._timerID);
        }

        /** 开启/恢复tween所有tick */
        public resume() {
            if (-1 == this._timerID) {
                Laya.timer.frameLoop(1, this, this.update)
                this._timerID = 1;
                // this._timerID = window.setInterval(this.update.bind(this), 1000 / 60.0);
            }
        }

        /** 清理所有tween */
        public clear() {
            this._actions = [];
        }

        private static _inst: tween_core = null;
        public static get inst(): tween_core {
            if (!tween_core._inst) {
                tween_core._inst = new tween_core();
                tween_core._inst.resume();
            }
            return tween_core._inst;
        }
        private constructor() { }
    }
    /** 维护tween链 */
    export class tween_action {

        protected _target = null;
        protected _index = 0;
        protected _commands: command[] = [];
        protected _times = 1;

        protected init(target) {
            this._target = target;
            this._target['gk7_kill'] = es.Functor.New(this.clear, this);
            this._target['gk7_tweenCount'] = es.Functor.New(this.getUndoneActionCount, this);
        }

        /**
         * 缓动
         * @param dur 持续时间
         * @param props 目标属性
         * @param easeFn ease处理方法 例如: Laya.Ease.backInOut
         */
        to(dur: number, props: any, easeFn = null) {

            let t = new command(this._target);
            t._tm = 0;
            t._dur = dur;
            t._dest = props;
            t._easeHandler = easeFn || Laya.Ease.linearNone;
            let b = this._commands.length > 0 ? this._commands[this._commands.length - 1]._dest : this._target;
            for (let k in props) {
                t._src[k] = k in b ? b[k] : this._target[k];
            }
            this._commands.push(t);
            return this;
        }
        /**
         * 贝塞尔运动
         * es.tween(this.sprite).bezier(1.0,new Laya.Point(0,0), 
         *      new Laya.Point(100,100), 
         *      new Laya.Point(300,0)).call( this.callbackFunction ).start();
         * 
         */
        bezier(dur: number, startPoint: Laya.Point, peakPoint: Laya.Point, endPoint: Laya.Point) {
            let t = new bezierCommand(this._target, dur, startPoint, peakPoint, endPoint);
            this.fullPreStateDest(t);
            this._commands.push(t);
            return this;
        }
        /**
         * 加入一个回调
         * @param fn 回调
         */
        call(fn: Function) {
            let t = new callbackCommand(fn);
            this.fullPreStateDest(t);
            this._commands.push(t);
            return this;
        }

        /**
         * 在动作间添加一个间隔
         * @param time 间隔秒
         */
        delay(time: number) {
            let t = new delayCommand(this._target);
            this.fullPreStateDest(t);
            t._tm = 0;
            t._dur = time;
            this._commands.push(t);
            return this;
        }

        /**
         * 循环 当 times < 0 无限循环  为0时 不执行动作  为1时执行一次
         * @param times 次数 默认1
         */
        loop(times: number = 1) {
            this._times = times;
            return this;
        }

        /**
         * 开启缓动
         */
        start() {
            tween_core.inst['add'](this);
        }


        /** 清除动作 是否立即完成所有动作 */
        clear(immediately) {
            //从更新池中移除
            tween_core.inst['sub'](this);
            //如果选择了立即完成
            if (immediately) {
                for (let command of this._commands) {
                    command.immediatelyComplete();
                }
            }
            this._commands = [];
        }

        /** 获取未完成的动作数量 */
        getUndoneActionCount() {
            return this._commands.length - this._index;
        }

        //将最后一个动作的结束值作为当前动作的结束状态
        protected fullPreStateDest(curren: command) {
            curren._dest = this._commands.length > 0 ? this._commands[this._commands.length - 1]._dest : this._target;
        }

        // 恢复所有动作
        protected resume() {
            this._index = 0;
            for (let com of this._commands) {
                com._tm = 0;
            }
        }

        // 每帧更新
        protected update(dt) {
            let com = this._commands[this._index];
            if (!com.isDone()) {
                com.update(dt);
            }
            else {
                ++this._index;
            }
        }

        // 完成
        protected isDone() {
            return this._index >= this._commands.length;
        }
    }
    /** 基础command, 主要对属性进行线性插值 */
    export class command {

        _tm = 0;
        _dur = 0;
        _src = {};
        _dest = {};
        _target = null;
        _easeHandler: (t: number, b: number, c: number, d: number) => number = null;
        constructor(target) { this._target = target; }

        isDone() {
            return this._dur === 0 || this._tm / this._dur >= 1.0;
        }
        update(dt) {
            this._tm += dt;
            this._tm = this._tm > this._dur ? this._dur : this._tm;
            let r = this._easeHandler(this._tm, 0, 1, this._dur);
            for (let k in this._dest) {
                this._target[k] = this._src[k] + (this._dest[k] - this._src[k]) * r;
            }
        }
        /** 立即完成当前命令 */
        immediatelyComplete() {
            if (this.isDone()) return;
            this._tm = this._dur;
            this.update(1 / 60);
        }
    }
    /** 延时 */
    class delayCommand extends command {
        isDone() {
            return this._dur === 0 || this._tm / this._dur >= 1.0;
        }
        update(dt) {
            this._tm += dt;
            this._tm = this._tm > this._dur ? this._dur : this._tm;
        }
        immediatelyComplete() {
            this._tm = this._dur;
        }
    }
    /** 回调command */
    class callbackCommand extends command {
        constructor(fn) {
            super(null);
            this._tm = 0;
            this._dur = 1 / 60.0;
            this._fn = fn;
        }

        _fn: Function = null;
        isDone() {
            this._fn && this._fn();
            this._fn = null;//保证只执行一次
            return true;
        }
    }
    /** 贝塞尔command */
    class bezierCommand extends command {

        posArr: Laya.Point[];
        constructor(target: any, dur: number, startPoint: Laya.Point, peakPoint: Laya.Point, endPoint: Laya.Point) {
            super(target);
            this._dur = dur;
            this.posArr = [
                startPoint,
                peakPoint,
                endPoint
            ];
        }

        update(dt) {
            this._tm += dt;
            const value = Math.min(this._tm / this._dur, 1.0);
            const posArr = this.posArr;
            this._target.x = (1 - value) * (1 - value) * posArr[0].x + 2 * value * (1 - value) * posArr[1].x + value * value * posArr[2].x;
            this._target.y = (1 - value) * (1 - value) * posArr[0].y + 2 * value * (1 - value) * posArr[1].y + value * value * posArr[2].y;
        }

    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

极客柒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值