cocos2d-x屏幕适配原理分析
cocos2d-x作为著名的cocos2d游戏开发框架的C++实现者,最近一年发展迅猛。越来越多的app使用它实现快速多平台部署,从最初的ios,android,win32等到新近的html5,实现移动,客户端到浏览器全覆盖,不得了~~
开发移动应用的屏幕适配和爱情一样是一个从洪荒时代就存在的永恒命题,根本目的是实现在不同设备上用户体验的统一。
cocos2d-x在cocos2d-2.0-x-2.0.4版本之前没有提供解决的方案,开发者只能自己解决,其中一些方法见 这里
从2.0-x-2.0.4开始,勤劳的cocos2d-x团队终于着手解决这个问题了~赞! 下面用一个实例分析一下其实现原理,官方说明文档见 这里
============================== 大家好,我是正文开始之前华丽的分隔线 ===========================
新建一个带helloworld例子的工程,跑起来是这样的:
窗口尺寸为480x320,背景火蓝精灵的图片原始大小就是480x320,正好铺满屏幕。
我们就从这里开始变化。。。
首先,模拟分辨率为960x640的屏幕,看看程序会是什么样子。
会修改win32下窗口尺寸么?如果你是从2.0-x-2.0.4之前的版本直接跳过来的,比如我(源自1.0.1-x-0.11.0。。。“源自xxxx”形容酒很有档次,对于编程么。。还是要与时俱进,改进工作作风密切联系群众~),第一反应就是到AppDelegate.cpp里去找设置win32窗口大小的代码。
很遗憾,你找不到熟悉的"CC_TARGET_PLATFORM == CC_PLATFORM_WIN32",新版本把设置win32窗口尺寸移到了main.cpp里面,
如下图:
从代码结构来讲,这样更合理,因为AppDelegate.cpp是与平台无关的程序逻辑入口,里面不应该还包含大量不同平台的适配代码。
交给win32自己的main.app去设置,让上帝的归于上帝,凯撒的归于凯撒!
接上述,把eglView->setFrameSize(480,320); 改为eglView->setFrameSize(960, 640); 模拟分辨率为960x640的屏幕,运行效果:
ok,背景图尺寸是480x320,在960x640屏幕下自然就填不满,很合理~
好,需求来了:我们想要的结果是不论在什么分辨率下,用户都应该看到背景图填满了屏幕!
so,解决方案:
1,使用不同的资源适配不同的屏幕,可以通过判断屏幕的分辨率来设置加载不同目录的资源。
2,使用一套资源,在不同的分辨率下匹配屏幕进行缩放。
一个个来,先说方案一,既然要为不同分辨率使用不同资源,那就再做一张图案一样的火蓝精灵背景图,尺寸为960x640,
用于分辨率为960x640的屏幕。在工程Resources目录下新建两个目录:
"iphone"目录存放480x320的背景图和按钮图标,"iphone-retina"存放960x640的背景图和按钮图标。
这两个目录名字随意,再看到AppDelegate.cpp,修改代码如下:
再运行程序:
肿么样,框架就按照设置找到了960x640的背景图,屏幕又恢复了不空虚不寂寞的状态
这里要注意:想获取设备屏幕实际分辨率需要使用CCSize szFrame = pEGLView->getFrameSize();
而不是之前版本的CCDirector::sharedDirector()->getWinSizeInPixels(); 这个函数另有含义,后文会介绍。
but,面对全世界那么多的移动设备,那么多的分辨率,你hold住么?就算你hold住,你的美术妹纸们hold住么?
一种分辨率就出一套匹配的美术资源。。“希望你能陪我到地牢到天晃,希望你能陪我到海叫到天哑”。
就算真有以做地老天荒牌游戏为荣的团队,出来的安装包之大,也会让用户下载到地老到天荒~
so,这不是个好办法。
那看看第二种方案,对资源进行缩放,还原Resources目录的结构:
现在还是只有一张480x320的背景图,怎么改呢?AppDelegate.cpp里修改:
再次运行,amazing!屏幕再次不空虚不寂寞~
分析:
designResolutionSize是一个新的概念,它让一切与坐标,尺寸相关的数据彻底摆脱了屏幕分辨率的羁绊,或者说
由框架层来帮开发者完成转换,开发者需要的只是设置designResolutionSize。告诉框架你在什么样尺寸的场景下
做的资源,比如此例,背景图原始尺寸480x320,需求是刚好填满屏幕,那么就应该告诉框架“嗨,我设计时是以
480x320的屏幕为标准的,你帮我转转”,框架就会回答你“放心吧!” 那么框架究竟如何实现的呢? 跟踪
pEGLView->setDesignResolutionSize(480, 320, kResolutionNoBorder);
可以发现,框架是获取了实际分辨率和开发者designResolutionSize的比例,渲染的时候把图片按照这个比例来缩放绘制。
拿本例来说,屏幕960x640,designResolutionSize为480x320,缩放比例为2,那么原始大小480x320的背景图,在绘制
时就会x2来绘制,也就是实际绘制成了960x640的大小,这样就填满窗口了!
请注意这里对不同ResolutionPolicy的处理,原理后文会分析。
ok,现在面对480x320和960x640的屏幕,你已经能写出自动适配的代码了(其实只有一句,框架的好处啊~~)
又来了新的需求,“那谁谁,你改一下代码,程序要能适应ipad2 ”,WTF!好吧,问候归问候,问题总要解决。
于是你开始想“ ipad2分辨率1024x768,就按刚才帅帅凡教我的到main.cpp里修改”,好,修改为1024x768,看效果:
ms填满了,你很满意,但是不对,右下角的按钮肿么快到屏幕外面去了?!仔细一看,不是填满是填太满,背景图都超出屏幕了。
这是肿么回事呢? 根源在于pEGLView->setDesignResolutionSize(480, 320, kResolutionNoBorder);第三个参数,找到定义:
讲得很清楚了:
kResolutionExactFit:会靠拉伸来填满屏幕,本例来说背景图会变形来填充屏幕,因为1024:768=1.3, 480:320=1.5,宽高比不同,图片也就无法等比缩放来填满屏幕,只能变形了。
kResolutionNoBorder: 看不到黑边,实际就是宽高等比缩放,但缩放比例取宽比和高比之中大的那一个。
kResolutionShowAll:全部显示,可以理解为保证内容都显示在屏幕之内,实际也是宽高等比缩放,但缩放比例取宽比和高比之中小的那一个。
一般来说,我们希望设计时一屏的内容,用户在实际设备上也能在一屏内看到,拿本例来说,1024x768分辨率时,右下角的按钮却跑到屏幕外去了。看完上面的分析,你应该知道如何解决了: 对了,改变pEGLView->setDesignResolutionSize(480, 320, kResolutionNoBorder);第三个参数为kResolutionShowAll。 ok,看看效果:
bingo!背景图填满了屏幕(水平方向),按钮紧贴到右下角,可以满足基本的风格统一要求。
不过纵向上出现黑边,这个是实际分辨率的宽高比和设计分辨率宽高比不同造成的,无法通过框架层来解决。
只能交给开发者自己了,比如在代码里根据分辨率计算会出现黑边时,在黑边填充相应的图片等。一些解决方法见 这里
setPosition()的变化
之前版本可能已经习惯了CCNode::setPosition(const CCPoint &position);和CCNode::setPositionInPixel(const CCPoint &position);
但在新的版本里,只有setPosition(const CCPoint &position);
这里传入的参数不是像素,也和传统的point有不同,它指的是在designResolutionSize参照下的坐标。
验证交给你自己来:比如设计分辨率为480x320,设置一个sprite的位置为240,160,在480x320分辨率下会发现它在屏幕正中,
模拟其他分辨率,960x640,1024x768,会发现它依然在屏幕中心,这就可了解240,160这个值跟实际屏幕分辨率已经无关了,
只和designResolutionSize有关,理解这一点至关重要,是后续开发正确空间感的基础!
CC_CONTENT_SCALE_FACTOR()的变化
首先enableRetinaDisplay()在新版本里取消了,将不会有什么retina设备上scalefactor为2的说法了,
因为designResolutionSize已经解决了按不同屏幕缩放的问题,所以CC_CONTENT_SCALE_FACTOR();始终返回1。
所以新版本里的定义已经过时了,无须理会。
当然,开发者可以通过CCDirector::sharedDirector()->setContentScaleFactor()来设置contentScaleFactor,这个系数可以理解为
图片原始尺寸和designResolutionSize的比值,这个比值将用来绘制图片。
如果只是一套资源按照不同屏幕分辨率缩放,可以不用理会。
getWinSize()的变化
CCDirector::sharedDirector()->getWinSize(); 获取的是designResolutionSize
CCDirector::sharedDirector()->getWinSizeInPixels(); 获取的是getWinSize*contentScaleFactor之后的值,和老版本一样。
如果contentScaleFactor为1,则这两个函数返回的值一样。
使用kResolutionNoBorder策略时要注意的
CCSize szVisible = CCDirector::sharedDirector()->getVisibleSize();
CCPoint posVisible = CCDirector::sharedDirector()->getVisibleOrigin();
使用该策略时,因为标准背景图可能会超出屏幕,所以设置位置时需要已一个可视矩形为基准。
可以这样理解,szVisible就是你在实际设备上能看到的有效区域的宽高,posVisible就是这个有效区域的起始坐标,和szVisible构成一个可视矩形,一般来说这个可视矩形是设计分辨率下可视矩形的子集。
总结一下,cocos2d-x新的版本(对于从1.0.1-x-0.11.0上来的人)的确添加了不少新的功能,代码结构也更加合理,这些都是开发者之福,希望2dxteam继续加油!