cocos2d-x多分辨率自适配及因此导致的CCListView的bug修复
cocos2d-x是一款众所周知的跨平台的游戏开发引擎。由于其跨平台的特性,多分辨率支持也自然就有其需求。
因此,在某一次更新中(抱歉,笔者已经忘了是哪次更新了),cocos2d-x添加了一个新的方法,可以非常简便地让cocos2d根据屏幕尺寸的大小做自适配。
方法名:
1 virtual void setDesignResolutionSize(float width, float height, ResolutionPolicy resolutionPolicy);
该方法所传的参数中,前两个参数width、height,指的是开发者在设计界面时默认设计的尺寸。例如:开发者在设计界面时以iphone5为准,则width和height就是568*320;若是以iphone4为准,则width和height就是480*320;以此类推。
第三个参数resolutionPolicy指的是cocos2d在缩放时需要遵守的规则。ResolutionPolicy这个自定义结构如下:
1 enum ResolutionPolicy
2 {
3 // The entire application is visible in the specified area without trying to preserve the original aspect ratio.
4 // Distortion can occur, and the application may appear stretched or compressed.
5 kResolutionExactFit,
6 // The entire application fills the specified area, without distortion but possibly with some cropping,
7 // while maintaining the original aspect ratio of the application.
8 kResolutionNoBorder,
9 // The entire application is visible in the specified area without distortion while maintaining the original
10 // aspect ratio of the application. Borders can appear on two sides of the application.
11 kResolutionShowAll,
12
13 kResolutionUnKnown,
14 };
通过注释可以看出:
kResolutionExactFit是指cocos2d缩放的时候不考虑画面失真,直接用画面填充满整个屏幕
kResolutionNoBorder是指cocos2d缩放的时候尽量顶满画面,如果因此有超出画面的部分,就会把超出的部分切除掉(举例来说:如果设计大小是568*320,在480*320大小的屏幕上自适配的时候,就会把宽度超出的部分切除)
kResolutionShowAll是指cocos2d缩放的时候显示所有的画面,如果有填不满屏幕的地方就会显示黑条。
这三种规则各有优缺点,具体使用哪种规则需要考量项目的需要进行合理的选择。
然而,需要特别注意的一点是:setDesignResolutionSize和enableRetina这两个方法是不可以同时使用的。这即意味着使用自适配的时候是不可以做高清版的。
如果查看cocos2d-x源代码,可以看到在setDesignResolutionSize方法中有一句代码:
1 CCAssert(m_bIsRetinaEnabled == false, "can not enable retina while set design resolution size!");
在下认为这是因为高清模式下图片资源都是进行了缩小的,所有的坐标都是定义的point,而不是pixel。因此高清模式和自适配可能有某些冲突存在。(以上只是笔者的猜测,不靠谱勿怪)
总之,如果想要使用自适配,就必须在项目一开始就放弃高清。如果要做高清版,目前看来就只能自己根据屏幕尺寸的比例变化设置setContentScaleFactor,这一部分的设置方法这里就不展开了。
另外,笔者在刚开始使用自适配功能时,误以为开发时要把UI元素的位置坐标和大小都写成相对于屏幕大小的一定比例。例如:
1 CCSprite* avatarSprite = CCSprite::create("avatar.png");
2 avatarSprite->setPosition(ccp(winSize.width * 0.000878, winSize.height * 0.653125));
3 addChild(avatarSprite);
但实际上这是没有必要的。自适配功能是直接调整整个CCDirector的画布大小,因此任何添加到CCDirector中的元素都会被缩放相应的比例,没有必要再特意将坐标写成相对屏幕比例的模式。
下面笔者将分享一下在使用自适配功能的时候遇到的一个问题,即CCListView在多分辨率模式下可能出现的被过多地切除一部分内容的问题。
首先先介绍下笔者发现问题时的背景:当时笔者是在实现一款跨平台的游戏的其中一个界面,该游戏的画面是按照iphone5的低清版设计,DesignResolutionSize设置的是568*320。在安装到iphone5上时一切正常,但是随后,在安装到iphone4机器上进行调试时,发现画面上所有的CCListView都被切除了左边的一部分内容。
由于在iphone5上一切正常,在iphone4上画面被切除了一部分,因此笔者立即怀疑是CCListView与setDesignResolutionSize之间有某些冲突。
通过debug和log,最后将问题锁定到了CCListView类的visit方法。方法的源代码如下:
1 void CCListView::visit(void)
2 {
3 if (!m_pListViewParent)
4 {
5 CCRect rectSelf;
6 float factor = CC_CONTENT_SCALE_FACTOR();
7 rectSelf.origin = convertToWorldSpace(CCPoint(0,0));
8 rectSelf.origin.x *= factor;
9 rectSelf.origin.y *= factor;
10 rectSelf.size = this->getContentSize();
11 rectSelf.size.width *= factor;
12 rectSelf.size.height *= factor;
13 glScissor((GLsizei)rectSelf.origin.x, (GLsizei)rectSelf.origin.y, (GLsizei)rectSelf.size.width , (GLsizei)rectSelf.size.height);
14 glEnable(GL_SCISSOR_TEST);
15 }
16 CCLayerColor::visit();
17 if (!m_pListViewParent)
18 {
19 glDisable(GL_SCISSOR_TEST);
20 }
21 }
这段代码中调用了一个方法:glScissor。这个方法是框定了CCListView将会显示在屏幕上的范围。而由于在使用自适配模式,对CC_CONTENT_SCALE_FACTOR没有做任何修改,因此这个框出来的范围是对应于DesignResolutionSize设定的原始设计画面大小的,而在屏幕尺寸发生了变化,整个CCDirector被缩放的情况下,这个rectSelf却没有做任何改变,所以就导致了上述的被切除一部分的问题。
随后,笔者通过搜索,发现已经有人遇到了同一个问题。而且还提供了修复的方法:http://www.cocos2d-x.org/boards/18/topics/14464
简单讲就是,用CCEGLView::sharedOpenGLView()->setScissorInPoints方法替换glScissor方法。其它任何地方都不用变。
修改好以后在iphone4和5上调试都没问题,因此这个bug本身可以算是修复了。
PS:由于对这个方法能够奏效感到好奇,笔者查看了setScissorInPoints方法的源文件,代码如下:
1 void CCEGLViewProtocol::setScissorInPoints(float x , float y , float w , float h)
2 {
3 glScissor((GLint)(x * m_fScaleX + m_obViewPortRect.origin.x),
4 (GLint)(y * m_fScaleY + m_obViewPortRect.origin.y),
5 (GLsizei)(w * m_fScaleX),
6 (GLsizei)(h * m_fScaleY));
7 }
可以看出这个方法内部仍然是调用了glScissor方法,但是传递的参数则考虑到了屏幕尺寸的变化,因此可以保证通过这个方法不会切除过多的CCListView的内容。
另外,在工程中搜索glScissor时发现CCScrollView也调用了这个方法,因此如果使用了CCScrollView推荐也替换掉源代码中glScissor方法。