5.1 Cocos2d-x适配策略
可以在Cocos2d-x中调用CCEGLView的setDesignResolutionSize方法设置游戏的分辨率策略,以及我们的设计分辨率。
setDesignResolutionSize()方法包含3个参数,分别是设计分辨率的宽和高,以及分辨率的适配策略。下面这行代码设置了960×640的设计分辨率,并使用了SHOW_ALL分辨率适配策略。
Director::getInstance()->getOpenGLView()->setDesignResolutionSize(960, 640, ResolutionPolicy::SHOW_ALL);
5.1.1 分辨率适配策略
Cocos2d-x的分辨率适配一般不是为每一种分辨率设计一种布局方案,而是在一种分辨率下进行设计(也就是设计分辨率),然后通过分辨率适配策略,让程序能够适应不同的分辨率。Cocos2d-x提供以下5种分辨率适配策略。
❑ EXACT_FIT以设置的分辨率为标准,按照该分辨率对x和y进行拉伸。
❑ NO_BORDER不留黑边,不拉伸,等比缩放,有一个方向(上下或左右)可能超出屏幕。
❑ SHOW_ALL设置的分辨率区域内全部可见,但上下左右都可能出现黑边。
❑ FIXED_HEIGHT锁定分辨率的高度,宽度不管,可能出现黑边也可能超出屏幕。
❑ FIXED_WIDTH锁定分辨率的宽度,高度不管,可能出现黑边也可能超出屏幕。
通过图5-1~图5-3可以直观地了解到在不同分辨率下,各个分辨率适配策略的表现。以960×640为设计分辨率,然后通过调整窗口的实际分辨率,选择不同的适配模式进行观察。在PC上调用Director的setFrameSize()方法可以自定义窗口的尺寸,但不要在移动设备上设置FrameSize。
图5-1 EXACT_FIT模式
图5-2 NO_BORDER模式
图5-3 SHOW_ALL模式
首先是EXACT_FIT模式,当在不同的分辨率下运行时,界面的宽和高都会根据我们的设计分辨率进行缩放,例如,当设计分辨率是100×200,在200×300的分辨率下运行时,宽会放大2.0,高会放大1.5,当实际分辨率小于设计分辨率时,Cocos2d-x又会相应地缩小界面使其适配,如图5-1所示。
NO_BORDER模式下会根据实际分辨率进行等比缩放,不留黑边。首先按照EXACT_FIT模式的缩放规则计算出宽和高的缩放值,按照最高的缩放值进行等比缩放。当实际分辨率无法完整放下缩放后的界面时,会有一部分内容显示在屏幕外,如图5-1所示,当界面以NO_BORDER模式进行适配时,红色边框为界面的完整内容,红色边框左下角的红色原点为OpenGL窗口的原点坐标,如图5-2所示。
SHOW_ALL模式下会根据实际分辨率进行等比缩放,完全显示界面的完整内容,与NO_BORDER模式相反,其会先按照EXACT_FIT模式的缩放规则计算出宽和高的缩放值,按照最低的缩放值进行等比缩放。由于是按照最小的分辨率进行缩放,所以左右和上下都有可能出现黑边,图5-3右侧图片中的红点处为OpenGL窗口的原点坐标,如图5-3所示。
FIXED_HEIGHT和FIXED_WIDTH模式比较类似,它们会将高度或宽度锁定,按照高度或宽度进行等比缩放,另外一个方向既可能超出,也有可能留下黑边。这两种模式会先按照EXACT_FIT模式的缩放规则计算出宽和高的缩放值,FIXED_HEIGHT取高度缩放值进行等比缩放,保证设计分辨率的高度刚好铺满设计分辨率,FIXED_WIDTH取宽度进行等比缩放,保证设计分辨率的宽度刚好铺满设计分辨率。
5.1.2 坐标编码
当我们的程序在不同的分辨率下运行时,setDesignResolutionSize()方法会对整个程序按照适配策略根据设计分辨率和实际分辨率进行缩放。在对坐标进行编码时,需要使用相对坐标编码,而根据窗口尺寸可以进行相对坐标的编码,如希望将一个节点放置在屏幕的正中间,就需要将其坐标的x和y分别设置为窗口尺寸的宽和高的1/2。相对左下角原点的坐标则可以直接使用绝对坐标,设置相对位置可以使得程序在不同的分辨率下运行,我们的对象都能够显示在正确的位置上。
在使用相对坐标编码时,Director单例中有几个方法可以获取尺寸,下面了解一下这几个获取尺寸相关的方法。
❑ getWinSize,获取OpenGL窗口的单位尺寸。
❑ getWinSizeInPixels,获取OpenGL窗口的实际像素尺寸。
❑ getVisibleSize,获取可视窗口的尺寸。
❑ getVisibleOrigin,获取可视窗口左下角坐标的位置。
另外GLView对象还提供了以下两个接口来获取其他的尺寸。
❑ getFrameSize,获取设备或窗口的尺寸。
❑ getDesignResolutionSize,获取设置的设计分辨率。
如图5-4直观地演示了上面描述的各种尺寸,WinSize和WinSizeInPixels分别是当前整个OpenGL窗口的单位尺寸和像素尺寸。VisibleSize和VisibleOrigin可以共同构成当前窗口中实际可见部分内容的矩形范围,FrameSize为当前窗口或设备的真实尺寸。
图5-4 WinSize与VisibleSize
❑ WinSize分别为图5-4中左右两图的红色框范围,虽然看上去范围不同,但这是一个单位尺寸,所以值并没有变化,也就是原图尺寸960×640,一般等同于设计分辨率的尺寸,也是OpenGL窗口的单位尺寸。
❑ WinSizeInPixels也对应图5-4两图中的红色框范围,但这个尺寸为实际占用的像素尺寸,所以在不同分辨率下有不同的值(程序逻辑中使用的坐标是单位尺寸,而非像素尺寸)。
❑ VisibleSize表示可视内容的尺寸,在图5-4左图中为红色框范围,右图则为黄色框范围,也就是可以看到的有内容的显示区域尺寸。
❑ VisibleOrigin表示可视内容的左下角坐标,分别是左右图中左下角的红点的位置,左图中OpenGL窗口原点的坐标与红点重叠,而右图中OpenGL窗口的原点为红色框的左下角,VisibleOrigin的Y轴比原点高了64个像素。
❑ FrameSize为窗口或设备的实际尺寸,也就是图5-4中两个窗口的窗口大小1200× 640。
Cocos2d-x推荐使用VisibleSize和VisibleOrigin进行相对位置的计算,就是因为根据它们来计算可以保证我们的对象能够处于可视范围中。
WinSize和(0,0)坐标构成了OpenGL窗口,VisibleSize和VisibleOrigin构成了可视窗口,可视窗口不会大于OpenGL窗口,因为OpenGL窗口以外的内容都是不可见的!但OpenGL窗口范围内的对象并不一定可见,如当屏幕窗口容不下OpenGL窗口时。可视窗口可以理解为OpenGL窗口和设备实际分辨率窗口相交的矩形区域。
5.1.3 OpenGL窗口与可视化窗口
绝大部分的游戏都可以使用FIXED_HEIGHT或FIXED_WIDTH模式来实现简单的分辨率适配,只需要在背景上将可能有黑边的内容进行填充即可。这两种模式与SHOW_ALL有些类似,就是都可能导致黑边或超出,但有一种本质区别,即它们的OpenGL窗口不同,这对于坐标编码是有巨大影响的!OpenGL窗口不同,说的是原点位置不同,WinSize、VisibleSize不同。
在图5-5中,使用FIXED_HEIGHT和SHOW_ALL模式都是同样的表现,左右都会有同样的黑边,但FIXED_HEIGHT模式下的OpenGL窗口和可视化窗口对应的是图5-5中的黄色矩形区域(包括左右的黑边),而SHOW_ALL模式下的OpenGL窗口和可视化窗口对应的是图5-5中的红色矩形区域(不包括黑边)。
图5-5 OpenGL窗口和可视化窗口
最直观的表现就是,在(0,0)的位置创建一个对象,FIXED_HEIGHT模式下会出现在黄色矩形区域的左下角,而SHOW_ALL模式下会出现在红色矩形区域的左下角。SHOW_ALL模式下的黑边部分是不会出现任何显示对象的,因为不在OpenGL窗口中。而FIXED_HEIGHT模式则可以正常显示,所以只要背景图片大一些,将左右的黑边区域遮住,即可简单地解决适配黑边的问题。正是由于这种实现方式,FIXED_HEIGHT和FIXED_WIDTH模式才可以在背景上对可能有黑边的内容进行填充来解决黑边的问题。
5.1.4 setDesignResolutionSize详解
在了解了适配策略和Cocos2d-x的各种尺寸之后,下面来进一步了解setDesign-ResolutionSize()方法,setDesignResolutionSize()方法中会简单判断传入的设计分辨率的宽度和高度,以及分辨率适配策略,将这些参数保存并调用updateDesignResolutionSize()方法更新分辨率。
void GLView::setDesignResolutionSize(float width, float height, ResolutionPolicy resolutionPolicy) { CCASSERT(resolutionPolicy ! = ResolutionPolicy::UNKNOWN, "should set resolutionPolicy"); if (width == 0.0f || height == 0.0f) { return; } _designResolutionSize.setSize(width, height); _resolutionPolicy = resolutionPolicy; updateDesignResolutionSize(); }
在updateDesignResolutionSize()方法中,首先根据屏幕尺寸和设计分辨率计算出x和y方向的缩放值,然后根据分辨率适配模式选择最终的缩放值,计算完缩放值之后,再计算视口的大小。
void GLView::updateDesignResolutionSize() { if (_screenSize.width > 0 && _screenSize.height > 0 && _designResolutionSize.width > 0 && _designResolutionSize.height > 0) { _scaleX = (float)_screenSize.width / _designResolutionSize.width; _scaleY =(float)_screenSize.height/ _designResolutionSize.height; //NO_BORDER模式下取最大的缩放值等比缩放 if (_resolutionPolicy == ResolutionPolicy::NO_BORDER) { _scaleX = _scaleY = MAX(_scaleX, _scaleY); } //SHOW_ALL模式下取最小的缩放值等比缩放 else if (_resolutionPolicy == ResolutionPolicy::SHOW_ALL) { _scaleX = _scaleY = MIN(_scaleX, _scaleY); } //FIXED_HEIGHT模式下取y轴缩放值等比缩放,并将设计分辨率的宽度调整为全屏的 宽度 else if ( _resolutionPolicy == ResolutionPolicy::FIXED_HEIGHT) { _scaleX = _scaleY; _designResolutionSize.width = ceilf(_screenSize.width/_scaleX); } //FIXED_WIDTH模式下取x轴缩放值等比缩放,并将设计分辨率的高度调整为全屏的 高度 else if ( _resolutionPolicy == ResolutionPolicy::FIXED_WIDTH) { _scaleY = _scaleX; _designResolutionSize.height = ceilf(_screenSize.height/_ scaleY); } //计算视口的尺寸,并设置视口的矩形区域 float viewPortW = _designResolutionSize.width * _scaleX; float viewPortH = _designResolutionSize.height * _scaleY; _viewPortRect.setRect((_screenSize.width - viewPortW) / 2, (_screenSize.height - viewPortH) / 2, viewPortW, viewPortH); //重置Director的成员变量来适应可视化矩形 auto director = Director::getInstance(); director->_winSizeInPoints = getDesignResolutionSize(); director->_isStatusLabelUpdated = true; director->setGLDefaultValues(); } }