creator没有实现原本2d-x有的globalZOrder功能导致的问题

我发现creator没有实现原本2d-x有的globalZOrder功能,这个功能可以让渲染对象脱离UI树的结构,自动管理渲染层级,不受节点逻辑影响,能过实现很好的渲染优化和复杂功能的层级管理,如图四个地图块,每个地图块有四层:
原始情况

globalZOrder处理后的情况

通过这个实现地图分块管理,每个节点上都有不同层级(地表层、建筑层等),同时通过globalZOrder不仅可以优化渲染,同时能够保证地表永远在一个层级,建筑永远在它上面,如果不通过globalZOrder,那么地图块之间是兄弟节点,那么两个节点间必然有高低,一个块里的地表层级会比另一个块里的建筑层级还高,这就不合理了,这就是说的UI树的逻辑层级影响到了渲染层级,之前2d-x3.0开始加了globalZOrder完美的拆分了这两者的耦合,现在没有这功能如果想实现这个效果,所有参与大地图的渲染对象必须都在一个父节点上,然后进行层级排序,安排地图层级以及人物之间的遮挡关系,没办法清晰的用节点进行分块管理,不借用节点对显示对象进行封装管理,最后导致整个世界显示对象管理非常混乱,都混在一个父节点上,位置设置以及其他的一些操作再也不能通过设置包装的节点整体改变,都要挨个设置对象了

7赞

目前creator 的合批渲染机制的确不太合理

确实 现在label打断渲染批次很麻烦

自己修改渲染顺序比较烦

引擎大佬不出来讨论在吗@jare

自问自答,困扰已久,为了解决如题描述问题,通过阅读引擎代码,修改引擎渲染流程,加入2d-x原有的globalZOrder功能,仅需改动少量代码就能实现基本功能,基本思想就是以一个目标节点根节点,设置为group模式,这个节点的子孙节点都进行globalZOrder排序后进行访问,从而实现显示与逻辑解耦,优化了渲染性能,解决类似大地图和背包列表dc优化问题,实现批渲染。目前仅支持独立的group模式,不支持group的嵌套,globalZOrder排序性能优化问题,有兴趣的可以自行优化,目前是每祯都排序的,下面贴出部分核心代码,分别为js部分和c++部分:



这些基本就是核心代码了,不多,改起来很简单,目前支持了native,h5平台根据同样的原理可以尝试修改,可能有些差异,待测。用大地图进行了测试,性能有明显提高,其他极端特殊情况还没有测,抛砖引玉,仅供大家参考吧

6赞

如果无合批问题,是不是反而会降低性能?

哇!大神终于现身了
对,无合批或者没有地图分块这类需求会降低性能,但其实如果这样也没必要用这个功能,那就是乱用了。性能很重要,但是功能和性能要取个平衡点,两边任意一方面追求极致,都会产生问题,如果只一味考虑性能,避免做一些功能,那么可能会导致因为缺乏这个功能,为了实现例如上面岁描述的问题,就会很难解决,这个问题除了合批更重要的是能够解决地图分块加载,每块多层的情况下,既可以按块管理地图,也可以更灵活的进行显示层级管理,达到节点关系和渲染关系解耦。面对一些特殊需求花费一些性能是值得的,就像cocos,明知引擎纯c++性能最好,但是考虑各方面需求还是有大量js代码的。所有功能都会消耗性能,但是太过于考虑性能不做功能也没办法解决问题。我的理解是:具体的情况,用特定的功能,消耗合理的性能,不产生性能问题是合理的,在性能消耗合理的情况下,提高易用性,开发便捷性,毕竟合理的情况下性能就是拿来花的 哈哈哈,再完美的功能,如果乱用都会产生性能问题。上面描述的问题,在目前creator功能当中没有找到能够很好解决问题的功能,想想如果全部拆散放在一个节点里。。。才能灵活的控制层级,想想就头大了,目前这个方法是我能够想到能够处理这个问题的唯一办法了,如果后面creator后面能够推出性能和功能兼得的功能来解决这个问题那就太棒了,期待:grin:

3赞

像这种不合批又想减少DC的方法,我只有一个方法。就是做四个地图的父节点,四种地图块分别加到四个不同的父节点上,这样保证一个父节点上的地图块都是相同的子节点,这样DC就从16变成4了。从设计上可以这样,一个小地图块的prefab可以全部放在一个节点上,使用的时候再通过代码拆分子节点添加到不同的父节点上。

2赞

希望早日加入globalZOrder功能 .

其实关于这种地图,我也比较倾向@344556726 说的,本地设置4个空节点当层级。
至于逻辑的管理封装,其实自己可以本地建造一个map表,把所有的节点通过js的set get函数绑定map表,这样其实更方便管理了。不得不说mvvm真的是ui克星。

但是我还是希望有个globalZOrder 的存在。

嗯嗯,你说的对!

按照楼主的思路优化,DrawCall降低很明显。给个赞

参考你的实现,DrawCall降低明显(在背包这种重灾区)

其次性能这块我这里额外添加了字段去优化了一下。大概思路是参考reorderChildren的_dirty字段,额外添加了一个groupDirty字段,在groupZOrder不变动的情况下,避免每帧排序。

这里简单分享一下改动实现:
C++:


TS:

import { UINodeEvent } from "../../common/UI/UINodeEvent";
import { getChildrensInHierarchy } from "../../common/utils/CommonUtils";


const { ccclass, property,menu } = cc._decorator

/**
 * UI分层渲染组件,目前只支持Native
 */
@ccclass
@menu("Custom/UIBatchRender")
export default class UIBatchRender extends cc.Component{
    protected static __CUSTOM_NAME:string = "UIBatchRender";
    protected get __CUSTOM_NAME():string{
        return UIBatchRender.__CUSTOM_NAME;
    }

    private _layoutDirty = true;

    private checkEnable():boolean{
        if( !(CC_JSB && CC_NATIVERENDERER) ){
            return false;
        }
        if( !(this.node['_proxy'] && this.node['_proxy'].setGlobalZOrder) ){
            return false;
        }
        return true;
    }

    onEnable(){
        if(!this.checkEnable()) return;
        this.node.isGroup = true;
        this._layoutDirty = true;

        cc.director.on(cc.Director.EVENT_AFTER_UPDATE, this.updateLayout, this);
        this.onEvents(this.node);
        this._addChildrenEventListeners(this.node);
        this.updateLayout();
    }
    onDisable(){
        if(!this.checkEnable()) return;
        this.node.isGroup = false;

        cc.director.off(cc.Director.EVENT_AFTER_UPDATE, this.updateLayout, this);
        this.offEvents(this.node);
        this._removeChildrenEventListeners(this.node);
        this.setGlobalZOrder(this.node,0,true);
    }

    private onEvents(node:cc.Node){
        node.on(UINodeEvent.CHILD_ADDED, this._childAdded, this);
        node.on(UINodeEvent.CHILD_REMOVED, this._childRemoved, this);
        node.on(UINodeEvent.CHILD_REORDER, this._doLayoutDirty, this);
    }
    private offEvents(node:cc.Node){
        node.off(UINodeEvent.CHILD_ADDED, this._childAdded, this);
        node.off(UINodeEvent.CHILD_REMOVED, this._childRemoved, this);
        node.off(UINodeEvent.CHILD_REORDER, this._doLayoutDirty, this);
    }

    private _addChildrenEventListeners(node:cc.Node) {
        let children:Array<cc.Node> = [];
        getChildrensInHierarchy(node,children)
        for (let i = 0; i < children.length; ++i) {
            let child = children[i];
            this.onEvents(child);
        }   
    }

    private _removeChildrenEventListeners(node:cc.Node){
        let children:Array<cc.Node> = [];
        getChildrensInHierarchy(this.node,children)
        for (let i = 0; i < children.length; ++i) {
            let child = children[i];
            this.offEvents(child)
        }
    }

    private _childAdded(child) {
        this.onEvents(child)
        this._addChildrenEventListeners(child);
        this._doLayoutDirty();
    }

    private _childRemoved(child) {
        this.offEvents(child)
        this._removeChildrenEventListeners(child);
        this._doLayoutDirty();
    }

    private _doLayoutDirty(){
        this._layoutDirty = true;
    }
    private setGlobalZOrder(node:cc.Node,loopNum:number = 0,clean:boolean = false){
        let childZorder = 1;
        if(!clean){
            for(let i=0;i<=loopNum;i++){
                childZorder *= 10;
            }
            if(childZorder <= 10){
                let loopAddNum = node.children.length.toString().length;
                loopNum += loopAddNum;
            }else{
                loopNum += 1;
            }
        }
        for(let i=0;i<node.children.length;i++){
            let child = node.children[i];
            if(!clean){
                child.globalZOrder = childZorder + i;
            }else{
                child.globalZOrder = 0;
            }
            this.setGlobalZOrder(child,loopNum,clean);
        }
    }

    /**
     * 更新全局Global字段
     * @param force 
     */
    public updateLayout(force:boolean = false){
        if(force || this._layoutDirty){
            this._layoutDirty = false;
            this.node.groupDirty = true;
            this.setGlobalZOrder(this.node);
        }
    }
    
}

把上述脚本挂载在需要设置group(UI分批渲染)的节点上。
该脚本会自动跟踪孩子的添加删除等,有更新全局Global字段动作,则会将节点的groupDirty设置为true,触发C++层面的孩子重新排序

2赞

砖头抛出去,果然引来玉了哈哈哈:+1:,处理的时候还需要注意下遮罩的情况,group里面有使用遮罩的就不能参与group排序了,否则会出问题,要以遮罩为父节点作为一个整体,递归的时候走else分支,同样道理还可以支持group的嵌套,当然如果不考虑这些特殊情况,目前基本功能是够用的

2赞

大哥,地理位置 很关键啊

creator做重度游戏必须要有强的性能意识

哪个版本的

2.3.3版本
在此基础上,再分享一下对应H5的实现:
engine\cocos2d\core\CCNode.js:

engine\cocos2d\core\renderer\render-flow.js:

3赞

在处理opacity时有一点问题