首页 我所理解的cocos2d-x
文章
取消

我所理解的cocos2d-x

我所理解的cocos2d-x

###消息分发 消息机制一直是软件开发中减少模块之间耦合的标准方式。游戏中有大量的游戏对象,在这些对象之间通过消息传递而不是直接调用的方法会大大简化对象之间的交互和依赖。

野指针:指针指向的内存单元已经被释放,但是其他指针可能还指向它,这些内存可能已经被重新分配给其他对象,从而导致无法预测的结果。 内存泄漏:不再被使用的内存单元如果不被释放,就会一直占用内存单元,如果这些操作不断重复,就会导致内存占用不断增加。在游戏中,内存泄露引发的问题尤其严重,因为可能每一帧都在创建一个永远不会被回收的游戏对象。

C++有3种管理数据内存的方式,分别是自动存储、静态存储和动态存储。其中,静态存储用于存储一些在整个应用程序执行期间都存在的静态变量,动态存储用于存储通过new关键字分配的内存单元。自动变量是一个局部变量,其作用域为包含它的代码块所对应的作用域。

默认AutoreleasePool一帧被清理一次,主要是用来清理UI元素的。因为UI元素大部分都是添加到UI树中的,会一直占用内存,所以,这种情况下每帧清理并不会对内存占用有太大的影响。

Cocos2d-x使用Objective-C的自动回收池机制来管理对象内存的释放。Autorelease类似于一个共享的智能指针,该智能指针的作用域为一帧,该帧结束后,它将释放自己的引用计数。此时,如果该对象没有被其他共享指针引用,则对象被释放;如果对象被引用,则保留。

对于单个的非集合元素对象,我们往往不会通过Autorelease进行管理,除非它是一个临时对象。这个时候,我们只能手动使用retain()方法和release()方法进行管理,这其实等价于通过new运算符和delete运算符来进行内存管理,这种情况就容易导致内存管理问题。

在对象的构造函数中分配内存,在对象的析构函数中释放内存。这就是我们前面讲述的将动态分配的内存映射到一个自动变量,通过自动变量的构造函数和析构函数来分配和释放内存,这可以保证资源始终会被释放(即使出现异常,也能被正常释放)。这也是各种智能指针(如std::shared_ptr)的基本实现原理。

通常,我们并不直接设置每个元素的世界坐标,而是设置每个元素相对于UI树父级元素的相对坐标。在绘制的时候,由引擎根据UI树对每个元素执行坐标变换,计算出世界坐标,并将位置信息发送到OpenGL ES进行绘制。

一个元素的本地坐标系是以元素的左下角为原点,屏幕向右为x轴,向上为y轴。

convertToNodeSpace()方法用于将一个世界坐标转换到该元素的本地坐标系中。例如,node1的锚点为(0,0),node2的锚点为(1,1),node1位于UI树的顶层(其本地坐标等于其世界坐标),我们可以通过以下语句进行计算。

1
     auto point=node1->convertToNodeSpace(node2->getPosition())

node.convertToWorldSpace(nodePoint)方法则用于将一个本地坐标系中的位置转换为世界坐标系,通常父元素可以通过此方法计算一个子元素的世界坐标。

注意,参数nodePoint必须表示node的子元素的位置才有意义(因为子元素处于父元素的本地坐标系中)。如果nodePoint为(0,0),则convertToWorldSpace()方法返回的是该元素左下角的坐标,即该元素本地坐标系原点的世界坐标。

如果不依赖父元素,要想计算出元素自身的世界坐标,则可以使用convertToWorldSpaceAR()方法。该方法用于计算以该元素锚点为原点的坐标系中的某个位置的世界坐标,示例如下。

1
node->convertToWorldSpaceAR(Point::Zero);

如果一个UI树顶层的Layer的锚点为(0.5,0.5),则对该元素执行convertToWorldSpaceAR(Point::Zero)计算的结果为屏幕中点。 我们不难计算出以下关系。

auto point1=node->convertToWorldSpace(Point::Zero);
auto point2=node->convertToWorldSpaceAR(Point::Zero);
auto size=node->getContentSize();
auto anchor=node->getAnchorPoint();
auto point3=Point(size.width*anchor.x,size.height*anchor.y);

则point2=point1+point3。

Cocos2d-x中的UI树根节点为Scene类,UI树中每个节点都是一个Node实例对象,每个Node对象具有一个children集合及一个parent节点,其中Scene的parent属性为空。

渲染系统最重要的职责就是遍历UI树中的每一个元素,然后将每个元素绘制到屏幕上。UI树的遍历有两个重要的目的:其一是遍历的顺序决定了元素被绘制的顺序;其二是在遍历过程中实现元素的模型视图变换矩阵的计算,其计算结果供OpenGL ES渲染管线计算顶点位置。

Cocos2d-x使用localZOrder来表示元素的逻辑深度,UI树的遍历采用中序(in-order)的深度优先算法进行遍历(参考资料6)。该方法的遍历顺序及特点如下。 •遍历左边的子节点。 •遍历根节点。 •遍历右边的子节点

逻辑深度用localZOrder属性表示,如果两个元素的逻辑深度一致,则按它们被添加到UI树中的顺序排序。这个顺序决定了每个元素被访问的顺序,因此也决定了元素最终被绘制的顺序,元素的绘制顺序还影响着事件的分发顺序,一个事件的接收者可以与一个元素相关联,最终所有的接收者按与元素的绘制顺序相反的顺序被分发。

当我们创建一个Node对象时,其引用计数为1,并加入当前AutoreleasePool,所以,当前帧结束时会被释放一次。如果我们在这一帧中并没有将该对象添加到UI树中,则该对象会在帧结束的时候被释放。 当我们将其加入UI树中时,Node使用Cocos2d-x自身提供的Vector来存放子元素,Vector对插入的元素执行retain()方法,并在移除元素的时候执行release()方法

template<class T>
class CC_DLL Vector
{
public:
void pushBack(T object)
 {
     _data.push_back( object );
object->retain();
 }
 
void popBack()
 {
auto last = _data.back();
     _data.pop_back();
last->release();
 }
};

在Cocos2d-x中,一个场景是一棵以Scene为根节点的UI树,Scene中包含一个场景的所有UI元素,如按钮、人物、道路等,每个时刻最多只有一个当前场景在运行,Director管理着当前运行的场景,并提供在不同场景之间进行切换的方法。

本文由作者按照 CC BY 4.0 进行授权