游戏是由无数的 UI 元素构成的,每个 UI 元素作为独立的个体,都具备自己的位置和样式,而不同的 UI 元素之间也存在着关联,本节课将从 Cocos2d-x 源码角度介绍如何确定一个 UI 元素的位置,并将独立的 UI 元素构建成 UI 树,和使用 UI 树。
Node
UI元素是指sprite、label、layer、scene等基本上所有常见的元素,继承自Node类,Node类的主要作用是提供:设置UI元素的位置、根据坐标系对UI元素进行坐标计算、确定UI元素的深度值、设置UI元素的缩放因子、得到UI元素的尺寸、设置UI元素的可见性/运行状态/事件监听器/动作/调度器/颜色/Camera、绘制UI元素、对子UI元素进行操作、操作与父UI元素的关系、获取UI元素的scene/碰撞矩阵绘制元素、查询OpenGL ES状态、操作用户数据等通用函数。
位置
UI元素的位置是通过3个因素确定的。1是确定UI元素的锚点,锚点的意义在于:设置UI位置的时候,通过讲锚点放在父UI元素坐标系的某个点上;2是确定UI元素的父UI元素是谁;3是确定讲UI元素的锚点放在父UI元素的那个点。确定了这三个因素后,UI元素的位置在开发者角度就基本确定了。如果父UI、祖父UI(如果有的话)等,在layer上的位置已经确定,那么UI元素的位置就算是确定了。
Node的setAnchorPoint用于设置Node的锚点位于Node本地坐标系的位置,传入参数为位置坐标的归一化坐标。getAnchorPoint与setAnchorPoint相对应
Node的getAnchorPointInPoints得到Node的锚点在Node本地坐标系的非归一化位置。
Node的setPosition用于设置Node的锚点在其父UI元素的本地坐标系中的位置,传入参数为位置坐标。getPosition与setPosition相对应
Node的setNormalizedPosition用于设置Node的锚点在其父UI元素的本地坐标系中的归一化位置,传入参数为位置坐标的归一化坐标。getNormalizedPosition与setNormalizedPosition相对应
Node的setPosition3D除了设置位置的x和y值,还设置了一个_positionZ和_globalZOrder,_globalZOrder用于排序和给事件分发器提供位置信息使用。getPosition3D与setPosition3D相对应
Node的ignoreAnchorPointForPosition是一个内部函数,只能用于scene和layer,作用是在设置scene和layer的位置的时候,锚点按照(0,0)来设置。其他类如果使用该函数会报错
坐标系
Cocos2d-X拥有一个世界坐标系,世界坐标系等同于OpenGL ES坐标系;然而开发者接触的基本都是本地坐标系,每个父UI元素都有一个自己的本地坐标系,子UI设置坐标的时候都是相对父UI的坐标系进行设置。
Node的getNodeToParentTransform用于获取一个Mat4的矩阵,该矩阵可以用于将Node的本地坐标系中的坐标,转换到父UI元素的坐标系中。比如Node的锚点为(0.5, 0.5),将Node的position设置为(100, 100),假如Node的contentsize为(50, 50),那么在Node本地坐标系(0, 0)的点,其实是其父UI元素坐标系中的(100-50*0.5, 100-50*0.5)=(75, 75)。setNodeToParentTransform与getNodeToParentTransform。getNodeToParentAffineTransform类似getNodeToParentTransform,区别在于getNodeToParentTransform得到的是Mat4的矩阵,getNodeToParentAffineTransform得到的是仿射变换,在有限维的情况,每个仿射变换可以由一个矩阵A和一个向量b组成。
Node的getParentToNodeTransform用于获取一个Mat4的矩阵,该矩阵可以用于父UI元素的坐标系中的坐标,转变为Node的本地坐标系中的坐标。getParentToNodeAffineTransform得到的是仿射变换。
Node的getNodeToWorldTransform用于获取一个Mat4的矩阵,该矩阵可以用于将Node的本地坐标系中的坐标,转换到世界坐标系中。getNodeToWorldAffineTransform得到的是仿射变换
Node的getWorldToNodeTransform用于获取一个Mat4的矩阵,该矩阵可以用于将世界坐标系中的一个坐标,转换到Node的本地坐标系中的坐标。getWorldToNodeAffineTransform得到的是仿射变换
Node的convertToNodeSpace是借用getWorldToNodeTransform得到的Mat4矩阵,将世界坐标,转换到Node本地坐标系中的本地坐标。反之,Node的convertToWorldSpace是借用getNodeToWorldTransform得到的Mat4矩阵,将Node本地坐标系中的本地坐标转为世界坐标。
Node的convertToNodeSpaceAR是将世界坐标,转换到以Node锚点为原点的坐标系中的坐标。反之,Node的convertToWorldSpaceAR将以Node锚点为原点的坐标系中的某一点坐标,转为世界坐标。
树是一堆节点的组合,每个节点(除了跟节点)都有一个父节点,跟节点没有父节点;每个节点(除了叶子节点)都有一个或多个子结点,叶子节点没有子结点。
Cocos2d-X中每个场景都可以看做是一棵独立的树,每个节点都是Node子类的实例化,跟节点是Scene,其父节点为空。
Node的addChild用于将一个节点设置为Node的子结点,每个Node都有一个Vector的容器用于存储子结点,addChild方法将一个节点加入Node的该容器中。同时设置子结点的tag、name(同时会计算出name的hash值用于搜索使用)、parent、_localZOrder、OrderOfArrival(用于在Z值相同的时候,排序使用)。若Node处于显示状态,则同时调用child的onEnter方法(该方法为每个Node被显示的时候调用的回调方法),并根据情况调用child的onEnterTransitionDidFinish(该方法为每个Node入场动画结束时调用的回调方法)、以及调用Node的updateCascadeColor(改变颜色)和updateCascadeOpacity(改变透明度)方法。
Node的getChildByTag方法,循环搜索Node的所有子节点,根据Tag,找到所要找的子结点并返回。getChildByName方法,根据name和name的hash值,找到所需要的子结点。getChildren方法,返回Node用于存储子结点的Vector容器。getChildrenCount方法返回子结点的数量。enumerateChildren方法用于找到child,并对child执行回调函数。
Node的setParent用于设置Node的父节点,getParent用于返回Node的父节点。
Node的removeFromParent、removeFromParentAndCleanup会判断是否有Node是否有父节点,如果有的话,会去调用父节点的removeChild方法。Node的removeChildByTag、removeChildByName会掉找到child,并调用removeChild方法。removeChild方法用于执行child的onExitTransitionDidStart(该方法为每个Node出场动画开始时调用的回调方法)、onExit(该方法为每个Node出场时调用的回调方法),如果有必要的话,停止child以及child的child们的动作和调度器,并将child的父节点设置为null,从父节点的容器中删除。removeAllChildrenWithCleanup和removeAllChildren相同,主体函数类似removeChild的功能,将所有的child执行退出的回调函数,停止动作,父节点设置为空并从清空父节点的容器。
_localZOrder作为UI元素的逻辑深度,_orderOfArrival代表着开发者将UI元素加入UI树的顺序。3.x之后又加入了_globalZOrder一起决定着UI元素的绘制顺序
UI树中每个节点都有0个或者1个或者多个子节点,所有子节点又分为左边的子节点以及右边的子节点。左边的子节点为_localZOrder小于0的UI元素,右边的子节点为_localZOrder大于或者等于0的UI元素。
决定绘制顺序之前,先将节点的子节点进行排序,按照_localZOrder从小到大,如果_localZOrder相同,_orderOfArrival从小到大的顺序进行排序。
对UI树的遍历使用的是中序遍历,即为左根右的顺序对UI树进行遍历。
举个例子:一个根节点的_localZOrder为0,左子节点为-1,右子节点为2,左子节点的左子节点为-3,左子节点的右子节点为3,右子节点的左子节点为-4,右子节点的右子节点为4。那么用_localZOrder来表示各个节点,绘制顺序为:-3,-1,3,0,-4,2,4。
以上的绘制顺序不能将不同父节点的两个子节点进行排序,所以3.x之后设计了_globalZOrder。默认为0,如果UI元素的_globalZOrder为非0,则按照_globalZOrder排序,否则,就按照上述排序。
这里需要借用这个系列第一讲的知识(Cocos2d-X的UI树(1)),在Cocos2d-X的UI树(1)中我们介绍了几种矩阵,有一些矩阵是用来计算Node的世界坐标系使用的,而绘制的时候所需要的就是Node的世界坐标系,但是开发者提供的是本地坐标系,所以需要将Node的本地坐标系、变化矩阵提供给shader,让shader计算出Node的世界坐标。
笔者制作网站的目的,主要是借用自己之前的知识背景(Android App开发和图形学知识),将自己学习笔记拿出来,和大家一起进行交流,毕竟每个人的知识体系不同,有交流才会有提高,所以欢迎大家通过各种方式和我联系。
网址:www.geekfaner.com
"极客学院"教学视频(高清版_推荐):http://www.jikexueyuan.com/course/cocos/1-5-0/