6.4 节点树的渲染
游戏中所有可见的东西都是节点,在游戏运行的时候,每一帧都会调用整个游戏世界最顶层的一个Node(Scene)的visit函数,而该visit函数将贯穿到游戏中的所有Node,访问整棵节点树,决定节点是否渲染,如何渲染,以及渲染顺序等。节点的visit函数执行流程如下。
❏如果Visible属性为false,直接返回,其下所有子节点都不会显示。
❏OpenGL模型视图矩阵入栈(接下来应用该节点的矩阵变换)。
❏如果Grid对象存在且处于激活状态,调用m_pGrid->beforeDraw()。
❏如果矩阵变化了,则计算出该节点的空间变化矩阵。
❏调用sortAllChildren对子节点进行排序,遍历子节点,先访问ZOrder小于0的节点,再绘制节点,最后访问ZOrder大于等于0的节点。
❏如果Grid对象存在且处于激活状态,调用m_pGrid->afterDraw(this)。
❏OpenGL模型视图矩阵出栈(清除该节点的矩阵变换)。
在Cocos2d-x 3.0中,visit函数的执行流程发生了一些变化:
❏将原先流程的第3步和第6步删掉了,Grid已经是和具体逻辑相关的内容了,Node不应该关心它,Grid抽象到Node并不好,因为Grid并不是所有Node的公共特性。
❏将第4步移到了第1步之前,矩阵的计算被封装到Node::processParentFlags中,当矩阵发生变化时,才会重新计算矩阵,这里不仅缓存了当前节点矩阵,还缓存了计算好的模型视图矩阵,懒惰计算提高了运行效率。
❏绘制节点的操作变为了添加一条渲染命令至渲染器中,由渲染器调用这条渲染命令来完成对节点的绘制,将具体渲染的实现和节点本身剥离开,由专门的渲染器去管理,这样做的好处将在第15章中详解。
整个Node运行机制的核心就在第5步,从最顶层的父节点自上而下地开始进行了一个轮回(递归),游戏对象被有序地渲染出来。
sortAllChildren会对所有的子节点进行排序,在每次渲染之前对子节点进行排序,sortAllChildren的调用并不会每次都进行排序,只有当数据发生变化时才进行排序,这是一个优化。
如果在每次数据变动的时候进行排序,若同一帧有100个子节点变化,就需要进行100次排序,而现在,每次变化只是将ReorderChildDirty设置为true,在sortAllChildren中,当ReorderChildDirty为true时,才进行排序,这种懒惰计算的方法,提高了游戏运行的效率。
排序完之后,Node先按排序好的子节点的localZOrder属性值从小到大进行访问,先访问localZOrder小于0的,它们会被渲染在最下面,然后渲染自己,最后再渲染localZOrder大于等于0的。在Cocos2d-x 2.x中直接调用OpenGL进行渲染,而Cocos2d-x 3.x则是依次添加渲染命令到渲染器中。