一, 场景设计

Cocos Creator 2.x之ScrollView分层渲染_Cocos

1,ScrollViewPrefab:挂载ScrollViewPrefab脚本。

Cocos Creator 2.x之ScrollView分层渲染_CD_02

2,ScrollViewPrefabItem: 挂载ScrollViewPrefabItem脚本。是内容item。

Cocos Creator 2.x之ScrollView分层渲染_Cocos_03

二,传统做法,加入30个item

Cocos Creator 2.x之ScrollView分层渲染_CD_04

三,分层处理, 加入30个item

1, 代码:CommomScrollViewCD

const { ccclass, property } = cc._decorator;

//绘画层
class Draw {
    key: string = ""; //以节点名为标记
    mask: boolean = false; //是否有mask遮盖组件
    layout: boolean = false; //是否有layout组件
    nodes: Array<cc.Node> = []; //绘画节点容器
    localOpacitys: Array<number> = [];
    childrens: Array<Array<cc.Node>> = []; //绘图节点原子节点数据

    next: Draw | null = null;
    prev: Draw | null = null;

    constructor(key: string) {
        this.key = key;
        this.mask = false;
        this.nodes = [];
        this.childrens = [];
    }
}

//绘画层队列
class Queue {
    items: any = {};
    head: Draw | null = null;

    constructor() {
        this.items = {};
        this.head = null;
    }

    get(key: string) {
        return this.items[key];
    }

    set(prev: Draw | null, data: Draw) {
        this.items[data.key] = data;
        if (!this.head) this.head = data;
        else {
            data.next = prev!.next;
            prev!.next = data;
            data.prev = prev;
        }
    }

    clear() {
        Object.keys(this.items).forEach((key) => {
            delete this.items[key];
        });
        this.head = null;
        this.items = {};
    }
}

@ccclass
export default class CommomScrollViewCD extends cc.Component {
    //全局合批队列记录
    private nodes: Array<cc.Node> = [];
    private queues: Array<Queue> = [];
    private aabb0 = cc.rect();
    private aabb1 = cc.rect();
    private worldMat4 = cc.mat4();

    queue: Queue = new Queue(); //优先级分层队列
    children: Array<cc.Node> = []; //记录原节点结构

    @property(cc.Node)
    culling: cc.Node = null;
    //重新刷新list列表
    private updateCallback: (flag: string) => void;
    
    public initUpdateCallback(updateCallback: (flag: string) => void): void{
    	this.updateCallback = updateCallback;
    }

    update(dt: number) {
        this.nodes.push(this.node);
        this.queues.push(this.queue);
    }

    protected onLoad(): void {
        cc.director.on(
            cc.Director.EVENT_BEFORE_DRAW,
            (dt) => {
                //绘画前拦截修改节点结构
                let nodes = this.nodes;
                let queues = this.queues;
                for (let i = 0; i < nodes.length; i++) {
                    let node = nodes[i];
                    if (node.active && node.isValid) {
                        this.changeTree(node, queues[i]);
                    }
                }
            },
            this
        );

        cc.director.on(
            cc.Director.EVENT_AFTER_DRAW,
            (dt) => {
                //绘画结束后恢复节点结构
                let nodes = this.nodes;
                let queues = this.queues;
                for (let i = 0; i < nodes.length; i++) {
                    let node = nodes[i];
                    if (node && node.isValid) {
                        this.resetTree(node, queues[i]);
                    }
                }

                nodes.length = 0;
                queues.length = 0;
            },
            this
        );
    }

    //遍历建立绘图层队,并收集绘画节点
    private getDrawNode(
        prev: Draw | null,
        node: cc.Node,
        queue: Queue,
        active: boolean,
        level: number = 0,
        opacity: number = 1.0
    ) {
        let render: any = node.getComponent(cc.RenderComponent) || node.getComponent(cc.RichText);

        //注意:根据节点的名字进行分层,务必确保名字的唯一性
        let key = node.name; //添加层级前缀加强同名过滤
        if (level == 0) key = "MinesDC"; //自定义收集首节点(允许Item首节点异名)
        let draw = queue.get(key);
        if (!draw) {
            draw = new Draw(key);
            queue.set(prev, draw);
            draw.mask = node.getComponent(cc.Mask) != null;
            draw.layout = node.getComponent(cc.Layout) != null;
        }

        prev = draw;

        if (render) {
            let nodes = draw.nodes;
            let localOpacitys = draw.localOpacitys;

            if (active) {
                //node.active && active
                nodes.push(node); //收集节点
                localOpacitys.push(node.opacity); //保存透明度
                node.opacity = opacity * node.opacity; //设置当前透明度
            }
        }

        opacity = (opacity * node.opacity) / 255.0;

        //遮罩直接打断返回
        if (draw.mask) return prev;
        if (draw.layout)
            //强制新layout世界矩阵
            node["_updateWorldMatrix"]();

        let childs = node.children;
        for (let i = 0; i < childs.length; i++) {
            let isActive = childs[i].active ? active : false;
            prev = this.getDrawNode(prev, childs[i], queue, isActive, level + 1, opacity);
        }

        return prev;
    }

    private getWorldBoundingBox(node: cc.Node, rect: cc.Rect) {
        let _contentSize = node.getContentSize();
        let _anchorPoint = node.getAnchorPoint();

        let width = _contentSize.width;
        let height = _contentSize.height;
        rect.x = -_anchorPoint.x * width;
        rect.y = -_anchorPoint.y * height;
        rect.width = width;
        rect.height = height;

        node.getWorldMatrix(this.worldMat4);
        return rect.transformMat4(rect, this.worldMat4);
    }

    private changeTree(parent: cc.Node, queue: Queue) {
        if (this.culling) {
            this.getWorldBoundingBox(this.culling, this.aabb0);
        }

        //遍历所有绘画节点,按顺序分层
        let nodes = parent.children;
        for (let i = 0; i < nodes.length; i++) {
            let node = nodes[i];
            if (node.activeInHierarchy) {
                //剔除显示范围外的item
                if (this.culling) {
                    this.getWorldBoundingBox(node, this.aabb1);
                    if (!this.aabb0.intersects(this.aabb1)) continue;
                }

                this.getDrawNode(null, node, queue, true);
            }
        }

        //记录item的父节点的子节点结构
        this.children = parent["_children"]; //记录原来节点结构
        let childs: Array<cc.Node> = (parent["_children"] = []); //创建动态分层节点结构

        //拼接动态分层的绘画节点
        let curr = queue.head;
        while (curr) {
            let mask = curr.mask;
            let nodes = curr.nodes;
            let childrens = curr.childrens;
            for (let i = 0; i < nodes.length; i++) {
                childrens[i] = nodes[i]["_children"]; //记录原来节点结构
                !mask && (nodes[i]["_children"] = []); //清空切断下层节点
            }

            //按顺序拼接分层节点
            childs.push(...nodes);
            curr = curr.next;
        }
    }

    private resetTree(parent: cc.Node, queue: Queue) {
        parent["_children"].length = 0; //清空动态分层节点结构
        parent["_children"] = this.children; //恢复原来节点结构
        if (this.children == null || this.children.length <= 0) {
            if (this.updateCallback) {
                this.updateCallback("all_item_cleared");//通知所有的item被清理掉了,如果有误,需要重新渲染
                return;
            }
        }
        //恢复原来节点结构
        let curr = queue.head;
        while (curr) {
            let nodes = curr.nodes;
            let childrens = curr.childrens;
            let localOpacitys = curr.localOpacitys;
            for (let i = 0; i < nodes.length; i++) {
                nodes[i]["_children"] = childrens[i]; 
                nodes[i].opacity = localOpacitys[i];
            }
            childrens.length = 0;
            nodes.length = 0;
            curr = curr.next;
        }

        queue.clear();
    }

    protected onDestroy(): void {
        cc.director.targetOff(this);
        this.updateCallback = null;
    }
}

2,ScrollViewPrefab预制体的content的节点中挂载CommonScrollViewCD脚本。

Cocos Creator 2.x之ScrollView分层渲染_分层_05

3,结果

Cocos Creator 2.x之ScrollView分层渲染_优化_06