展会信息港展会大全

Cocos2d-x 内存管理,对象构造,坐标情况,容器使用
来源:互联网   发布日期:2015-09-26 18:25:59   浏览:3055次  

导读:Cocos2d-x创建工程Cocos2d-x 由若干部分组成,有些采用静态链接、有些则是动态链接、更有些是直接源代码分发的;加上工程本身的跨平台属性,需要针对不同平台写初始化代码;总而言...

Cocos2d-x

创建工程

Cocos2d-x 由若干部分组成,有些采用静态链接、有些则是动态链接、更有些是直接源代码分发的;加上工程本身的跨平台属性,需要针对不同平台写初始化代码;总而言之,从零开始手写一个空工程是相当繁琐的。

所以老版本的策略是:为不同平台下的开发工具提供模板、脚本之类的东西以便获得每次的初始空工程。比如它会为 Visual Studio

安装一个 cocos2d-x

程序模板,这样就可以直接在新建对话框里选择新建一个 Visual C++

的空 cocos2d-x

工程了。

当然,从 2.1.4

版本开始,官方就不再为 VS

提供模板了,逐步在各平台采用统一的 Python

脚本创建跨平台工程。(吐槽:虽然不提供模板了,但是原先用来安装模板的 install-templates-msvc.bat

文件居然还在,虽然一运行就报错的说……)

这个创建脚本使用 Python 2.X,同时它还有一个 Bug:使用当前目录而不是脚本文件所在目录查找模板文件(GitHub

上的 develop

版已经修复)。所以,要创建工程,我们需要先从命令行进入 tools/project-creator

目录,然后再使用

create_project.py -project工程名称>-package Java 包名> -language 语言>

就会在 projects

目录下创建相应的工程。这一点在直接运行 create_project.py 时会给出提示。(3.0

版本将在 cocos2d-x

根目录提供 create-multi-platform-projects.py

文件为你间接调用 tools/project-creator/create_project.py)那么作为一个示例,我们可以输入:

create_project.py -projectWhatever -package

com.timothyqiu.Whatever -language

cpp

咳咳,如果你对为什么语言是 C++

也要输入「Java

包名」感到困惑,那么看一眼生成后的工程目录结构就明白了:

Whatever -+- Classes

|- Resources

|- proj.android

|- proj.blackberry

|- proj.ios

|- proj.linux

|- proj.mac

|- proj.marmalade

`- proj.win32

结构非常精巧:平台无关的(自己写的逻辑之类的)代码都在 Classes

目录下;程序用到的资源都在 Resources

目录下;其余的平台相关(预置的初始化之类的)代码及工程文件都在各自的目录下。

所以,既然有 Android

工程在,那么创建时显然是需要包名的。

渲染树

如果你熟悉 3D

也许你也熟悉「渲染树」的概念:加入渲染树的节点才能够被渲染,而节点的父子关系也对其位移、缩放、旋转有相应影响。渲染一帧图像的过程就是遍历整棵渲染树、依次绘制各个节点的过程。

cocos2d-x 中的节点,以 CCNode 表示,它除了作为一棵渲染树的节点外,还提供了定时回调以及执行 CCAction 动作的功能。当然,CCNode 还封装了一些类似位置、缩放、旋转的基本属性,其中锚点(Anchor

Point)是非常重要的属性:节点的位置和节点左上角、右下角的位置是什么关系?缩放以哪个点为定点?旋转以哪个点为中心?这些问题的答案就是锚点。

cocos2d-x 中对象的属性使用 Getter/Setter

封装,均为 setPropertyName(value) 和 getPropertyName()、isPropertyName() 的形式。一些 CCNode 常用属性如下:

·位置(Position)默认为

(0, 0),是一个 CCPoint 类型的值。

·旋转(Rotation)默认为 0,顺时针以角度计。

·缩放(Scale)默认为 1.0f。

·尺寸(ContentSize)默认为

(0, 0),是一个 CCSize 类型的值。

·可见(Visible)默认为 true。不可见和不存在是两码事。

·锚点(AnchorPoint)默认为

(0, 0)。每个分量都是一个浮点数,0.0f 表示左上,1.0f 表示右下(所以 0.5f 表示中心点)。取值也可以不在

0 和 1

的范围内,表示锚点位置在节点范围外。

另外还有 Tag、UserData、UserObject

三人组,类型分别是 int、void* 和 CCObject,都是用户为节点自定义的数据,CCNode 只是把它存起来以供你日后获取,不作它用。

CCNode 常见的一些子类包括:

·CCScene 场景,一般作为渲染树的根节点存在

·CCLayer 层,一般作为精灵的容器存在,能够接受外部输入(触摸事件、加速计等)

·CCSprite 精灵,图像节点

·CCMenu 抽象菜单,可以添加 CCMenuItem 的

UI 元素。本身是从 CCLayer 派生的

出于拯救大众于水火的目的,以上子类的锚点都被默认设成了 (0.5, 0.5)。

p.s. cocos2d-x 中的绝大多数类均以 CC 作为前缀。但这样的做法明显是与它们所在的 cocos2d 这个

namespace 重复的,所幸在将来的 3.X

新版本中这些重复的前缀都会被删掉。

无规矩不成方圆

cocos2d-x 基于 OpenGL,故使用右手坐标系:从左到右、从下到上为坐标轴正方向。

前面说过的 CCPoint 类型,可以用来表示坐标点,也可以用来表示向量;而 CCSize 类型类似,用来表示尺寸;另外还有一个 CCRect 是CCPoint 和 CCSize 的组合,表示矩形区域的左上角和尺寸。

因为 CCPoint 和 CCSize 实质都是两个 float 的组合,所以其构造函数也都需要两个 float。介于每次传入不同类型变量的都显式去static_cast 过于手残,cocos2d-x

给出了三个对应的宏来帮你写转型:CCPointMake(x,y)、CCSizeMake(w,h) 和CCRectMake(x,

y, w, h)。当然,CCPoint 太常用了,以至于它还有一个更省击键数的宏 ccp(x,

y)。

前面还说过旋转属性,它是一个角度值。cocos2d-x

提供了 CC_DEGREES_TO_RADIANS(deg) 和 CC_RADIANS_TO_DEGREES(rad) 宏进行弧度、角度的换算。

内存管理

cocos2d-x 使用了来自 cocos2d-iphone

的 Objective-C

风格,这不仅体现在代码风格上,还体现在内存管理风格上。

Objective-C 的内存管理使用的是(手动)引用计数技术。简而言之,就是对象默认存在

1 个引用、用 retain() 可以增加一个引用、用release() 可以减少一个引用。一旦引用减少到

0,对象自动释放。

聪明的你一定想到了不少实现方法。不过由于目前版本中所有对象都只是裸指针(例如 CCDirecter *director 的本质是一个指针而非对象),所以,似乎在这一前提下实现引用计数的唯一方法就是:所有对象都继承自同一根类,由这个根类负责进行引用计数工作。当然如此一来,所有对象都应该创建在堆上。

顺带一提,cocos2d-x

当前版本并没有使用智能指针,一是因为 cocos2d-iphone

就是如此,二是因为 cocos2d-x

项目启动时 C++11

仍未定稿,三是因为当时各手机平台上支持情况不一样。不过时过境迁,在 2013

都过去了一半的现在,官方已经决定在 3.0

新版本里引入 C++11

的智能指针。

根对象

cocos2d-x 用来提供引用计数功能的根类就是 CCObject。主打这四个函数:

·CCObject::CCObject()

·CCObject::retain()

·CCObject::release()

·CCObject::autorelease()

前三个好说,分别是将当前对象的引用计数置一、增一、减一。那么最后一个 autorelease() 是干啥的?

借用 Objective-C

中的解释:release() 是引用计数立即减一,而 autorelease() 则是在不久的将来减一(至少在当前函数之后)。

在 cocos2d-x

的实现中,使用了名叫 CCAutoReleasePool 的「自动释放池」对象。它会在自身被释放(析构)时调用其所持有的所有对象的release() 方法。CCObject::autorelease() 所做的正是将自己加入自动释放池中。

cocos2d-x 会在每一帧的开始创建一个 CCAutoReleasePool,并在该帧结束时释放它。所以,所有被调用过 autorelease() 的 CCObject 都有机会在当前帧结束后自动释放。(之所以是「有机会」,因为即使调用 release() 同样也只是有机会释放,是否真正释放由引用计数决定。)

所以,大致小结一下就是:

·每一次 new 或者 retain() 都应该对应存在一次 autorelease() 或者 release()

·autorelease() 是有代价的,所以应该尽量使用 release()

创建和销毁

cocos2d-x 中没有使用异常,所以对象的创建使用了两步构造法(Two-phase Construction)。所谓两步构造法,就是构造函数只用来为变量赋初值而不执行逻辑相关代码,所有初始化用的功能代码另写一个初始化函数。(构造函数没有返回值,所以只能用异常与外界沟通。)

CCSprite *sprite = newCCSprite();

// 创建对象

sprite->initWithFile("background.png");

// 初始化对象

上面这两行就是两步构造法的一个实例。介于这么写相当的繁琐,cocos2d-x

为每一个 initXXX() 都提供一个相应的工厂方法createXXX():

CCSprite *sprite =CCSprite::create("background.png");

其实现很简单:

CCSprite *pobSprite = newCCSprite();

if(pobSprite && pobSprite->initWithFile(pszFileName)) {

pobSprite->autorelease();

return pobSprite;

}

CC_SAFE_DELETE(pobSprite);

return NULL;

注意到其中的 autorelease() 了吗?这意味着,如果只是 create() 而没有做其它任何操作,那么这个对象会在本帧结束时释放;但如果在create() 后调用了 sprite->retain(),那么这个对象就不会自动释放(如果是 this->addChild(sprite) 则同理,因为 addChild() 会间接调用 retain())。

需要注意的是,这种创建方式也导致父类无法自动初始化,必须在子类的 initXXX() 方法里手动去调用直接父类的 initXXX() 方法。于是(如果手写),一般的初始化都类似于:

boolHelloWorldLayer::init()

{

bool success = false;

do {

CC_BREAK_IF(!CCLayer::init());

//actual init code here, break if failed

success = true;

} while (0);

// clean up if nescesary

return success;

}

容器

因为 cocos2d-x

的这种「特殊」内存管理方式,对 retain()、release() 一无所知的

STL 容器或许并不适合你,于是便有了CCArray、CCSet、CCDirectory。(在

STL 容器中直接存放裸指针,可能由于野指针带来各种麻烦。当然,如果考虑使用智能指针的话,存入对应 std::shared_ptr 即可保证容器生命周期内所含对象有效;而

cocos2d-x 在未来版本中全面引入智能指针代替 Objective-C

风格的内存管理后,单存入 std::weak_ptr 就可以检测到野指针了。)

cocos2d-x 所提供的这些容器所存放的都是 CCObject 对象(其实是指针,但鉴于正确的使用方法只可能是指针,所以方便起见以后就不强调是指针了)。在对象进入容器时,容器自动调用一次 retain() 取得其引用;在对象移出容器或是容器本身被销毁时,容器自动为对象调用一次 release() 放弃引用。

这样就保证了所有在这些容器中的对象都是有效的对象。(还记得 CCAutoReleasePool 吗?它就是用 CCArray 实现的。)

对于容器,除却添加删除,常用的还有遍历。与 STL

提供迭代器不同,CCArray 和 CCDirectory 都是用

For Each 宏来遍历的。(CCSet和 STL

相同。)

CCObject *element = nullptr;

CCARRAY_FOREACH(arrayOfSprites,element) {

auto sprite = dynamic_cast(element);

// ...

}

导演

CCDirector 类就是导演类,(总)导演每部戏只有一个,所以它是个单件类。cocos2d-x

中所有单件类都通过类似 CCName::sharedName()的方式获得 CCName

* 单件对象。这种(命名)习惯是从 cocos2d-iphone

里带来的,在 3.X

中,将会变成 CCName::getInstance() 这种更方便的方式。

导演的主打功能是场景调度和游戏流程控制。

·runWithScene() 开始运行,设置初始场景

·replaceScene() 切换场景,不保留旧场景

·pushScene()、popScene() 常见的场景栈式管理

·pause() 暂停当前场景(仍有会绘制场景,但是不会执行逻辑)

·resume() 恢复当前场景

·end() 结束运行

细枝末节

在 SDK

为我们提供的空工程中,除了上面提到的这些「大头」,还有一些零碎的细枝末节。

入口

事实上,每一个 proj.* 目录下都有自己平台的初始化代码,也就是各自类似于 main() 的东东。

而在 Classes 目录下,平台无关代码中,程序的入口便是 AppDelegate 类。没错,这个类(依旧)源自

cocos2d-iphone,确切的说是 iOS,个人理解,这是一个用来接收程序 UI

状态消息的类。

程序通过其中仅有的三个顾名思义的方法 applicationDidFinishLaunching() applicationDidEnterBackground()applicationWillEnterForeground() 来感知自身的状态,以作出反应。比如

Did finish launching 时初始化引擎;Did enter background

时暂停背景音乐、暂存状态;Will enter foreground

时取出状态、恢复背景音乐等等。

现有的「空工程」中,applicationDidFinishLaunching() 里的核心其实就是这两句:

CCScene *pScene =HelloWorld::scene();

pDirector->runWithScene(pScene);

它搭建了 Classes

目录下 AppDelegate 和 HelloWorld 这两个类的桥梁:将场景创建出来后交给导演,然后导演就自顾自干活去了。

选择器

在 HelloWorld.cpp 中,我们可以看到这样的语句:

CCMenuItemImage*pCloseItem = CCMenuItemImage::create(

"CloseNormal.png",

"CloseSelected.png",

this,menu_selector(HelloWorld::menuCloseCallback)

);

这个 menu_selector 好神奇,究竟是什么?

好吧,这货(没错,还)是从 cocos2d-iphone

来的,我们来看实际代码(2.1.4

版本的 C

风格转换看着不爽,这里使用 C++

风格类型转换稍作修改):

typedef void(CCObject::*SEL_MenuHandler)(CCObject*);

#definemenu_selector(_SELECTOR)static_cast<:sel_menuhandler>(&_SELECTOR)

你看,没什么大不了的,各种 selector

都只是「成员函数指针 +

强制类型转换」而已,它和它的前一个参数(CCObject *)共同指定了一个回调。(回想一下,你还记得 C++

里该如何定义、初始化、使用成员函数指针吗?)

当然,在不久的将来,3.X

版本会使用更灵活的 std::function 和 std::bind 来代替这种「selector」。

转自:http://timothyqiu.com/archives/cocos2d-x-note-first-steps/

赞助本站

人工智能实验室

相关热词: Cocos2d 游戏开发 教程

AiLab云推荐
展开

热门栏目HotCates

Copyright © 2010-2024 AiLab Team. 人工智能实验室 版权所有    关于我们 | 联系我们 | 广告服务 | 公司动态 | 免责声明 | 隐私条款 | 工作机会 | 展会港