IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> 【cocos creator】滑动列表复用,减少drawcall(TS) -> 正文阅读

[游戏开发]【cocos creator】滑动列表复用,减少drawcall(TS)

ScrollViewCtrl:

import NodePool from "./NodePool";

const { ccclass, property } = cc._decorator;

@ccclass
export default class ScrollViewCtrl extends cc.Component {

    @property(cc.Prefab)
    itemPrefab: cc.Prefab = null;

    bindIndexList: {};
    scrollView: cc.ScrollView = null;
    content: cc.Node = null;
    view: cc.Node = null;
    layout: cc.Layout = null;
    itemName: string = null;
    mat4: cc.Mat4 = null;
    isInit: boolean = null;
    data: any[] = null;
    callbackList: any[] = null;
    extData: any = null;
    firstX: number = null;
    firstY: number = null;
    itemCache: any[] = null;
    itemBuffer: any = null;
    _tmpV2: any = null;
    viewRect: any = null;

    start() {
        this.initOnce();
    }

    initOnce() {
        this.bindIndexList = {};    /**记录哪个index需要绑定Item */
        this.scrollView = this.node.getComponent(cc.ScrollView);
        this.content = this.scrollView.content
        this.view = this.content.parent;
        this.layout = this.content.getComponent(cc.Layout);
        this.itemName = this.itemPrefab.name;
        this.mat4 = cc.mat4();
        this.initOnce = function () { }
    }

    onDestroy() {
        this.content.off(cc.Node.EventType.POSITION_CHANGED, this.scrollEvent, this);
        this.recycle();
    }

    /**注册滚动事件 */
    registerScrollEvent(handler, target) {
        if (!handler.name) {
            return;
        }
        var scrollView = this.node.getComponent(cc.ScrollView);
        var eventHandler = new cc.Component.EventHandler();
        eventHandler.target = target.node;
        eventHandler.component = cc.js.getClassName(target);
        eventHandler.handler = handler.name;
        var index = scrollView.scrollEvents.length;
        scrollView.scrollEvents[index] = eventHandler;
    }

    /**
     * 初始化数据和表现
     * @param {Array} data 所有的列表数据组成的数组
     * @param {any} param 要传递到item的额外参数
     */
    init(data, param?) {
        this.initOnce();
        if (!Array.isArray(data)) {
            console.error("传进来的数据不为数组!");
            return;
        }
        if (!data.length) {
            this.recycle();
            return;
        }
        param = param || {};
        this.isInit = true;
        this.data = data;
        this.callbackList = [];
        this.extData = param.extData;
        if (data.length == 0) {
            this.content.off(cc.Node.EventType.POSITION_CHANGED, this.scrollEvent, this);
            this.recycle();
            return;
        }
        if (param.onChanged) {
            this.onItemChanged(param.onChanged);
        }
        this.layout.enabled = false;
        this.scrollView.stopAutoScroll();
        if (!NodePool.Instance.hasPool(this.itemName)) {
            NodePool.Instance.initPool(this.itemPrefab);
        }

        /**模板 */
        var template = this.itemPrefab.data;

        var paddingLeft = this.layout.paddingLeft;      /**左边距 */
        var paddingRight = this.layout.paddingRight;    /**右边距 */
        var paddingTop = this.layout.paddingTop;        /**上边距 */
        var paddingBottom = this.layout.paddingBottom;  /**下边距 */
        var spacingX = this.layout.spacingX;            /**水平间隔距离 */
        var spacingY = this.layout.spacingY;            /**垂直间隔距离 */

        let firstX = template.x;
        let firstY = template.y;

        if (this.scrollView.horizontal) {
            firstX = -template.width / 2
            firstX -= paddingLeft;
        }
        if (this.scrollView.vertical) {
            firstY = -template.height / 2;
            firstY -= paddingTop;
        }
        this.firstX = firstX;
        this.firstY = firstY;

        this.itemCache = [];
        this.itemBuffer = this.itemBuffer || [];
        //初始化itemBuffer
        this.itemBuffer.forEach(buffer => {
            buffer.index = -1;
            buffer.item.x = -9999999;
            buffer.item.y = -9999999;
            buffer.item.opacity = 0;
        });

        var initCache = (i) => {
            this.itemCache[i] = this.itemCache[i] || {};
            this.itemCache[i].x = firstX;
            this.itemCache[i].y = firstY;
            this.itemCache[i].width = template.width;
            this.itemCache[i].height = template.height;
            this.itemCache[i].scaleX = template.scaleX;
            this.itemCache[i].scaleY = template.scaleY;
            this.itemCache[i].visible = false;
        }
        initCache(0);
        for (let i = 1; i < this.data.length; i++) {
            initCache(i);
            if (this.scrollView.horizontal) {
                this.itemCache[i].x = this.itemCache[i - 1].x - (this.itemCache[i - 1].width / 2 + this.itemCache[i].width / 2 + spacingX);
            }
            if (this.scrollView.vertical) {
                this.itemCache[i].y = this.itemCache[i - 1].y - (this.itemCache[i - 1].height / 2 + this.itemCache[i].height / 2 + spacingY);
            }
        }
        let lastItem = this.itemCache[this.itemCache.length - 1];
        if (this.scrollView.horizontal) {
            this.content.width = Math.abs(lastItem.x + lastItem.width / 2 + paddingRight);
        }
        if (this.scrollView.vertical) {
            this.content.height = Math.abs(lastItem.y - lastItem.height / 2 - paddingBottom);
        }
        this.content.on(cc.Node.EventType.POSITION_CHANGED, this.scrollEvent, this);
        this.scheduleOnce(this.updateListView);
    }

    updateItemView(item, index) {
        var js = this.getScript(item);
        if (js && js.initData) {
            js.initData(this.data[index], index, this.extData);
            js.updateView && js.updateView();
        }
    }
    //创建item预制体
    getItem() {
        let item = NodePool.Instance.getNode(this.itemName);
        item.x = this.firstX;
        item.y = this.firstY;
        let data = {
            item: item,
            index: -1,
        }
        //将创建的item预制体放入data数据结构,加入itemBuffer,方便在更新视图时操作
        this.itemBuffer.push(data);
        item.on(cc.Node.EventType.SIZE_CHANGED, this.onItemSizeChanged.bind(this, item), this);
        item.on(cc.Node.EventType.SCALE_CHANGED, this.onItemSizeChanged.bind(this, item), this);
        return data;
    }

    scrollEvent() {
        if (!this.content || !this.isInit) return;
        this.updateListView();
    }
    /**
     * 在显示区域的itemx x,y坐标相对于content的位置是固定的,这部分数据存于itemCache. 监听content位置改变时,
     * 遍历所有itemCache,根据itemCache中每个item相对于content的位置计算出每个item相对于世界坐标的rect区域,与当前mask相对世界坐标
     * 的rect区域判断是否相交,若相交则该item显示,根据itemCache里面存的坐标给item设置位置,层级和父节点,不显示的item给
     * 移到下方
     */
    updateListView() {
        if (!this.itemCache) {
            return;
        }
        var attach = (buffer, i) => {
            buffer.index = i;
            buffer.item.x = this.itemCache[i].x;
            buffer.item.y = this.itemCache[i].y;
            buffer.item.scaleX = this.itemCache[i].scaleX;
            buffer.item.scaleY = this.itemCache[i].scaleY;
            buffer.item.opacity = 255;
            if (this.scrollView.horizontal) {
                buffer.item.width = this.itemCache[i].width;
            }
            if (this.scrollView.vertical) {
                buffer.item.height = this.itemCache[i].height;
            }
            buffer.item.parent = this.content;
            this.updateItemView(buffer.item, i);
        }
        for (var i = 0; i < this.itemCache.length; i++) {
            var cache = this.itemCache[i];
            var visible = this.isItemInView(i);
            var buffer = this.itemBuffer.find((v) => {
                return v.index == i;
            });
            //创建当前滑动区域内可见的item预制体
            if (visible) {
                //当前下标有绑定的item,直接使用该item
                if (this.bindIndexList[i]) {
                    buffer = this.itemBuffer.find((v) => {
                        return v.bindIndex == i;
                    });
                }
                //创建item
                if (!buffer) {
                    //如果itemBuffer里面有初始化缓存的预制体,则使用之
                    buffer = this.itemBuffer.find((v) => {
                        return v.index == -1 && v.bindIndex == undefined;
                    });
                    //没有缓存的话,则使用getIItem创建后,并放入itemBuffer方便后面使用
                    buffer = buffer || this.getItem();
                }
                //根据item绑定的序列号重新设置层级以及位置
                if (buffer.index != i) {
                    attach(buffer, i);
                }
            } else if (buffer) { //将不可见item移到非常靠下的位置,使content大小足够大,能够向下滑动
                buffer.index = -1;
                buffer.item.x = -9999999;
                buffer.item.y = -9999999;
                buffer.item.opacity = 0;  //active会影响layout大小,所以不可见将透明度设置为0
            }
            //执行每个item可见变化的回调,可见->不可见 回调, 不可见->可见回调
            if (cache.visible != visible) {
                this.runItemChangedCallback(i, visible);
            }
            cache.visible = visible;
        }

        /**调整层级 */
        this.itemBuffer.sort((a, b) => {
            if (a.index < 0 || b.index < 0) {
                return 1;
            }
            return a.index - b.index;
        });
        //按照index对item进行排序
        for (var i = 0; i < this.itemBuffer.length; i++) {
            this.itemBuffer[i].item.setSiblingIndex(i);
        }
    }

    /**item是否在可视区域内 */
    isItemInView(index) {
        //当前滑动列表区域的坐标
        this._tmpV2 = this._tmpV2 || cc.v2(0, 0);
        //view为mask裁剪区域
        this.view.getWorldMatrix(this.mat4);
        let scale = this.mat4.m[0];
        let wposx = this.mat4.m[12];
        let wposy = this.mat4.m[13];

        let width1 = this.view.width * scale;
        let height1 = this.view.height * scale;

        let wpos = this.view.convertToWorldSpaceAR(cc.Vec2.ZERO, this._tmpV2);
        //rect比那辆没有初始化或则进行了缩放或则横坐标或纵坐标变动了,则将rect变量重赋值
        if (!this.viewRect || scale != 1 || (this.viewRect.x + width1 / 2) != wposx || (this.viewRect.y + height1 / 2) != wposy) {
            this.viewRect = new cc.Rect(wpos.x - width1 / 2, wpos.y - height1 / 2, width1, height1);
        }
        //获取当前item缓存的数据
        let data = this.itemCache[index];
        //转成世界坐标
        let wpos2 = this.content.convertToWorldSpaceAR(cc.v2(data.x, data.y));
        //获取item的宽高
        let width2 = data.width * data.scaleX;
        let height2 = data.height * data.scaleY;
        let rect = new cc.Rect(wpos2.x - width2 / 2, wpos2.y - height2 / 2, width2, height2);
        //判断两个矩形区域是否相交
        let ret = this.viewRect.intersects(rect);
        return ret;
    }

    onItemSizeChanged(item) {
        if (!this.itemCache) {
            return;
        }
        let data = this.itemBuffer.find((v) => {
            return v.item == item;
        });
        if (data && data.index >= 0) {
            var itemData = this.itemCache[data.index];
            if (this.scrollView.horizontal && itemData.width == item.width && itemData.scaleX == item.scaleX) {
                return;
            }
            if (this.scrollView.vertical && itemData.height == item.height && itemData.scaleY == item.scaleY) {
                return;
            }
            itemData.width = item.width;
            itemData.scaleX = item.scaleX;

            itemData.height = item.height;
            itemData.scaleY = item.scaleY;

            this.updateBuffer();
            this.scheduleOnce(this.updateListView);
        }
    }

    setItemProperty(index, property, value) {
        if (!this.itemCache || !this.itemBuffer) {
            return;
        }
        var buffer = this.itemBuffer.find(v => {
            return v.index == index;
        });
        if (buffer) {
            buffer.item[property] = value;
        } else {
            var itemData = this.itemCache[index];
            itemData[property] = value;
        }
        this.updateBuffer();
        this.scheduleOnce(this.updateListView);
    }
    /**刷新itemCache的缓存里面每个item缓存数据的y坐标,刷新itemBuffer缓存预制体的纵坐标,并更新content的高度
     * 每个显示的item相对于content的位置不变,只在content容器内的item大小变化时,刷新itemCache数据
     **/
    updateBuffer() {
        let lastItem = this.itemCache[this.itemCache.length - 1];
        if (this.scrollView.vertical) {

            this.itemCache[0].y = -this.itemCache[0].height / 2 - this.layout.paddingTop;
            if (this.itemCache[0].scaleY != 1) {
                this.itemCache[0].y = -Math.abs(this.itemCache[0].scaleY * this.itemCache[0].height) / 2 - this.layout.paddingTop;
            }
            this.itemBuffer.find((v) => {
                if (v.index == 0) {
                    v.item.y = this.itemCache[0].y;
                }
            });
            for (var i = 1; i < this.data.length; i++) {
                var data1 = this.itemCache[i - 1];
                var data2 = this.itemCache[i];
                var h1 = data1.height;
                var h2 = data2.height;

                if (data1.scaleY != 1) {
                    h1 = Math.abs(data1.scaleY * data1.height);
                }
                if (data2.scaleY != 1) {
                    h2 = Math.abs(data2.scaleY * data2.height);
                }

                data2.y = data1.y - (h1 / 2 + h2 / 2 + this.layout.spacingY);
                this.itemBuffer.find((v) => {
                    if (v.index == i) {
                        v.item.y = data2.y;
                    }
                });
            }
            var lastRealHeight = lastItem.height / 2;
            if (lastItem.scaleY != 1) {
                lastRealHeight = Math.abs(lastItem.scaleY * lastItem.height) / 2;
            }
            this.content.height = Math.abs(lastItem.y - lastRealHeight - this.layout.paddingBottom);
        }
    }

    /**
     * 滑动到指定Item位置
     * @param {*} index 
     */
    scrollToItem(index, t?, extParams?) {
        if (!this.itemCache || !this.itemCache.length) {
            return;
        }
        if (index < 0) index = 0;
        if (index >= this.itemCache.length) index = this.itemCache.length - 1;

        var cache = this.itemCache[index];
        if (!cache) {
            return;
        }
        extParams = extParams || {};
        t = t || 0;
        if (this.scrollView) {
            var toY;
            if (extParams.customTween) {
                // toY = -(cache.y + Math.abs(cache.height * cache.scaleY) / 2);
                // this.content.runAction(cc.moveTo(t, cc.v2(0, toY)));
            } else {
                toY = -(cache.y + Math.abs(cache.height * cache.scaleY) / 2);
                this.scrollView.scrollToOffset(cc.v2(0, toY), t)
            }
        }
    }

    /**
     * 获取Item
     */
    getItemByIndex(index) {
        if (!this.itemBuffer) {
            return;
        }
        var buffer = this.itemBuffer.find(v => {
            return v.index == index;
        }) || {};
        return buffer.item;
    }

    removeItemByIndex(index) {
        if (!this.itemBuffer) {
            return;
        }
        var idx = this.itemBuffer.findIndex(v => {
            return v.index == index;
        });
        if (idx >= 0) {
            var buffer = this.itemBuffer.splice(idx, 1);
            this.destroyNode(buffer.item);
        }
    }

    /**
     * 下标和Item绑定
     * item只能指定index, item将不可复用
     */
    bindItemWithIndex(item, bindIndex, isBind) {
        if (!this.itemBuffer) {
            return;
        }
        isBind = isBind || true;
        var buffer = this.itemBuffer.find(v => {
            return v.item == item;
        });
        if (buffer) {
            buffer.bindIndex = bindIndex;
            if (isBind) {
                this.bindIndexList[bindIndex] = isBind;
            } else {
                delete this.bindIndexList[bindIndex];
                delete buffer.bindIndex;
            }
        }
    }

    onItemChanged(callback) {
        if (typeof (callback) == "function") {
            this.callbackList.push(callback);
        }
    }

    runItemChangedCallback(index, visible) {
        try {
            for (let i = 0; i < this.callbackList.length; i++) {
                this.callbackList[i](index, visible);
            }
        } catch (e) {
            console.error(e);
        }
    }

    recycle() {
        if (this.itemBuffer) {
            this.itemBuffer.forEach(element => {
                if (element && cc.isValid(element.item)) {
                    element.item.off(cc.Node.EventType.SIZE_CHANGED, this.onItemSizeChanged.bind(this, element.item), this);
                    element.item.off(cc.Node.EventType.SCALE_CHANGED, this.onItemSizeChanged.bind(this, element.item), this);
                    NodePool.Instance.putNode(this.itemName, element.item);
                }
            });
        }
        this.itemCache = null;
        this.itemBuffer = null;
    }

    destroyNode(node) {
        if (!cc.isValid(node)) {
            console.error("Tools: destroyNode error, param is invalid");
            return;
        }
        node.removeFromParent(false);
        node.destroy();
    }
    getScript(node: cc.Node) {
        if (!node) return null;
        //@ts-ignore
        let arr = node._components;
        for (let i = 0; i < arr.length; i++) {
            const element = arr[i];
            if (element && arr[i].hasOwnProperty("_super")) {
                return arr[i];
            }
        }
        return null;
    }
}

NodePool:

import EngineUtil from "../Utils/EngineUtil";

var _nodePool = {};
var _prefabList = {};

export default class NodePool {

    protected static _instance: NodePool = null;
    public static get Instance(): NodePool {
        if (NodePool._instance == null) {
            NodePool._instance = new NodePool();
        }
        return NodePool._instance;
    }

    private _pathList: {};
    /**
     * 添加路径,调用loadPool的时候就不需要传完整路径了
     * @param {*} path 路径数组或者字符串
     */
    addPath(path) {
        this._pathList = this._pathList || {};
        if (!Array.isArray(path)) {
            path = [path];
        }
        for (let i = 0; i < path.length; i++) {
            let name = cc.path.basename(path[i]);
            this._pathList[name] = this._pathList[name] || path[i];
        }
        console.log(this._pathList);
    }

    /**
     * 加载预制,保存到节点池中
     * @param {*} name 预制名字,不需要带路径
     * @param {*} callback 
     */
    loadPool(name, callback) {
        if (!Array.isArray(name)) {
            name = [name];
        }
        var paths = [];
        for (let i = 0; i < name.length; i++) {
            paths.push(this._pathList[name[i]]);
        }
        cc.resources.load(paths, cc.Prefab, (res: any) => {
            if (!Array.isArray(res)) {
                res = [res];
            }
            for (let i = 0; i < res.length; i++) {
                let poolName = res[i].name;
                if (!this.hasPool(poolName)) {
                    this.initPool(poolName, res[i]);
                }
            }
            if (callback) callback();
        });
    }

    /**
     * 初始化节点池
     * @param {*} name 缓存名字,一般和预制体名字一致 
     * @param {*} object 预制对象
     * @param {*} count 缓存个数,默认为1
     */
    initPool(object, count = 1, name = "") {
        if (!name) name = object.name;
        if (!this.hasPool(name)) {
            if (_nodePool[name]) {
                for (const n of _nodePool[name]) {
                    EngineUtil.destroyNode(n);
                }
            }
            _nodePool[name] = [];
            _prefabList[name] = object;
            for (var i = 0; i < count; i++) {
                var node = cc.instantiate(object);
                node.active = false;
                _nodePool[name].push(node);
            }
        }
    }

    /**
     * 是否已经缓存在节点池
     * @param {*} name 预制名字
     */
    hasPool(name) {
        return _prefabList[name] && _prefabList[name].isValid;
    }

    /**
     * 回收节点
     * @param {*} name 预制名字 
     * @param {*} node 预制节点实例
     */
    putNode(name, node) {
        if (!cc.isValid(node)) {
            console.error("putNode: node param is invalid");
            return;
        }
        var pool = _nodePool[name];
        if (!pool) {
            console.error("putNode: pool %s not found", name);
            return;
        }

        if (pool.findIndex((item) => {
            return item == node
        }) >= 0) {
            return;
        }
        node.stopAllActions();
        node.removeFromParent(true);
        node.x = 0;
        node.y = 0;
        node.scale = 1;
        node.opacity = 255;
        node.active = false;
        pool.push(node);
    }

    /**
     * 获取缓存的节点实例
     * @param {*} name 预制名字
     */
    getNode(name) {
        var pool = _nodePool[name];
        if (!pool) {
            console.error("getNode: pool %s not found", name);
            return null;
        }
        var node = pool.length > 0 ? pool.pop() : cc.instantiate(_prefabList[name]);
        node = cc.isValid(node) ? node : cc.instantiate(_prefabList[name]);
        node.active = true;
        node.x = 0;
        node.y = 0;
        // ant.FontTools.updateFont(node);
        return node;
    }

    /**重置节点池 */
    reset() {
        if (!_nodePool) {
            return;
        }
        var list = _nodePool;
        for (var key in list) {
            var pool = list[key];
            while (pool.length > 0) {
                EngineUtil.destroyNode(pool.pop());
            }
        }
    }

    getPool() {
        return _nodePool;
    }

    _test() {
        console.log(_nodePool);
    }

}

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2022-03-06 13:29:43  更:2022-03-06 13:30:25 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/27 16:35:31-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码