原帖地址
http://www.tairan.com/archives/3627
在大多数平台上,cocos2d-x调用不同的SDK API来播放背景音乐和音效。CocosDenshion在同一时间只能播放一首背景音乐,但是能同时播放许多音效。
背景音乐
平台 | 支持的BGM格式 |
Android | 在android上CocosDenshion能支持的音频格式是对应于android.media.MediaPlayer所支持的格式。 |
iOS | 在IOS上面Cocos2d-x的CocosDenshion所支持的格式和Cocos2d-iphone中所支持的是一样,mp3,caf是推荐的格式。 |
windows | .mid, .wav are supported. Note that mp3 is not supported. |
Marmalade | mp3 |
Windows mid,wav, mp3都是被支持的。(修改:原文说mp3不被支持)
音效
警告:Samsung i9100 似乎在音频驱动上面有一个bug。它不能同时播放太多音效。所以当你在Samsung i9100中运行你的程序的时候,最好不要调用SimpleAudioEngine::playEffect(const char*filePah)太过频繁。
平台 | 支持的音效格式 |
Android | ogg是最好的选择,对wav的支持不是太好 |
iOS | IOS 和cocos2d-iphone中cocosDenshion所支持的格式一样。个人建议是苹果的caf格式 |
windows | * .mid, .wav. |
Marmalade | 只支持原生 PCM格式(参照 http://www.madewithmarmalade.com/devnet/forum/5459) |
在Android上使用OpenSL ES来播放音效
为什么使用OpenSL ES
一些开发者提醒在Samsung S2 i9100上面使用android原始的SoundPool来播放音效将会导致崩溃,所以我们决定使用OpenSL ES来解决这个问题。但是请注意目前为止只有在I9100上面SoundPool会引起崩溃,所以OpenSL ES只会替换掉I9100上面的SoundPool的实现方式。
怎么样在Cocos2d-x中使用OpenSL ES
实际上,你什么都不需要做。我们将会在你第一次使用SimpleAudioEngine的时候检测你设备类型。如果是I9100,就会自动切换为OpenSL ES,否则就使用SoundPool来播放音效的。
一些限制
OpenSL ES目前只用于samsung i9100。
当前实现的版本对预加载音效的数量有些限制,你最多只能预加载31个音效。
音量接口是有效的,但是当试着去控制音量的时候,可能会有问题。我们发现一些设备,包括I900,对OpenSL ES的支持不是很完美,因此我们不能得到这个音量的准确值。
修复的问题
2012-08-17 开发者不需要指定Android平台。OpenSL ES会侦测你设备类型,将会自动的在I9100上面工作。
2012-08-02 当同一个音效在一个很短的时间内播放了很多次,这个音效将会停止,然后重新播放,而不是同时播放很多次这个音效。
数据结构
CCArray
简介
CCArray是cocos2d所支持的。在你游戏中使用是非常合适。你可以在cocos2d-x源文件里面cocos2d/support/ data_support文件夹里面找到CCArray的文件。CCArray在cocos2d内使用广泛,模拟了苹果的NSMutableArray,但是执行效率更好。
CCArray是只提供面向对象包装的类
CCArray是继承至CCObject(CCObject主要是为了自动内存管理而创建的),并且提供了一系列接口,包括
创建
/** 创建一个数组 */ static CCArray* create(); /** 使用一些对象创建数组 */ static CCArray* create(CCObject* pObject, ...); /** 使用一个对象创建数组 */ static CCArray* createWithObject(CCObject* pObject); /** 创建一个数组并且提供容量 */ static CCArray* createWithCapacity(unsigned int capacity); /** 使用一个存在数组对象来新建一个数组 */ static CCArray* createWithArray(CCArray* otherArray);
插入
/** 插入一个对象 */ void addObject(CCObject* object); /** 插入一个存在数组里面的全部对象 */ void addObjectsFromArray(CCArray* otherArray); /** 在一个确定的索引位置插入一个对象 */ void insertObject(CCObject* object, unsigned int index);
删除
/** 移除最后的一个对象 */ void removeLastObject(bool bReleaseObj = true); /**移除一个确定的对象 */ void removeObject(CCObject* object, bool bReleaseObj = true); /** 移除一个确定索引位置的元素 */ void removeObjectAtIndex(unsigned int index, bool bReleaseObj = true); /** 移除全部元素 */ void removeObjectsInArray(CCArray* otherArray); /** 移除所有对象 */ void removeAllObjects(); /** 快速移除一个对象 */ void fastRemoveObject(CCObject* object); /** 快速移除一个确定索引位置的对象 */ void fastRemoveObjectAtIndex(unsigned int index);
remove和fastRemove有什么区别,可以看看源代码,remove是从CCArray中完全的移除,fastRemove只是将CCArray中对应的对象释放掉了,没够改变整个CCArray的结构。从代码上来看,区别在于删除元素之后,有没有把之后的元素向前移动覆盖掉之前的位置元素。
代码区别如下:
unsigned int remaining = arr->num - index;< if(remaining>0) { memmove((void *)&arr->arr[index], (void *)&arr->arr[index+1], remaining * sizeof(CCObject*)); }
遍历CCArray
在教程第五章- 如何检测碰撞 中,在update()函数下面调用了CCARRAY_FOREACH(arr, obj)方法,这个方法就是用来遍历CCArray(_targets和_projectiles),用来在每一帧中检测碰撞。
在HelloWorldScene.h中申明,并且在HelloWorldScene.cpp中定义
void HelloWorld::update(ccTime dt) { CCArray *projectilesToDelete = new CCArray; CCObject* it = NULL; CCObject* jt = NULL; CCARRAY_FOREACH(_projectiles, it) { CCSprite *projectile = dynamic_cast<CCSprite*>(it); CCRect projectileRect = CCRectMake( projectile->getPosition().x - (projectile->getContentSize().width/2), projectile->getPosition().y - (projectile->getContentSize().height/2), projectile->getContentSize().width, projectile->getContentSize().height); CCArray* targetsToDelete =new CCArray; CCARRAY_FOREACH(_targets, jt) { CCSprite *target = dynamic_cast<CCSprite*>(jt); CCRect targetRect = CCRectMake( target->getPosition().x - (target->getContentSize().width/2), target->getPosition().y - (target->getContentSize().height/2), target->getContentSize().width, target->getContentSize().height); // if (CCRect::CCRectIntersectsRect(projectileRect, targetRect)) if (projectileRect.intersectsRect(targetRect)) { targetsToDelete->addObject(target); } } CCARRAY_FOREACH(targetsToDelete, jt) { CCSprite *target = dynamic_cast<CCSprite*>(jt); _targets->removeObject(target); this->removeChild(target, true); } if (targetsToDelete->count() >0) { projectilesToDelete->addObject(projectile); } targetsToDelete->release(); } CCARRAY_FOREACH(projectilesToDelete, it){ CCSprite* projectile = dynamic_cast<CCSprite*>(it); _projectiles->removeObject(projectile); this->removeChild(projectile, true); } projectilesToDelete->release(); }
CCArray 和 NSArray
CCArray效率更高,这也意味着CCArray中的对象会改变位置,因此假如你依赖这些对象的特殊位置,你就不应该使用fastRemoveObject方法。
速度测试
以下代码是测试CCArray和NSArray分别遍历200个对象:
测试A(NSArray)
for(int w = 0; w<100; w++){ for(id object in arrayNS){ //Do something } }
测试B(CCArray)
ccArray *arrayData = array->data; id object; int nu = arrayData->num; for(int w = 0; w<100; w++){ CCARRAY_FOREACH(arrayData,object){ object = arrayData->arr[i]; //Do something } }
结果
以上测试表明在遍历数组的时候,CCArray比NSArray在性能上提升了大概10%。在使用CCARRAY_FOREACH和NSArray快速枚举来迭代整个数组也是有些细微的性能改善。当使用快速枚举的时候,这两种方式的数组和相同领域中的C数组基本上有相同的性能表现,而且CCArray相比纯C数组有极其细微的性能提升。
使用注意事项
CCArray一般不会被加到其他类中,所以他的引用计数是1,并且被设置了autorelease。创建CCArray对象并且retain,然后在这个类中的析构函数中调用release方法来释放内存。
CCDictionary
简介
CCDirtionary使用UTHash实现的。老版本的CCMutableDictionary使用STL实现的,但是已经被移除了。而且CCDictionary也是最近才被添加上的。由于CCDictionary没有使用模板了,因此可以轻松的绑定到script。
关键字类型
CCDictionary支持两种类型的关键字,一个是“std::string”,一个是“int”。一个CCDictionary实例对象只支持唯一的关键字。所以在你调用“setObject”方法的时候,你需要确认一下。
遍历
我们移除了之前使用“begin”“end”和“next”来遍历整个字典的方法。现在我们实现了CCDICT_FOREACH方法来遍历整个字典。而且使用CCDICT_FOREACH的方式和使用CCARRAY_FOREACH的方式非常类似。
下面代码就是举例说明怎么样遍历CCDictionary:
CCDictElement* pElement = NULL; CCDICT_FOREACH(theDict, pElement) { CCObjectSubClass* pSubClassObj = (CCObjectSubClass*)pElement->getObject(); // 你也可以得到当前key,但是你需要确定key的类型。 std::string oneStrKey = pElement->getStrKey(); // 假如key的类型是string // int oneIntKey = pElement->getIntKey(); // 假如有key的类型是integer // 下面就可以使用上面.pSubClassObj对象做一些操作了 }
CCDictionary::allkeys的方法被保留了,你也可以使用这个来遍历整个字典,但是我们强烈不建议你这样做。因为CCDICT_FOREACH的性能比使用allkeys的方法好很多。假如你想要在lua中遍历整个CCDictionary,你就不能使用CCDICT_FOREACH宏了,这种情况下,就只有使用老办法了。
CCString
简介
CCString继承至CCObject,CCObjecte这个基类主要是为了自动内存管理。CCString提供一系列的接口,例如create,convert等等。
常用的方法
创建:
/**使用std::string创建了一个字符串, 你也可以传递一个c字符串指针,因为std::string的构造函数可以访问c字符串指针 * @返回的 CCString 指针是一个自动释放对象, *也就意味着你不需要调用release操作,除非你retain了. */ static CCString* create(const std::string& str); /**使用格式化方式来创建一个字符串,这个方法和c语言里面的‘sprintf’类似,默认缓存大小是(1024*100)bytes *假如你想要改变这个缓存大小,你可以去CCString.cpp中,更改kMaxStringLen 这个宏定义。 * @返回的 CCString 指针是一个自动释放对象, *也就意味着你不需要调用release操作,除非你retain了. */ static CCString* createWithFormat(const char* format, ...); /** 使用二进制数据来创建字符串 * @返回的 CCString 指针是一个自动释放对象, *也就意味着你不需要调用release操作,除非你retain了. */ static CCString* createWithData(const unsigned char* pData, unsigned long nLen); /**使用一个文件来创建一个字符串, * @return A CCString pointer which is an autorelease object pointer, * it means that you needn't do a release operation unless you retain it. */ static CCString* createWithContentsOfFile(const char* pszFileName);
转换
CCString允许CCString实例变量转换为另外类型的变量。
/** convert to int value */ int intValue() const; /** convert to unsigned int value */ unsigned int uintValue() const; /** convert to float value */ float floatValue() const; /** convert to double value */ double doubleValue() const; /** convert to bool value */ bool boolValue() const;
常用的宏定义
#define CCStringMake(str) CCString::create(str) #define ccs CCStringMake
这些宏可以非常方便的构建一个自动释放的CCString对象。假如你想要新建很多的CCString对象,并且把他们增加到CCArray中,下面的代码就实现了这个目标,而且你的代码看起来相当简洁。
CCArray *stringArray = CCArray::create( ccs("Hello"), ccs("Variable"), ccs("Size"), ccs("!"),NULL);
内存管理
Cocos2d-x中的引用计数(Reference Count)和自动释放池(AutoReleasePool)
引用计数
引用计数是c/c++项目中一种老实的内存管理方式。当我在8年前研究一款名叫TCPMP的开源项目的时候,引用计数就存在了。
IOS SDK把这项机制封装到了NSAutoreleasePool中。所以我们也在cocos2d-x中克隆了一套CCAutoreleasePool。两者的用法基本上一样,所以假如你没有涉及过ios开发,你可以看看苹果官方文档NSAutoreleasePool Class Reference。
CCAutoreleasePool
Cocos2d-x的CCAutoreleasePool和cocoa的NSAutoreleasePool有相同的概念和API,但是有两点比较重要的不同:
1.CCAutoreleasePool不能被开发者自己创建。Cocos2d-x会为我们每一个游戏创建一个自动释放池实例对象,游戏开发者不能新建自动释放池,仅仅需要专注于release/retain cocos2d::CCObject的对象。
2.CCAutoreleasePool不能被用在多线程中,所以假如你游戏需要网络线程,请仅仅在网络线程中接收数据,改变状态标志,不要这个线程里面调用cocos2d接口。下面就是原因:
CCAutoreleasePool的逻辑是,当你调用object->autorelease(),object就被放到自动释放池中。自动释放池能够帮助你保持这个object的生命周期,直到当前消息循环的结束。在这个消息循环的最后,假如这个object没有被其他类或容器retain过,那么它将自动释放掉。例如,layer->addChild(sprite),这个sprite增加到这个layer的子节点列表中,他的生命周期就会持续到这个layer释放的时候,就不是当前消息循环的最后。
这就是为什么你不能在网络线程中管理CCObjects,因为在每一个UI线程的最后 ,自动释放对象将会被删除,所以当你调用这些被删掉的指针的时候,你就会遇到crash。
CCObject::release(), retain() and autorelease()
简而言之,这只有两种情况你需要调用release()方法
1. 你新建一个cocos2d::CCObject子类的对象,例如CCSprite,CCLayer等。
2. 你得到cocos2d::CCObject子类对象的指针,然后在你的代码中调用过retain方法。
下面例子就是不需要调用retain和release方法:
CCSprite* sprite = CCSprite::create("player.png");
这里就没有更多的代码用于sprite了。但是请注意sripte->autorelease()已经在CCSprite::create(const char*)方法中被调用了,因此这个sprite将在消息循环的最后自动释放掉。
使用静态构造函数
CCSprite::create(“player.png”)是一个使用静态构造函数的例子。所以在cocos2d-x中所有的类,除了单例,都提供了静态构造函数,这些静态构造函数包含下面4项操作:
1. 调用object新建一个对象
2. 调用object->init(…)
3. 假如初始化成功,例如,成功的找到纹理文件,那么接下来将会调用object->autorelease()。
4. 返回这个已经被标记了autorelease的对象。
所有CCAsdf::createWithXxxx(…)这种类型的函数都有以上这些方式。
在cocos2d-x v1.x或者更早版本里,这些方式是:
CCSprite* sprite = CCSprite::spriteWithTexture(...)
使用这些静态构造函数,你不需要关心“new”, “delete”和“autorelease”,仅仅关心object->retain() 和 object->release()。
一个错误的例子
一个开发者报告了一个使用CCArray 并导致crash的例子
bool HelloWorld::init() { bool bRet = false; do { // // super init first // CC_BREAK_IF(! CCLayer::init()); // // add your codes below... // CCSprite* bomb1 = CCSprite::create("CloseNormal.png"); CCSprite* bomb2 = CCSprite::create("CloseNormal.png"); CCSprite* bomb3 = CCSprite::create("CloseNormal.png"); CCSprite* bomb4 = CCSprite::create("CloseNormal.png"); CCSprite* bomb5 = CCSprite::create("CloseNormal.png"); CCSprite* bomb6 = CCSprite::create("CloseNormal.png"); addChild(bomb1,1); addChild(bomb2,1); addChild(bomb3,1); addChild(bomb4,1); addChild(bomb5,1); addChild(bomb6,1); m_pBombsDisplayed = CCArray::create(bomb1,bomb2,bomb3,bomb4,bomb5,bomb6,NULL); //m_pBombsDisplayed 是在头文件中被定义为一个 protected 变量. // <--- 我们应该添加在这里m_pBombsDisplayed->retain()方法来防止在HelloWorld::refreshData()中crash。 this->scheduleUpdate(); bRet = true; } while (0); return bRet; } void HelloWorld::update(ccTime dt) { refreshData(); } void HelloWorld::refreshData() { m_pBombsDisplayed->objectAtIndex(0)->setPosition(cpp(100,100)); }
他的错误是m_pBombsDisplayed是使用CCArray::create(…)创建的,这种创建方式是静态构造方式,这个数组被标记了autorelease。
所以这个数组会在当前消息循环的最后被CCAutoreleasePool释放掉。
当后面的消息循环调用HelloWorld::update(ccTime)的时候,m_pBombsDisplayed已经是一个野指针了,这就将引起崩溃。
为了修复这个崩溃情况,我们需要增加m_pBombsDisplayed->retain()在 m_pBombsDisplayed =CCArray::create(…);之后,并且在 HelloWorld::~HelloWorld() 的析构函数中调用m_pBombsDisplayed->release()。
纹理缓存(Texture Cache)
简介
纹理缓存是将纹理缓存起来方便之后的绘制工作。每一个缓存的图像的大小,颜色和区域范围都是可以被修改的。这些信息都是存储在内存中的,不用在每一次的绘制的时候发送到GPU中。
CCTextureCache
(Cocos2d uses a texture cache that persists CCSprite by invoke CCTextureCache or CCSpriteFrameCache. 这句话中persists你是想表达持有,还是创建喃,我感觉你是想要用创建么)
cocos2dx将使用纹理缓存来保持一个CCSprite(通过调用CCTextureCache 或 CCSpriteFrameCache的)。所以你可以预先将纹理加载到缓存中,这样你在场景中使用的时候就非常方便快捷了。怎么样加载这些纹理就看你自己的想法。例如,你可以选择异步加载方式,这样你就可以维持帧率的情况下加载场景,并增加一个进度条。
当你创建一个精灵,你一般会使用CCSprite::create(pszFileName)。假如你去看CCSprite::create(pszFileName)的实现方式,你将看到它将这个图片增加到纹理缓存中去了:
bool CCSprite::initWithFile(const char *pszFilename) { CCAssert(pszFilename != NULL, "Invalid filename for sprite"); CCTexture2D *pTexture = CCTextureCache::sharedTextureCache()->addImage(pszFilename); if (pTexture) { CCRect rect = CCRectZero; rect.size = pTexture->getContentSize(); return initWithTexture(pTexture, rect); } // don't release here. // when load texture failed, it's better to get a "transparent" sprite then a crashed program // this->release(); return false; }
上面代码显示一个单例在控制加载纹理。一旦这个纹理被加载了,在下一时刻就会返回之前加载的纹理引用,并且减少加载的时候瞬间增加的内存。(详细API请看CCTextureCache API。)
CCSpriteFrameCache
CCSpriteFrameCache单例是所有精灵帧的缓存。使用spritesheet和与之相关的xml文件,我们可以加载很多的精灵帧到缓存中,那么之后我们就可以从这个缓存中创建精灵对象了。
和这个xml相关的纹理集一般是一个很大的图片,里面包含了很多小的纹理。下面就是一个纹理集的例子:
有三种方式来加载纹理集到CCSpriteFrameCache中:
1. 加载一个xml(plist)文件
2. 加载一个xml(plist)文件和一个纹理集
3. 通过一个CCSpriteFrame和一个精灵帧的名字
具体完整API请看CCSpriteFrameCache API。
样例:
CCSpriteFrameCache* cache = CCSpriteFrameCache::sharedSpriteFrameCache(); cache->addSpriteFramesWithFile("family.plist", "family.png");
使用缓存的原因就是减少内存,因为当你使用一个图片创建一个精灵的时候,如果这个图片不在缓存中,那么就会将他加载到缓存中,当你需要用相同的图片来新建精灵的时候,就可以直接从缓存中取得,而不用再去新分配一份内存空间。
CCSpriteFrameCache vs. CCSpriteBatchNode
a)最好是尽可能创建更少的spritesheets (CCSpriteBatchNodes)。Sprite batching减少draw的调用次数。Draw的调用是非常耗时的。每一个batchnode创建调用一次draw,所以你会尽可能少的使用它,因为最终目标是使draw的调用次数减少。
b)CCSpriteBatchNode渲染所有的子节点只需要一次,只需要调用一次draw。这就是你把精灵加载到节点集合的原因,因为可以统一一起渲染。但是只有这个精灵使用的纹理包含在节点集合中的才可以添加到节点集合上,因为你只能从相同纹理批量绘制。不管引擎什么时候切换纹理,都会发起一个新的draw调用。
c)假如你把精灵添加到其他的节点上,那么每一个精灵就会调用另外的draw函数,节点集就不起作用了。
d)CCSpriteBatchNode也是一个普通的节点。你可以从场景中像其他节点一样移除掉。纹理集和精灵帧都被缓存在CCTextureCache 和 CCSpriteFrameCache单例中。假如你想要从内存中移除纹理集和精灵帧,那么你不得不通过缓存类来完成这个工作。
总结
以上观点主要是针对CCSprite的,但是一般的原则也是适用于cocos2d其他的缓存资源(纹理集, 精灵帧,动画和音效)。大多数没有缓存的这些资源也是可以简单的重新加载和更新的。
图形
设备方向
这篇文章是描述怎么样在ios和android上设置设备的方向。
正如你所知道的,有两种方法来设置设备方向:
1. OpenGL / Cocos2d-x方式
这种方式很快,但是不会旋转UIKit对象(ios)或者小部件(android)。
2. ViewController方式(ios)或者设置xml(android)
这种方法有一点慢,但是UIKit对象(ios)或者小部件(android)放在正确的位置。
openGL方式
你可以像下面那样设置设备方向:
CCDirector::sharedDirector()->setDeviceOrientation(kCCDeviceOrientationLandscapeLeft);
这种方式只能旋转cocos2d引擎渲染的对象,例如精灵。或者标签。
ViewController方式(ios)
设备方向起作用可以按下面这样方式:
告诉系统那些方向是我们需要的支持(RootViewController.mm)
// Override to allow orientations other than the default landscape orientation. - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { return UIInterfaceOrientationIsLandscape( interfaceOrientation ); }
把openGL渲染的view替换root view controller的view(AppController.mm)
// Use RootViewController manage EAGLView viewController = [[RootViewController alloc] initWithNibName:nil bundle:nil]; viewController.wantsFullScreenLayout = YES; viewController.view = __glView;
上面代码是在cocos2d-x中作为一个ios模板实现的。
通过AndroidManifest.xml设置设备的方向
<application android:label="@string/app_name" android:debuggable="true" android:icon="@drawable/icon"> <activity android:name=".ApplicationDemo" android:label="@string/app_name" android:screenOrientation="landscape" android:theme="@android:style/Theme.NoTitleBar.Fullscreen" android:configChanges="orientation"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> <pre> <strong>总结</strong> 1. 假如想要在ios中使用OpenGL的方法。那么就不要使用了RootViewController。 2. 假如想要在Android中使用OpenGL的方式,那么在AndroidManifest.xml中设置设备方向为竖直(portrait)。 3. 假如你不想要使用任何的UIkit的东西(ios)或者小部件(android),那么建议使用OpenGL的方式,这样最快。 <strong>支持多方向</strong> 以下设置多方向的方式是适用于cocos2d-x 2.0.2所创建的工作空间。 这个补充可能不会被以后的cocos2d-x所支持,或者部分被弃用。 Ios 1.注释掉cocos2dx/platform/ios/CCEGLView.mm::setContentScaleFactor(...)中的assert(m_eResolutionPolicy kResolutionUnKnown); 2.注释掉cocos2dx/platform/CCEGLViewProtocol.cpp::setDesignResolutionSize(...)中的CCAssert(m_bIsRetinaEnabled false, "can not enable retina while set design resolution size!"); 3.在shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation的方法里面返回true。 4.在(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation中增加下面的代码: <pre> CGSize s; if (UIInterfaceOrientationIsLandscape(UIApplication.sharedApplication.statusBarOrientation)) { s=CGSizeMake(std::max<float>(UIScreen.mainScreen.bounds.size.width, UIScreen.mainScreen.bounds.size.height), std::min<float>(UIScreen.mainScreen.bounds.size.width, UIScreen.mainScreen.bounds.size.height)); } else { s = CGSizeMake(std::min<float>(UIScreen.mainScreen.bounds.size.width, UIScreen.mainScreen.bounds.size.height), std::max<float>(UIScreen.mainScreen.bounds.size.width, UIScreen.mainScreen.bounds.size.height)); } cocos2d::CCDirector* director = cocos2d::CCDirector::sharedDirector(); director->enableRetinaDisplay(false); director->getOpenGLView()->setFrameSize(s.width, s.height); director->getOpenGLView()->setDesignResolutionSize(s.width, s.height, kResolutionShowAll); director->enableRetinaDisplay(true);
Android
1. 把nativeInit(w,h);添加到cocos2dx/platform/android/java/src_common/org/cocos2dx/lib/Cocos2dxRenderer.java -> voidonSurfaceChanged(GL10 gl, int w, int h).中。
2. 把下面的代码添加到void Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit(JNIEnv* env, jobject thiz, jint w, jint h):
cocos2d::CCEGLView* view = cocos2d::CCDirector::sharedDirector()->getOpenGLView(); if (!view) { ... } else { ... if (view->getFrameSize().width != w || view->getFrameSize().height != h) { view->setFrameSize(w, h); view->setDesignResolutionSize(w, h, kResolutionShowAll); } }
多分辨率的支持
这篇涉及到的API只适用于cocos2d-x 2.0.4
android的分辨率由于太多了很难来适配。但是Cocos2d-x提供了CCEGLView::setDesignResolutionSize() 和 CCDirector::setContentScaleFactor()来帮助你使用最小的工作量来让你的的游戏运行在不同分辨率下。
基本原则
自从2.0.4版本之后我们已经把所有和enableRetina方法相关的代码移除了,所以从cocos2d-2.0-x-2.0.4高清就消失了。在ios平台上,假如设备支持高清显示,默认是支持的。
你可以通过CCEGLView::sharedOpenGLView()->getFrameSize()方法来得到屏幕的真实分辨率大小。例如以上那个函数在Iphone4S的横屏状态下返回“960*640”。
但是怎么样使用非retina坐标的retina设备?这有两个概念你要知道,一个是designResolutionSize,一个是contentScaleFactor。
designResolutionSize
所有你游戏的坐标都依赖设计分辨率,而不管设备屏幕大小。假如你游戏的UI布局在所有的分辨率下都是相同的,那么你只仅仅需要设置一套坐标。设计分辨率是通过CCEGLView::sharedOpenGLView()->setDesignResolutionSize(width, height, policy)方法来设置的,第一,二个参数分别是设计分辨率的宽度和高度,第三个参数是你想要的策略。后面将会解释第三个变量。
你也可以能过searchPath.push_back(largeResource.directory),
使用好几套资源在不同设备上,这样你可以有更好的显示效果,但是contentScaleFactor。
下面就是HelloCpp项目里的代码片段。
typedef struct tagResource { cocos2d::CCSize size; char directory[100]; }Resource; static Resource smallResource = { cocos2d::CCSizeMake(480, 320), "iphone" }; static Resource mediumResource = { cocos2d::CCSizeMake(1024, 768), "ipad" }; static Resource largeResource = { cocos2d::CCSizeMake(2048, 1536), "ipadhd" }; static cocos2d::CCSize designResolutionSize = cocos2d::CCSizeMake(480, 320); bool AppDelegate::applicationDidFinishLaunching() { // initialize director CCDirector* pDirector = CCDirector::sharedDirector(); CCEGLView* pEGLView = CCEGLView::sharedOpenGLView(); pDirector->setOpenGLView(pEGLView); // Set the design resolution pEGLView->setDesignResolutionSize(designResolutionSize.width, designResolutionSize. height, kResolutionNoBorder); CCSize frameSize = pEGLView->getFrameSize(); // In this demo, we select resource according to the frame's height. // If the resource size is different from design resolution size, you need to set contentScaleFactor. // We use the ratio of resource's height to the height of design resolution, // this can make sure that the resource's height could fit for the height of design resolution. // if the frame's height is larger than the height of medium resource size, select large resource. if (frameSize.height > mediumResource.size.height) { CCFileUtils::sharedFileUtils()->setResourceDirectory(largeResource.directory); pDirector->setContentScaleFactor(largeResource.size.height/designResolutionSize.height); } // if the frame's height is larger than the height of small resource size, select medium resource. else if (frameSize.height > smallResource.size.height) { CCFileUtils::sharedFileUtils()->setResourceDirectory(mediumResource.directory); pDirector->setContentScaleFactor(mediumResource.size.height/designResolutionSize.height); } // if the frame's height is smaller than the height of medium resource size, select small resource. else { CCFileUtils::sharedFileUtils()->setResourceDirectory(smallResource.directory); pDirector->setContentScaleFactor(smallResource.size.height/designResolutionSize.height); } ................... ................... }
contentScaleFactor
ContentScaleFactor是指ResourcesSize 和 designResolutionSize的比例系数。一般的,你可以通过’ResourceBackGround.height/DesignResolution.height’ 或’ResourceBackGround.width/DesignResolution.width’的做法来设置。选择哪种方式是依靠你游戏的设计。
下面就用插图的方式来说明一下,我们这里使用高度来计算这个比例值。
图表1:资源大小=960*640,设计分辨率大小=480*320,目标设备屏幕大小=800*480,RH/DH=RW/DW=2.0f,第三个参数选择的是NoBorder模式。
当使用NoBorder模式的时候,有一些背景区域显示到屏幕以外去了。假如你使用绝对坐标在设计分辨率大小(480*320),你游戏的一些UI可能会显示不全。为了解决这个问题,你不得不设置这个坐标依据’visible rectangle’(可见矩形)的。你可以得到可见矩形的起点,通过CCDirector::sharedDirector()->getVisibleOrign()方法。调用CCDirector::sharedDirector()->getVisibleSize()方法你就可以确定屏幕上面的9个点, 左边,右边,上面,下面,中间,左上角,右上角,左下角,右下角。
假如你的游戏的所有坐标就是依靠这9个点,那么你的游戏就可以全屏显示了。
关于怎么计算这些点,你可以参考TestCpp项目里面的“VisibleRect”类。
图表2:资源大小=1024*768,设计分辨率大小=480*320,目标设备屏幕大小=800*480,RH/DH!=RW/DW=,第三个参数选择的是无边框模式。
当RH/DH不等于RW/RH的时候,你就需要选择的是相对于设计分辨率的宽度比例还是高度比例。
在图表2中,我们仍然使用高度比例来计算contentScaleFator,因此资源背景的高度将会适应设计分辨率
在将设计分辨率绘制到屏幕上之后,标记① –> 标记②, 和标记③将会在屏幕外面。
现在,你有两个选择来让你的游戏全屏。一种是让你的背景图变宽点。另外一种是根据宽度的比例去设置contentScaleFactor。
模式
现在cocos2d-x支持三种模式
Exact fit(精确匹配)
整个应用程序的内容都会在特殊区域可见,并且不用提供这些比例系数。可能会出现形变,所以的应用程序看起来可能会是拉伸或者压缩的。
No border(无边框)
当设置了屏幕高宽比之后,整个应用程序将会显示在这个特殊区域,但是没有形变,但是可能一些裁剪。
Show all(全部显示)
当设置了屏幕高宽比之后,整个应用程序也是会在这个特殊的区域可见,没有形变,不过可能会出现两条黑边。
图表3:资源大小 = 1024*768;设计分辨率大小 = 480*320;目标设备屏幕大小 = 800*480;RH/DH != RW/DW; 全部显示模式
标记② 和标记③都是黑色的矩形区域。但是他们不同的,标记③是在Opengl可视区域外面的,因此你不能在上面放任何的游戏元素。标记②的出现是因为RW/RH不等于DW/DH,但是在Opengl可视区域,你可以在上面放置你的游戏元素。
假如开发者为了更好的游戏显示使用无边框模式,那么你需要使用相对坐标。
不同平台上Cocos2d-x所支持的最大纹理大小
按道理来说,cocos2d-x是可以显示任何大小纹理,但是实际上纹理大小由于硬件和操作系统原因是有限制的。
这里我们提供一个不同平台模拟器上纹理大小限制的表格
platform | maxsize in pixels |
win32 | 2048*2048 |
android | 4096*4096 |
iPhone3 | 1024*1024 |
iPhone3GS | 2048*2048 |
iPhone4 | 2048*2048 |
在真实的机器上面,也有一些不同的限制,这里有一些测试结果:G3 1024*1024, iPhone4 2048*2048
因此对于开发者来说,假如你想要跨平台,并且游戏运行流畅,你最好保持你的纹理大小小于1024*1024,这个是大多数机器的限制。