(如有错误,请联系我更正,以免误导他人!)
在学习cocos2dx时经常出现这种情况→→→→→→→→→→→→→→→→
!!!本该出现的妹纸为何变成娘娘!逗逼的表情背后隐藏着令人心动的美丽脸蛋,是什么让娘娘得宠,又是什么让妹纸消失!唯一看透了真相的是一个。。。好吧屁话不多说,下面我们就来探究下cocos2dx的渲染顺序的“依仗”——
Zorder!
在cocos2dx的node.h源码中我们可以看到下面三个函数:setLocalZOrder、setGlobalZOrder、setOrderOfArrival。这三个函数分别设置了node(cocos2dx场景中的元素都是继承于node)的三个属性:_localZOrder、_globalZOrder、_orderOfArrival。cocos2dx就是根据这三个属性来决定元素的渲染顺序。break表示当时看到三个zorder瞬间懵逼啊,但是break不服啊,为了每次都能看到后面的妹纸再难也要解决它!
首先我们看到setLocalZOrder函数的官方注释:(咳咳,蹩脚英语翻译好久)
LocalZOrder is the'key' used to sort the node relative to its siblings.
LocalZOrder是用于给node(节点)在它的兄弟姐妹(如一个Layer下add的多个node,这些node就互为兄弟姐妹)之间排序的关键字。
The Node's parent willsort all its children based ont the LocalZOrder value.
node的父节点会基于LocalZOrder给他的所有孩子节点排序。
If two nodes have the same LocalZOrder, then the node that was added first tothe children's array will be in front of the other node in the array.
如果两个节点有相同的LocalZOrder,那么先add的节点会先添加到children数组里(node有一个类型为Vectory
Also, the Scene Graph is traversed using the "In-Order" treetraversal algorithm
And Nodes that have LocalZOder values < 0 are the "left" subtree
While Nodes with LocalZOder >=0 are the "right" subtree.
!!重点!!场景图(Scene Graph暂且这么翻译)将使用中序遍历二叉树的算法去遍历他上面的所有节点。LocalZOrder<0的节点在左子树,>=0的在右子树。(这就意味着在同一个层次中拥有较小的LocalZOrder值的子节点先被遍历)(刚修完的数据结构就用上了,想想还有点小鸡冻)
由此我们可以看出,LocalZorder是父节点用来管理子节点的,而且父节点使用中序遍历二叉树的算法来遍历他的孩子节点。小伙伴看到这里可能就会问了:这也没提到渲染跟这个LocalZOrder有什么关系啊。不要急嘛,跟着break的脚步,迎娶白富美,一步一步走向人生巅峰(咳咳。。)
接下来我们来看setGlobalZOrder函数的注释:
Defines the oder inwhich the nodes are renderer.
规定节点的渲染顺序。(进入主题!)
Nodes that have a Global Z Order lower, are renderer first.
GlobalZOrder值低的节点先被渲染。
In case two or more nodes have the same Global Z Order, the oder is notguaranteed.
在两个以上节点有相同的GlobalZOrder值的情况下,渲染顺序是不被保证的。
The only exception if the Nodes have a Global Z Order == 0. In that case,the Scene Graph order is used.
只有一个例外情况:如果节点的GlobalZOrder都=0,在这种情况下场景图渲染顺序就被使用。(!!!LocalZOrder就是被场景图使用的)
By default, all nodeshave a Global Z Order = 0. That means that by default, the Scene Graph order isused to render the nodes.
默认情况下,所有节点的GlobalZOrder=0。这就意味着,在默认情况下,场景图渲染顺序用来渲染所有节点。
Global Z Order is useful when you need to render nodes in an orderdifferent than the Scene Graph order.
当你需要使用不同于场景图渲染顺序的顺序去渲染节点时,GlobalZOrder是有帮助的。
到这里,我们就可以用我们学到的知识来英雄救美啦!
首先,贴上我们刚才贴的第一张图的程序的init函数源码。
bool LGameScene::init()
{
if (!Layer::init())
return false;
auto VisibleSize =Director::getInstance()->getVisibleSize();
auto Origin =Director::getInstance()->getVisibleOrigin();
Vec2 Center;
Center.x = Origin.x + VisibleSize.width/ 2;
Center.y = Origin.y + VisibleSize.height/ 2;
Sprite* beauty =Sprite::create("picsource\\meizhi.jpg");
beauty->setScale(0.5f);
beauty->setPosition(Center);
addChild(beauty);
Sprite* idiot =Sprite::create("picsource\\22222.jpg");
idiot->setPosition(Center);
addChild(idiot);
return true;
}
我们可以看到,我们首先在当前场景上创建了漂亮妹纸和娘娘两个元素(beauty、idiot)。我们并没有使用LocalZOrder与GlobalZOrder。所以根据刚才我们看的源码注释我们可知,先add的先渲染,所以我们的漂亮妹纸就被娘娘挡住了。那为了看到漂亮妹纸,我们修改一下我们的代码
把第一个addChild函数调用改动如下:
(break查看了Node的构造函数源代码,发现Node构造时,_localZOrder、_globalZOrder、_orderOfArrival都为0,即三个成员变量的默认值为0)
addChild(beauty,1);(第二个参数就是设置节点的LocalZOrder值)
现在,beauty的LocalZOrder值就大于idiot了。那么我们的beauty就应该被场景图后于idiot渲染了。那么!!!妹纸是不是该出来了!鸡冻的时刻!→→→→→→→→
好了,漂亮妹纸出来了我们的学习动力也来了!接下来我们来测试我们对GlobalZOrder的理解。
我们知道GlobalZOrder值大的节点后被渲染,所以我们在最开始的代码的基础上改变beauty的GlobalZOrder值,使它比idiot的GlobalZorder值大也能达到上图的效果(篇幅太长就不贴图和代码了,小伙伴们可以自己测试)。那当GlobalZOrder和LocalZOrder都使用的情况下呢?
我们来用三张图片来测试一下:
bool LGameScene::init()
{
if (!Layer::init())
return false;
autoVisibleSize = Director::getInstance()->getVisibleSize();
auto Origin = Director::getInstance()->getVisibleOrigin();
Vec2 Center;
Center.x = Origin.x + VisibleSize.width / 2;
Center.y = Origin.y + VisibleSize.height / 2;
Sprite*beauty = Sprite::create("picsource\\meizhi.jpg");
beauty->setScale(0.3f);
beauty->setPosition(Center);
addChild(beauty);
Sprite* idiot = Sprite::create("picsource\\22222.jpg");
idiot->setScale(0.5f);
idiot->setPosition(200,150);
addChild(idiot);
Sprite* cartoon = Sprite::create("picsource\\xiaoxin.jpg");
cartoon->setScale(0.5f);
cartoon->setPosition(100,100);
addChild(cartoon);
return true;
}
现在我们有三个精灵了(Sprite)。而且,三个Sprite的LocalZOrder和GlobalZOrder值都是默认值0。→→→→→→→→
现在我们让妹纸的LocalZOrder=2,小新的LocalZOrder=1,娘娘的GlobalZOrder=1。
在init函数return之前加上:
beauty->setLocalZOrder(2);
cartoon->setLocalZOrder(1);
idiot->setGlobalZOrder(1);
现在有LocalZOrder:妹纸=2>小新=1>娘娘=0。
GlobalZOrder:妹纸=0=小新=0<娘娘=1。
→→→→→→→→
娘娘的LocalZOrder最小,但是她的GlobalZOrder最大,她是最后渲染。
小新和妹纸的GlobalZOrder值相等,他们按照LocalZOrder的大小决定渲染顺序。
由此可见,在渲染顺序上,GlobalZOrder相对于LocalZOrder有较高的优先级。所以我们要先对比节点的GlobalZOrder值的大小,大的后渲染。相等的情况下我们再比较节点的LocalZOrder值,同样的,大的后渲染。由此判断节点的渲染顺序。
那么,当GlobalZOrder和LocalZOrder都相等的情况下我们怎么判断节点的渲染顺序呢?
接下来就是break在本文要说的最后一个Order——
这是setOrderOfArrival函数的源码注释:
Sets the arrival order when this node has a same ZOrder with other children.
当这个节点有和其他节点一样的ZOrder时设置它的到达序号(orderofArrival)。(add节点的先后顺序)
A node which called addChild subsequently will take a larger arrival order,
更后作为参数调用addChild函数的节点得到一个更大的到达序号(orderofArrival),
If two children have the same Z order, the child with larger arrival order will be drawn later.
如果两个子节点有相同的LocalZOrder,那么有更大的到达序号(orderofArrival)的子节点会被更后绘制(渲染)。
@warning This method is used internally for localZOrder sorting, don't change this manually
警告:这个函数在以LocalZOrder进行排序的函数里面调用,不要手动的改变到达序号(orderofArrival)。(看到这break就忍不住吐槽一下了,也希望大家一起交流下。既然cocos2dx官方不希望我们手动调用这个函数,那么为什么要把它设为公有函数呢?)。
看到这,小伙伴们应该和break一样豁然开朗了,为什么在setLocalZOrder函数的注释中说到,当两个节点的LocalZOrder值相等时,先被add的先被渲染,后add的后渲染——原因就在于:每次调用addChild时,在addChild函数内部给了node的_orderOfArrival成员变量一个值,而且更后作为参数调用addChild的节点得到的_orderOfArrival值更大。而拥有更大的_orderOfArrival的节点更后渲染。
总结:节点的渲染顺序跟节点的三个成员变量有关(_localZOrder、_globalZOrder、_orderOfArrival)分别对应三个设置函数setLocalZOrder、setGlobalZOrder、setOrderOfArrival。无论是_localZOrder、_globalZOrder、_orderOfArrival都是越大的越后渲染,越小的越先渲染,而且有_globalZOrder的优先级大于_localZOrder的优先级大于_orderOfArrival的优先级。所以我们判断节点间的渲染(绘制)顺序时应先对比他们的_globalZOrder值,如若相等,再对比他们的_localZOrder值,如若相等,再对比他们的_orderOfArrival值。其中,_orderOfArrival值在调用addChild函数是自动给出,一般我们不调用setOrderOfArrival函数去更改节点间的渲染(绘制)顺序。
补充:当采用LocalZOrder作为节点渲染(绘制)顺序的判断值时,父节点的LocalZOrder不与子节点的LocalZOrder值作比较。子节点中LocalZOrder值小于0的节点作为以父节点为根节点的树的左子树的根节点,大于0的作为右子树的根节点。所以在中序遍历下,先(渲染)绘制子节点中LocalZOrder值小于0的子节点,再渲染(绘制)父节点,再渲染LocalZOrder值大于0的子节点。
问题都弄清楚了?显然不是,细心的小伙伴(跟我一样喜欢钻牛角尖的小伙伴)肯定要问了:那要是_localZOrder、_globalZOrder、_orderOfArrival的值都相等呢?由于篇幅太长了,break今天也有点鸡冻有点累了,所以下次再来跟小伙伴们分享答案咯。