Przeglądaj źródła

组件模块提交:UI打开关闭动画、飞节点动画、ScrollView多预制体动态加载滑动列表

薛鸿潇 5 lat temu
rodzic
commit
f610652bd0

+ 465 - 0
mk_framework/assets/script/game/component/TableViewUtils.ts

@@ -0,0 +1,465 @@
+
+const { ccclass, property } = cc._decorator;
+
+/** 动态加载的滚动列表
+ * - 滑动列表动态加载
+ * - 支持多种类型预制体混合滚动 
+ * - item脚本需要有函数 setItemData 用于接收数据
+ * - 多个prefab混合时,要为每条item指定 pfbType
+ * - item、content锚点y需要为1其他为0.5
+ * @author 薛鸿潇
+*/
+@ccclass
+export default class TableViewUtils extends cc.Component {
+
+    /** 数据 */
+    private itemData: any = null;
+    /** 滚动列表组件 */
+    private scrollView: cc.ScrollView = null;
+    /** item预制体组 */
+    private item: Array<any> = null;
+    /** 间隔 */
+    private itemInterval: number = 0;
+    /** 滑动列表的内容节点 */
+    private content: any = null;
+    /** 列表高 */
+    private layerHeight: any = null;
+    /** ? */
+    private firstItemIndex: number = 0;
+    private lastItemIndex: number = 0;
+    private firstItemData: any = {};
+    private lastItemData: any = {};
+    private itemArr: Array<any> = [];
+    private itemNode: Array<any> = [];
+    private itemPosMap: any = new Map();
+    private initItemData: boolean = true;
+    private count: number = 0;
+    private itemCanMoveDown: boolean = null;
+    private itemCanMoveUp: boolean = null;
+    //itemData是item数据,itemPosMap是滚动条滚动时要用到的item位置数据.
+    /**
+     * @param itemData 列表数据
+     * @param scrollView 滑动组件
+     * @param item 预制体组
+     * @param itemInterval 间隔
+     * @param isSkipInit 跳过初始化
+     * @returns 
+     */
+    public init(itemData: Array<any>, scrollView: cc.ScrollView, item: Array<cc.Prefab>, itemInterval: number, isSkipInit) {
+        if (!arguments[0]) {
+            return false;
+        }
+
+        this.itemData = itemData; //item数据
+        this.scrollView = scrollView;
+        this.item = item;
+        this.itemInterval = itemInterval || 0; //间距
+        //初始化各类属性
+        this.content = this.scrollView.content;
+        this.layerHeight = this.scrollView.node.height;
+        this.firstItemIndex = 0;
+        this.lastItemIndex = 0;
+        this.firstItemData = {};
+        this.lastItemData = {};
+        this.itemArr = [];
+        this.itemNode = [];
+        this.itemPosMap = new Map();
+        this.initItemData = true;
+        this.count = 0;
+        if (isSkipInit) return;
+        for (let i = 0; i < this.item.length; i++) {
+            this.itemNode[i] = cc.instantiate(this.item[i]);
+        }
+        // cc.log(this.testTime(0));// 消耗计时
+        this.initItem();
+        this.getItemPos(0);
+        this.scrollView.node.on('scrolling', this.callback, this);
+        // cc.log('tableView结束:' + this.testTime());
+    }
+    /** 刷新列表 
+     * 仅更新data数据
+    */
+    private updateList() {
+
+    }
+
+    private getItemPos(index: number) {
+        let item_data_count = this.itemData.length;
+        for (let i = index; i < item_data_count; i++) {
+            let obj: any = {}
+            let y;
+            if (i === 0) {
+                obj.startPos = 0;
+            } else {
+                obj.startPos = this.itemPosMap.get(i - 1).endPos;
+            }
+            let j = this.itemData[i].pfbType || 0;
+            obj.endPos = obj.startPos + this.itemNode[j].height + this.itemInterval;
+            this.itemPosMap.set(i, obj);
+        }
+        if (item_data_count > 0) {
+            this.updateContentHeigh(this.itemPosMap.get(item_data_count - 1).endPos);
+        }
+    }
+    /**
+     * 实例化所有用到的item,控制实例化item的数目,暂定超出两个
+     */
+    private initItem() {
+        let j = 0;
+        for (let i = 0; i < this.itemData.length; i++) {
+            if (this.content.height > this.layerHeight) {
+                j++
+                if (j > 2) {
+                    break;
+                }
+            }
+            let y = 0;
+            if (i > 0) {
+                y = this.itemArr[i - 1].y - this.itemArr[i - 1].height - this.itemInterval;// 下一条目纵坐标
+            }
+            this.addItemNode(i, y);
+            this.updateContentHeigh(this.itemArr[i].height - y);
+        }
+    }
+    /**
+     * 添加条目节点
+     * @param i 编号
+     * @param y 坐标y
+     */
+    private addItemNode(i: number, y: number) {
+        let pfbType = this.itemData[i].pfbType || 0;
+        let item = cc.instantiate(this.itemNode[pfbType]);
+        item.parent = this.content;
+        item.pfbType = pfbType;
+        item.index = i;
+        if (i === 0) {
+            item.y = 0;
+        } else {
+            item.y = y;
+        }
+        item.x = 0;
+        //对item赋值
+        let comp_script = item.getComponent(item.name);
+        comp_script && comp_script.setItemData(this.itemData[i], this);
+        this.itemArr.push(item);
+        // cc.log('生成itemNode' + i);
+    }
+    /**
+     * 更新centent高
+     * @param num  
+     */
+    private updateContentHeigh(num: number) {
+        this.content.height = num > this.layerHeight ? num : this.layerHeight;
+        // cc.log('滚动条高度:', this.content.height);
+    }
+    /**
+     * 触摸滚动条的函数回调
+     * @param event 
+     * @param eventType 
+     * @returns 
+     */
+    private callback(event, eventType) {
+        // cc.log(event && event.type || eventType)
+        if (this.content.height > this.layerHeight) {
+            let firstItemPos = this.scrollView.getScrollOffset().y;
+            let lastItemPos = firstItemPos + this.layerHeight;
+            if (firstItemPos < 0) return;
+            if (this.initItemData) {
+                // cc.log('111:%o', this.itemPosMap)
+                this.initItemData = false;
+                this.updateFirstItemIndex();
+                this.itemCanMoveDown = true;
+                this.updateLastItemIndex();
+                this.itemCanMoveDown = false;
+            }
+
+            //超出边界直接返回.
+            //滚动条向上滑动可能会触发的函数
+            if (firstItemPos > this.firstItemData.endPos) {
+                if (this.lastItemIndex + 1 < this.itemData.length) {
+                    this.updateFirstItemIndex();
+                }
+                this.count++;
+            }
+            if (lastItemPos > this.lastItemData.endPos) {
+                if (this.lastItemIndex + 1 < this.itemData.length) {
+                    this.itemCanMoveDown = true;
+                    this.updateLastItemIndex();
+                    this.itemCanMoveDown = false;
+                }
+            }
+            //滚动条向下滑动可能会触发的函数
+            if (lastItemPos < this.lastItemData.startPos) {
+                this.updateLastItemIndex();
+                this.count--;
+            }
+            if (firstItemPos < this.firstItemData.startPos) {
+                this.itemCanMoveUp = true;
+                this.updateFirstItemIndex();
+                this.itemCanMoveUp = false;
+            }
+
+        }
+
+    }
+
+    private updateFirstItemIndex() {
+        let num = this.firstItemIndex;
+        if (this.itemCanMoveUp && num > this.getItemIndex()[0] && num > 0) {
+            this.itemMoveUp(this.firstItemIndex - 1);
+        }
+        this.updateItemIndex();
+    }
+    private updateLastItemIndex() {
+        let num = this.lastItemIndex;
+        if (this.itemCanMoveDown && num < this.getItemIndex()[1] && num + 1 < this.itemData.length) {
+            this.itemMoveDown(this.lastItemIndex + 1);
+        }
+        this.updateItemIndex();
+    }
+
+    private updateItemIndex() {
+        //cc.log(this.firstItemIndex, this.lastItemIndex, this.itemArr, this.itemData)
+    }
+    /**
+     * 得到滚动条此时状态下应有的itemNode元素,包括滚动条上方一个,滚动条下方一个
+     * @returns 
+     */
+    private getItemIndex() {
+        let firstItemPos = this.scrollView.getScrollOffset().y;
+        let lastItemPos = firstItemPos + this.layerHeight;
+        let arr = [];
+        this.itemPosMap.forEach((value, key) => {
+            let status1 = value.startPos <= firstItemPos && value.endPos > firstItemPos;
+            let status2 = value.startPos >= firstItemPos && value.endPos < lastItemPos;
+            let status3 = value.startPos <= lastItemPos && value.endPos > lastItemPos;
+            if (status1) {
+                this.firstItemData.startPos = value.startPos;
+                this.firstItemData.endPos = value.endPos;
+                this.firstItemIndex = key;
+                arr.push(key);
+            }
+            if (status3) {
+                this.lastItemData.startPos = value.startPos;
+                this.lastItemData.endPos = value.endPos;
+                this.lastItemIndex = key;
+                arr.push(key);
+            }
+        })
+        return arr;
+    }
+    /**
+     * 滚动到顶部【滚动条顺序是从上到下开始遍历】
+     * @param num 
+     * @returns 
+     */
+    private itemMoveUp(num: number) {
+        if (num < 0 || this.lastItemIndex + 1 < num || num + 1 > this.itemData.length) {
+            return;
+        }
+        if (!this.hasItem(num)) {
+            this.itemMove(num, -this.itemPosMap.get(num).startPos);
+        }
+        num++;
+        return this.itemMoveUp(num);
+
+    }
+    /** 滚动到底部 */
+    private itemMoveDown(num: number) {
+        if (num < 0 || this.firstItemIndex - 1 > num || num + 1 > this.itemData.length) {
+            return;
+        }
+        if (!this.hasItem(num)) {
+            this.itemMove(num, -this.itemPosMap.get(num).startPos);
+        }
+        num--;
+        return this.itemMoveDown(num);
+
+    }
+    /**
+     * 判断指定index位置是否存在itemNode
+     * @param index 节点下标
+     * @returns 
+     */
+    private hasItem(index: number) {
+        for (let i = 0; i < this.itemArr.length; i++) {
+            if (this.itemArr[i].index === index) {
+                return true;
+            }
+        }
+        return false;
+    }
+    /**
+     * 移动条目
+     * 逻辑判断,第一种情况,修改itemArr数组的某个对象,第二种情况实例化一个新itemNode
+     * @param index 条目节点标记
+     * @param y 
+     * @returns 
+     */
+    private itemMove(index: number, y: number) {
+        for (let i = 0; i < this.itemArr.length; i++) {
+            //index存在-1的情况,类似于在缓存池里的item.
+            let status1 = this.itemArr[i].index < this.firstItemIndex - 1 ? true : false;
+            let status2 = this.itemArr[i].index > this.lastItemIndex + 1 ? true : false;
+            let status3 = this.itemArr[i].pfbType === this.itemData[index].pfbType;
+            //cc.log('item的索引', this.firstItemIndex, this.lastItemIndex)
+            if (status1 && status3 || status2 && status3) {
+                //cc.log(i, index, this.itemArr, this.content.height);
+                //给item赋值还有设置位置
+                this.itemArr[i].index = index;
+                this.itemArr[i].y = y;
+                let comp_script = this.itemArr[i].getComponent(this.itemArr[i].name);
+                comp_script && comp_script.setItemData(this.itemData[index], this);
+                return;
+            }
+        }
+        this.addItemNode(index, y);
+    }
+    /**
+     * 得到相关位置的排序index
+     * 方法待验证,可能有问题
+     * @param pos 坐标
+     * @returns 
+     */
+    public getPosIndex(pos: cc.Vec2) {
+        for (let [key, value] of this.itemPosMap.entries()) {
+            if (value.endPos > pos && value.startPos <= pos) {
+                return key;
+            }
+        }
+    }
+
+    /**
+     * 添加条目
+     * @param obj 数据对象
+     * @returns 
+     */
+    public addItem(obj: any) {
+        this.itemData.push(obj);
+        this.getItemPos(this.itemData.length - 1);
+        let endPos = this.itemPosMap.get(this.itemData.length - 1).endPos;
+        if (endPos - this.layerHeight > 0) {
+            let startPos = endPos - this.layerHeight;
+            //得到当前的firstItemIndex;
+            for (let i = this.itemData.length - 1; i >= 0; i--) {
+                if (this.itemPosMap.get(i).endPos > startPos && this.itemPosMap.get(i).startPos <= startPos) {
+                    this.firstItemIndex = i;
+                }
+            }
+            this.scrollView.scrollToBottom();
+            this.lastItemIndex = this.itemData.length - 1;
+            let num = this.firstItemIndex - 1 > 0 ? (this.firstItemIndex - 1) : 0;
+            this.itemMoveUp(num);
+            return true;
+        } else {
+            this.firstItemIndex = 0;
+            this.lastItemIndex = this.itemData.length - 1;
+            this.itemMoveUp(this.firstItemIndex);
+            return false;
+        }
+
+    }
+    /**
+     * 清理条目
+     */
+    public clearItem() {
+        this.itemData = [];
+        this.itemPosMap.clear();
+        this.scrollView.scrollToTop();
+        this.content.height = 0;
+        for (let i in this.itemArr) {
+            // this.itemArr[i].index = -1;
+            // this.itemArr[i].y = 3000;
+            this.itemArr[i].destroy();
+        }
+    }
+    /**
+     * 删除指定条目
+     * @param i 节点.index
+     * @returns 
+     */
+    public deleteItem(i: number) {
+        this.itemData.splice(i, 1);
+        const item_data_count = this.itemData.length;
+        if (item_data_count <= 0) return;
+        this.getItemPos(item_data_count - 1);
+
+        //改变this.itemArr的内容
+        for (let j = 0; j < this.itemArr.length; j++) {
+            if (this.itemArr[j].index === i) {
+                this.itemArr[j].index = -1;
+                this.itemArr[j].y = 3000;
+            }
+            if (this.itemArr[j].index > i) {
+                let num = this.itemArr[j].index;
+                this.itemArr[j].y = -this.itemPosMap.get(num - 1).startPos;
+                this.itemArr[j].index = num - 1;
+            }
+        }
+        this.updateContentHeigh(this.itemPosMap.get(this.itemData.length - 1).endPos);
+        if (!this.lastItemIndex) this.getItemIndex();// 初始化时没有初始化此值,会导致为0.不能创建新的item,此处校验
+        this.itemMoveUp(this.firstItemIndex);//
+    }
+    /**
+     * 重置指定item
+     * @param index 节点下标
+     * @param item_data 新的数据
+     */
+    public resetItemData(index: number, item_data?: any) {
+        for (let i = 0; i < this.itemArr.length; i++) {
+            if (this.itemArr[i].index === index) {
+                if (item_data) this.itemData[i] = item_data;
+                let comp_script = this.itemArr[i].getComponent(this.itemArr[i].name);
+                comp_script && comp_script.setItemData(this.itemData[index], this);
+                break;
+            }
+        }
+    }
+
+    private lastResetItemInfoHeight: any = null;
+    private lastResetItemIndex: any = null;
+    /**
+     * 重置条目大小
+     * @param index 节点下标
+     * @param infoHeight 新的高度
+     */
+    public resetItemSize(index: number, infoHeight: number) {
+        let func = (function (index, infoHeight) {
+
+            for (let i = 0; i < this.itemArr.length; i++) {
+                if (this.itemArr[i].index > index) {
+                    this.itemArr[i].y -= infoHeight;
+                }
+            }
+
+            for (let [key, value] of this.itemPosMap.entries()) {
+                if (key === index) {
+                    value.endPos += infoHeight;
+                }
+                if (key > index) {
+                    value.endPos += infoHeight;
+                    value.startPos += infoHeight;
+                }
+            }
+            this.lastResetItemInfoHeight = infoHeight;
+            this.lastResetItemIndex = index;
+        }).bind(this);
+        if (this.lastResetItemIndex !== null && this.lastResetItemInfoHeight) {
+            if (this.lastResetItemIndex === index) {
+                func(this.lastResetItemIndex, -this.lastResetItemInfoHeight);
+                this.lastResetItemIndex = null;
+                this.lastResetItemInfoHeight = 0;
+            } else {
+                func(this.lastResetItemIndex, -this.lastResetItemInfoHeight);
+
+                func(index, infoHeight);
+            }
+        } else {
+            func(index, infoHeight);
+        }
+        this.itemMoveUp(this.firstItemIndex);
+        this.updateContentHeigh(this.itemPosMap.get(this.itemData.length - 1).endPos);
+    }
+
+
+}

+ 9 - 0
mk_framework/assets/script/game/component/TableViewUtils.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.0.8",
+  "uuid": "9a31dad7-33ef-4d08-a624-17d0fbf33945",
+  "isPlugin": false,
+  "loadPluginInWeb": true,
+  "loadPluginInNative": true,
+  "loadPluginInEditor": false,
+  "subMetas": {}
+}

+ 12 - 0
mk_framework/assets/script/game/component/tween.meta

@@ -0,0 +1,12 @@
+{
+  "ver": "1.1.2",
+  "uuid": "0a129a35-b5bd-40c6-b7bc-987ad03bb1fc",
+  "isBundle": false,
+  "bundleName": "",
+  "priority": 1,
+  "compressionType": {},
+  "optimizeHotUpdate": {},
+  "inlineSpriteFrames": {},
+  "isRemoteBundle": {},
+  "subMetas": {}
+}

+ 140 - 0
mk_framework/assets/script/game/component/tween/FlyNode.ts

@@ -0,0 +1,140 @@
+
+const { ccclass, property } = cc._decorator;
+/**
+ * 飞item组件
+ * - item使用容器存储,可复用,组件的节点被销毁时,销毁所有item
+ * - 流程:
+ * - 把节点在范围内随机展开,移动到指定位置
+ * - 数值优先使用通过事件传递的值,没有则使用装饰器上的默认值
+ * @author 薛鸿潇
+ */
+@ccclass
+export default class FlyNode extends cc.Component {
+
+    @property({ displayName: '起始阶段消耗时间' })
+    private start_move_time: number = 0.1;
+    @property({ displayName: '结束阶段消耗时间' })
+    private end_move_time: number = 0.3;
+    // @property({ displayName: 'item缩放比率' })
+    // private item_scale: number = 0.2;
+    @property({ displayName: 'item默认数量' })
+    private item_count: number = 10;
+    /** 组件数据 */
+    private fly_data = {
+        /** 节点的父节点 */
+        node_p: null,
+        /** 节点数量 */
+        item_count: this.item_count,
+        /** 节点大小 */
+        item_scale: [0.3, 1],
+        /** 节点纹理 */
+        item_sf: null,
+        /** 起始位置 */
+        start_pos: cc.Vec2.ZERO,
+        /** 结束位置 */
+        end_pos: cc.Vec2.ZERO,
+        /** 完成回调 */
+        complete_call: null,
+    }
+    /** item父节点 */
+    private node_fly: cc.Node = null;
+    /** item容器 */
+    private arr_item: Array<cc.Node> = [];
+    onLoad() {
+        this.node.on('ui-fly-node', this.getNodeProgress, this);
+    }
+    /** 加载 小号图标组 
+     * - 0.8, 1.2范围内随机大小
+     * - -45, 45范围内随机角度
+    */
+    private getNodeProgress(_fly_data: any) {
+        // 数据整理node_p, item_sf: cc.SpriteFrame, item_count: number, start_pos: cc.Vec2 = cc.Vec2.ZERO, item_scale: number = 1
+        // this.fly_data = _fly_data;
+        if (_fly_data.node_p) this.fly_data.node_p = _fly_data.node_p;
+        if (_fly_data.item_count) this.fly_data.item_count = _fly_data.item_count;
+        if (_fly_data.item_scale) this.fly_data.item_scale = _fly_data.item_scale;
+        if (_fly_data.item_sf) this.fly_data.item_sf = _fly_data.item_sf;
+        if (_fly_data.start_pos) this.fly_data.start_pos = _fly_data.start_pos;
+        if (_fly_data.end_pos) this.fly_data.end_pos = _fly_data.end_pos;
+        if (_fly_data.complete_call) this.fly_data.complete_call = _fly_data.complete_call;
+        let node_p = this.fly_data.node_p;
+        const item_count = this.fly_data.item_count;
+        const item_scale = this.fly_data.item_scale;
+        let item_sf = this.fly_data.item_sf;
+        let start_pos = this.fly_data.start_pos;
+        // 创建节点
+        if (!this.node_fly) {
+            this.node_fly = new cc.Node('node_fly');
+            this.node_fly.parent = node_p;
+        }
+        for (let i = 0; i < item_count; i++) {
+            let node_item = this.arr_item.shift();
+            if (!node_item || (node_item && node_item.parent)) {
+                // 无item 或者item存在,但有父节点【表示正在运行中】
+                node_item = new cc.Node('node_item');
+                this.arr_item.push(node_item);
+            }
+            node_item.stopAllActions();
+            node_item.parent = this.node_fly;
+            node_item.setPosition(start_pos);
+            // // 创建位置
+            node_item.angle = this.Range(-45, 45, true);
+            let new_scale = this.Range(item_scale[0], item_scale[1], false);
+            node_item.scale = new_scale;
+            // 偏移位置计算-新
+            let dir = cc.v2(this.Range(-1, 1, false), this.Range(-1, 1, false));
+            dir = dir.normalize();
+            let deltaInit = cc.v2(this.Range(10, 80, false) * dir.x, this.Range(10, 80, false) * dir.y);
+            let initPos = start_pos.add(deltaInit); Range
+            // node_item.setPosition(initPos);
+
+            // 纹理
+            let spr_item = node_item.getComponent(cc.Sprite) || node_item.addComponent(cc.Sprite);
+            spr_item.spriteFrame = item_sf;
+            // 执行动画
+            this.movePos(node_item, initPos);
+        }
+    }
+    /** 移动到起始位置 */
+    private movePos(node_item, initPos) {
+        let new_start_move_time = this.Range(0.3, 1, false);
+        //this.start_move_time
+        let start_pos = cc.tween().to(new_start_move_time, { position: new cc.Vec2(initPos) }, { easing: 'sineOut' });
+        let dt1 = cc.tween().delay(this.start_move_time * 1.5);
+        // this.end_move_time
+        // 结束阶段效果
+        let end_pos = cc.tween().to(this.end_move_time, { position: this.fly_data.end_pos }, { easing: 'sineOut' })
+        let end_scale = cc.tween().to(this.end_move_time, { scale: 1 });
+        let end_angle = cc.tween().to(this.end_move_time, { angle: 0 });
+        let parallel1 = cc.tween().parallel(end_pos, end_scale, end_angle);
+        let call1 = cc.tween().call(() => {
+            this.fly_data.complete_call && this.fly_data.complete_call();
+            this.fly_data.complete_call = null;
+            this.arr_item.push(node_item);
+            node_item.removeFromParent();
+        });
+        cc.tween(node_item).then(start_pos).then(dt1).then(parallel1).then(call1).start();
+    }
+    private Range(min: number, max: number, isInt: boolean = true): number {
+        //return min + Math.ceil(Math.random() * 1000) % (max - min);         //不包含最大值
+        let del: number = max - min;
+        if (del > 0) {
+            let val: number = min + Math.random() * 0xffffff % del
+            if (isInt) {
+                return Math.floor(val);
+            }
+            return val
+        }
+        return min;
+    }
+    onDestroy() {
+        const item_count = this.arr_item.length
+        for (let i = 0; i < item_count; i++) {
+            if (cc.isValid(this.arr_item[0])) {
+                this.arr_item[0].destroy();
+                this.arr_item.shift();
+            }
+        }
+    }
+    // update (dt) {}
+}

+ 9 - 0
mk_framework/assets/script/game/component/tween/FlyNode.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.0.8",
+  "uuid": "fa8815e2-92ca-4897-bb17-ed809348e082",
+  "isPlugin": false,
+  "loadPluginInWeb": true,
+  "loadPluginInNative": true,
+  "loadPluginInEditor": false,
+  "subMetas": {}
+}

+ 45 - 0
mk_framework/assets/script/game/component/tween/TweenEasing.ts

@@ -0,0 +1,45 @@
+export enum TweenEasing {
+    linear,
+    smooth,
+    fade,
+    quadIn,
+    quadOut,
+    quadInOut,
+    quadOutIn,
+    cubicIn,
+    cubicOut,
+    cubicInOut,
+    cubicOutIn,
+    quartIn,
+    quartOut,
+    quartInOut,
+    quartOutIn,
+    quintIn,
+    quintOut,
+    quintInOut,
+    quintOutIn,
+    sineIn,
+    sineOut,
+    sineInOut,
+    sineOutIn,
+    expoIn,
+    expoOut,
+    expoInOut,
+    expoOutIn,
+    circIn,
+    circOut,
+    circInOut,
+    circOutIn,
+    elasticIn,
+    elasticOut,
+    elasticInOut,
+    elasticOutIn,
+    backIn,
+    backOut,
+    backInOut,
+    backOutIn,
+    bounceIn,
+    bounceOut,
+    bounceInOut,
+    bounceOutIn
+}

+ 9 - 0
mk_framework/assets/script/game/component/tween/TweenEasing.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.0.8",
+  "uuid": "7d9391f6-5e6e-4aef-9213-43dc2ad2d153",
+  "isPlugin": false,
+  "loadPluginInWeb": true,
+  "loadPluginInNative": true,
+  "loadPluginInEditor": false,
+  "subMetas": {}
+}

+ 103 - 0
mk_framework/assets/script/game/component/tween/UIOpenAndClose.ts

@@ -0,0 +1,103 @@
+
+const { ccclass, property } = cc._decorator;
+
+enum EffectType {
+    None = 0,   //无
+    Scale = 1,  //缩放
+    ScaleToPoint = 2,    //缩放到某点
+    CenterUnfolding = 3,   //从中部展开
+}
+/**
+ * 界面打开 和 关闭效果
+ * 组件请挂载到对应ui界面上
+ * @author 薛鸿潇
+ */
+@ccclass
+export default class UIOpenAndClose extends cc.Component {
+
+    @property({ type: cc.Node, displayName: '动画节点' })
+    private effect_par: cc.Node = null
+
+    @property({ type: cc.Enum(EffectType), displayName: '打开动画节点效果', visible() { return this.effect_par } })
+    private open_effect: EffectType = EffectType.None;
+
+    @property({ type: cc.Enum(EffectType), displayName: '关闭动画节点效果', visible() { return this.effect_par } })
+    private close_effect: EffectType = EffectType.None;
+
+    @property({ displayName: '关闭节点缩小到此节点坐标', visible() { return this.close_effect == EffectType.ScaleToPoint } })
+    private close_toPoint: string = "";
+
+    @property({ displayName: '激活组件时播放' })
+    public playOnLoad: boolean = false;
+    onLoad() {
+        this.node.on('ui-close', this.hideEffect, this);// 界面关闭事件
+    }
+
+    start() {
+        this.playOnLoad && this.showEffect();
+    }
+
+    /** 界面打开效果 */
+    private showEffect() {
+        if (this.effect_par) {
+            switch (this.open_effect) {
+                case EffectType.None:
+                    break;
+                case EffectType.Scale:
+                    this.effect_par.scale = 0;
+                    cc.tween(this.effect_par)
+                        .to(0.15, { scale: 1 })
+                        .start()
+                    break;
+                case EffectType.CenterUnfolding:
+                    this.effect_par.scaleX = 0;
+                    cc.tween(this.effect_par)
+                        .to(0.15, { scaleX: 1 })
+                        .start()
+                    break;
+            }
+        }
+    }
+
+    /** 关闭打开效果 */
+    private hideEffect(cb: Function = null) {
+        if (this.effect_par) {
+            switch (this.close_effect) {
+                case EffectType.None:
+                    cb && cb();
+                    break;
+                case EffectType.Scale:
+                    cc.tween(this.effect_par)
+                        .to(0.15, { scale: 0 })
+                        .call(() => {
+                            cb && cb();
+                        })
+                        .start()
+                    break;
+                case EffectType.ScaleToPoint:
+                    if (this.close_toPoint == "") {
+                        console.error('需要配置缩小到终点节点路径')
+                    } else {
+                        let path = this.close_toPoint;
+                        let node = cc.find(path);
+                        if (node) {
+                            let world_pos = node.parent.convertToWorldSpaceAR(node.getPosition());
+                            let node_pos = this.effect_par.parent.convertToNodeSpaceAR(world_pos);
+                            cc.tween(this.effect_par)
+                                .to(0.3, { scale: 0, position: new cc.Vec3(node_pos.x, node_pos.y, 0) })
+                                .call(() => {
+                                    cb && cb();
+                                })
+                                .start()
+                        } else {
+                            cb && cb();
+                            cc.error('关闭时动画节点未找到,请检查!!!!!')
+                        }
+                    }
+                    break;
+            }
+        } else {
+            cb && cb();
+        }
+    }
+}

+ 9 - 0
mk_framework/assets/script/game/component/tween/UIOpenAndClose.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.0.8",
+  "uuid": "ac053cf0-a9a4-43e8-8266-4e541699e7d9",
+  "isPlugin": false,
+  "loadPluginInWeb": true,
+  "loadPluginInNative": true,
+  "loadPluginInEditor": false,
+  "subMetas": {}
+}

+ 153 - 0
mk_framework/assets/script/game/component/tween/UITween.ts

@@ -0,0 +1,153 @@
+
+import { TweenEasing } from "./TweenEasing";
+const { ccclass, property, executeInEditMode, playOnFocus } = cc._decorator;
+/**
+ * 缓动数据
+ * @author 
+ */
+@ccclass("PropsData")
+export class PropsData {
+    @property({ tooltip: "坐标" })
+    public position: cc.Vec2 = cc.Vec2.ZERO;
+    @property({ tooltip: "旋转" })
+    public angle: number = 0;
+    @property({ tooltip: "缩放" })
+    public scale: number = 1;
+}
+/**
+ * 缓动数据
+ * @author 
+ */
+@ccclass//("TweenData")
+export class TweenData {
+    // @property({ tooltip: "属性数据", type: PropsData })
+    // public propsData: PropsData = new PropsData();
+    @property({ tooltip: "坐标" })
+    public position: cc.Vec2 = cc.Vec2.ZERO;
+    @property({ tooltip: "旋转" })
+    public angle: number = 0;
+    @property({ tooltip: "缩放" })
+    public scale: number = 1;
+
+    @property({ tooltip: "持续时间,单位为秒" })
+    public duration: number = 0.3;
+    @property({ tooltip: "延迟时间,单位为秒" })
+    public delay: number = 0;
+    @property({ tooltip: "是否为差值" })
+    public isOffset: boolean = false;
+    @property({ tooltip: "当缓动动作完成时触发", type: cc.Component.EventHandler })
+    public onComplete: cc.Component.EventHandler = null;
+    @property({ tooltip: "当缓动动作启动时触发", type: cc.Component.EventHandler })
+    public onStart: cc.Component.EventHandler = null;
+    @property({ tooltip: "当缓动动作更新时触发", type: cc.Component.EventHandler })
+    public onUpdate: cc.Component.EventHandler = null;
+}
+/**
+ * 缓动组件,目前只支持节点的三个基础属性
+ * 目前改动类后会导致编辑器内的属性记录丢失,请不要使用
+ * @author 
+ */
+@ccclass//('UITween')
+@executeInEditMode()
+@playOnFocus()
+export class UITween extends cc.Component {
+    @property
+    private err = '属性记录会丢失,请不要使用'
+    @property({ tooltip: "节点初始属性", type: PropsData })
+    public fromData: PropsData = new PropsData();
+    @property({ tooltip: "节点目标属性", type: TweenData, serializable: false })//, serializable: false
+    public targetData: TweenData[] = [];//new TweenData()
+    @property({ tooltip: "要播放缓动动画的目标,不填则是当前节点", type: cc.Node })
+    public target: cc.Node = null;
+    @property({ tooltip: "缓动效果", type: cc.Enum(TweenEasing) })
+    public tweenEasing: TweenEasing = TweenEasing.linear;
+    @property({ tooltip: "执行次数,为0则一直重复执行" })
+    public repeat: number = 1;
+    @property({ tooltip: "单次缓动序列执行完毕时触发", type: cc.Component.EventHandler })
+    public onOneRepeatComplete: cc.Component.EventHandler = null;
+    @property({ tooltip: "是否在缓动组件运行时自动播放缓动动画" })
+    public playOnLoad: boolean = false;
+    @property({ displayName: "编辑器预览" })
+    public _playOnEdit: boolean = false;
+    @property({ displayName: "编辑器预览" })//, editorOnly: true
+    get playOnEdit(): boolean {
+        if (this._playOnEdit) {
+            this.play()
+        }
+        return this._playOnEdit;
+    }
+    set playOnEdit(v: boolean) {
+        this._playOnEdit = v;
+    }
+    public start() {
+        // if (CC_EDITOR && !this._playOnEdit) return;
+        !this.target && (this.target = this.node);
+        this.target = this.target || this.node;
+        this.currentRepeat = this.repeat;
+        this.playOnLoad && this.play();
+    }
+    private t: cc.Tween<any>;
+    private currentRepeat: number;
+    public play() {
+        if (CC_EDITOR && !this._playOnEdit) return;
+        if (this.repeat == 0) {
+            this.t = cc.tween(this.target);
+            this.readyPlay();
+            this.t = this.t.call(this.play.bind(this));
+            this.t.start();
+        }
+        else if (this.currentRepeat) {
+            this.t = cc.tween(this.target);
+            this.readyPlay();
+            this.t = this.t.call(this.play.bind(this));
+            this.currentRepeat--;
+            this.t.start();
+        }
+        else {
+            this.t = null;
+        }
+    }
+    public readyPlay() {
+        let self = this;
+        if (this.fromData) {
+            this.node.setPosition(this.fromData.position.clone());
+            this.node.scale = this.fromData.scale;
+            this.node.angle = this.fromData.angle;
+        }
+        if (this.targetData.length) {
+            for (let i = 0; i < this.targetData.length; i++) {
+                const targetData = this.targetData[i];
+                if (targetData.delay != 0) {
+                    this.t = this.t.delay(targetData.delay);
+                }
+                let toData = {
+                    position: targetData.position,
+                    scale: targetData.scale,
+                    angle: targetData.angle
+                }
+                if (targetData.isOffset) {
+                    toData.position = this.node.getPosition().add(targetData.position);
+                    toData.scale = this.node.scale + targetData.scale;
+                    toData.angle = this.node.angle + targetData.angle;
+                }
+                this.t = this.t.to(
+                    targetData.duration,
+                    toData,
+                    {
+                        easing: TweenEasing[this.tweenEasing] as any,
+                        onComplete: (target: cc.Node) => {
+                            targetData.onComplete && targetData.onComplete.emit([target]);
+                        },
+                        onStart: (target: cc.Node) => { targetData.onStart && targetData.onStart.emit([target]) },
+                        onUpdate: (target: cc.Node, ratio: number) => { targetData.onUpdate && targetData.onUpdate.emit([target, ratio]) }
+                    }
+                );
+            }
+        }
+        if (this.onOneRepeatComplete) {
+            this.t = this.t.call(() => {
+                self.onOneRepeatComplete.emit([this, this.t])
+            });
+        }
+    }
+}

+ 9 - 0
mk_framework/assets/script/game/component/tween/UITween.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.0.8",
+  "uuid": "a20d913e-37b1-4188-857b-cf5c21b93e89",
+  "isPlugin": false,
+  "loadPluginInWeb": true,
+  "loadPluginInNative": true,
+  "loadPluginInEditor": false,
+  "subMetas": {}
+}