本文出自[无间落叶](转载请保留出处):http://blog.leafsoar.com/archives/2013/05-10-19.htmlhtml
为了适应移动终端的各类分辨率大小,各类屏幕宽高比,在 cocos2d-x(当前稳定版:2.0.4) 中,提供了相应的解决方案,以方便咱们在设计游戏时,可以更好的适应不一样的环境。linux
而在设计游戏之初,决定着咱们屏幕适配的因素有哪些,简而言之只有两点:屏幕大小 和 宽高比。这两个因素是如何影响游戏的:android
- 屏幕大小: 从小分辨率 480x320 到 1280x800 分辨率,再到全高清 1080p,从手机到平板,还有苹果设备的 Retina屏,这么多不一样的分辨率,并且大小差距甚大,不可能作到一套资源走天下,资源往小了设计,在大屏幕会显示模糊,图片往大了设计,在小屏幕设备又太浪费,并且小屏幕的手机硬件资源也会相对的紧缺,因此 根据屏幕大小使用不一样的资源 是有必要的,而 cocos2d-x 也帮咱们解决了这一点。
- 宽高比: 什么是宽高比,就是你的屏幕是方的仍是长的,靠近方形的分辨率如 480x320,比例为 3:2,还有 960x540 的16:9 标准宽屏,这也算是两种总极端状况了,若是能在这两种比例状况作好适配基本就能够了,若是比 3:2 “更方”如 4:3,比 16:9 “更长”,那么不论如何布局,显示效果差距甚大,最好对固定比例优化吧。当在宽高比在必定范围内,能够经过灵活编写程序去适应,而在显示效果上,cocos2d-x 为咱们提供了三种模式,这些 模式更多的是帮咱们解决比例不一的状况而存在 的,若是只是屏幕大小(比例同样),那经过简单的放大缩小便可完成。
三种模式
说是三种模式,其实还有一种 无模式,也就是 cocos2d-x 默认的适配方案,如今咱们就来认识一下这些模式,而且经过这些模式去认识其中一些概念 FrameSize、WinSize、VisibleSize、VisibleOrigin,以及它们存在的意义,而且最后灵活运行这些概念 建立出一个不属于这些模式而超越这些模式的新适配解决方案,这是最终目的。git
kResolutionUnKnown 认识 FrameSize
这是 cocos2d-x 编写的默认模式,没有作任何处理,在这种状况下,游戏画面的大小与比例都是不可控的,在程序运行之初,由各个平台入口函数定义画面大小:github
// proj.linux/main.cpp linux 平台手动指定画面大小 CCEGLView* eglView = CCEGLView::sharedOpenGLView(); eglView->setFrameSize(720, 480); // proj.android/jni/hellocpp/main.cpp android 平台由 jni 调用传入设备分辨率参数 void Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit(JNIEnv* env, jobject thiz, jint w, jint h) { if (!CCDirector::sharedDirector()->getOpenGLView()) { CCEGLView *view = CCEGLView::sharedOpenGLView(); view->setFrameSize(w, h); AppDelegate *pAppDelegate = new AppDelegate(); CCApplication::sharedApplication()->run(); } else { // other ... } }
在此咱们首先认识了 FrameSize 参数,在游戏运行时,咱们能够经过 CCEGLView::sharedOpenGLView()->getFrameSize();
得到此值。若是在手机上运行,那么不一样分辨率将会获得不一样的值,既然这个值不可控,那么在写游戏中也就没有参考价值了,好比咱们写一个精灵的位置距离底部 320 高度,在 480x320 分辨率,能看到其在屏幕上方,若是换一台手机分辨率 960x540 那么只能显示在中间靠上的位置,若是设置精灵位置为距离屏幕上方(高度)320,反之依然,显示效果不一。函数
此时可行的方案是使用百分比,如精灵位置在屏幕横向距离左边 1/3 宽度,在 1/2 正中间处,而相似这样的设置也不用依赖 FrameSize 的具体数值。而这样的作法,使得内部元素像弹簧同样,随着 FrameSize 的大小改变而改变,伸缩或者挤压,对于图片资源大小也是彻底不可控,若是根据屏幕大小放大缩小,那咱们能够考虑用下面要说的模式,在此不推荐使用 cocos2d-x 的无模式方案。布局
kResolutionExactFit and kResolutionShowAll 认识 WinSize
在 AppDelegate.cpp 处能够经过设置:优化
CCEGLView::sharedOpenGLView()->setDesignResolutionSize(720, 480, kResolutionShowAll); // 或者 CCEGLView::sharedOpenGLView()->setDesignResolutionSize(720, 480, kResolutionExactFit);
DesignResolutionSize!顾名思义,也就是逻辑上的游戏屏幕大小,在这里咱们设置了其分辨率为 720x480 为例,那么在游戏中,我么设置精灵的位置即可以参照此值,如 左下角 ccp(0,0),右上角 ccp(720, 480),而不论 FrameSize 的大小为多少,是 720x480 也好,是 480x320 也罢,总能正确显示其位置,左下角和右上角。可以实现这一点的缘由是,固定了设计分辨率大小,从而肯定了其固定的宽高比,它的 优点 是可使用具体的数值摆放精灵位置,不会由于实际屏幕大小宽高比而是内部元素相对位置关系出现混乱。this
而为了保持画面的宽高比,cocos2d-x 作了些牺牲,牺牲了什么呢?kResolutionExactFit 牺牲了画质而保持了全屏显示,对画面进行了拉伸,这意味着什么?意味着相对极端状况下,原本精灵是方形的,显示出来变成长方形,原本圆形的变成了椭圆,固此模式不推荐使用。kResolutionShowAll 为了保持设计画面比例对四周进行留黑边处理,使得不一样比例下画面不能全屏。鱼和熊掌不能兼得也 ~spa
咱们能够经过以下方法获取到 setDesignResolutionSize 所设置的值:
CCSize winSize = CCDirector::sharedDirector()->getWinSize();
咱们能够用 Cocos2d-x 程序是如何开始运行与结束的 一文的方法,跟踪 WinSize 的初始化,获取过程,在这里简单提一下,以下步骤:
// 得到 winSize CCSize winSize = CCDirector::sharedDirector()->getWinSize(); // 查看其 getWinSize(); 方法实现 [cocos2dx-path]/cocos2dx/CCDirector.cpp CCSize CCDirector::getWinSize(void) { return m_obWinSizeInPoints; } // 而 m_obWinSizeInPoints 是什么时候被赋值的 [cocos2dx-path]/cocos2dx/platform/CCEGLViewProtocol.cpp void CCEGLViewProtocol::setDesignResolutionSize(float width, float height, ResolutionPolicy resolutionPolicy) { ... ... m_obDesignResolutionSize.setSize(width, height); ... ... CCDirector::sharedDirector()->m_obWinSizeInPoints = getDesignResolutionSize(); } const CCSize& CCEGLViewProtocol::getDesignResolutionSize() const { return m_obDesignResolutionSize; }
具体的优点:经过设置逻辑分辨率大小,相比无模式,能够帮咱们解决了屏幕自动放大缩小问题,而且保持屏幕宽高比,使得游戏更好设计,能够将设计画面大小做为默认背景图片大小等,惟一点遗憾就是那点前面所提到的一点点牺牲。
kResolutionShowAll 方案能够做为咱们的默认解决方案,使得游戏的设计更为简化,但为了补填拉伸或留黑边这点缺憾,进入下一个模式!
kResolutionNoBorder 了解 VisibleSize 与 VisibleOrigin
此模式能够解决两个问题,其一:游戏画面全屏;其二:保持设置游戏时的宽高比例,相比 kResolutionShowAll 有所区别的是,为了填补留下的黑边,将画面稍微放大,以致于可以正好补齐黑边,而这样作的后果可想而知,补齐黑边的同时,另外一个方向上将会有一部分画面露出屏幕以外,以下示意图:
黑色边框标示实际的屏幕分辨率,紫色区域标示游戏设计大小,而经过放大缩小,保持宽高比固定, 能够看到 Show All 之中的黑色阴影部分为留边,而 No Border 的紫色阴影部分则不能显示,而这紫色区域的大小是游戏设计之时是不可控的。那么原设计的画面大小就失去了 必定的 参考价值了,由于这可能让你的画面显示残缺。这时仅仅经过 WinSize 知足不了咱们的设计需求,因此引入了 VisibleSize 与 VisibleOrigin 概念。
如上所示,紫色区域是被屏幕截去的部分,不可显示的,根据实际状况,可能出现横向截取和竖向截取,这取决于实际分辨率的宽高比。而 A、B、C、D所标示的是设计分辨率,固定大小。若是咱们想让一个精灵元素显示在屏幕上方靠边,那么若是使用 WinSize 的高度设置其位置,可能出现的状况就是显示到屏幕以外了。FrameSize 和 WinSize 咱们已经知道其概念,而 VisibleSize 和 VisibleOrigin 所表明的是什么呢,又时如何为咱们解决靠边的问题!注意上图下方的定义, VisibleSize = H I J K 是用紫色标注的。 而在上图是 黑色 标注,标示屏幕实际分辨率,虽然 FrameSize 和 VisibleSize 都是 H I J K,但其意义不一样,紫色代表它是与设计分辨率相关的。
FrameSize 是实际的屏幕分辨率,而 VisibleSize 是在 WinSize 以内,保持 FrameSize 的宽高比所能占用的最大区域,实际屏幕分辨率 H I J K (黑色) 能够大于 WinSize ,但VisibleSize 必定会小于或者等于 WinSize,这二者相同的是宽高比。VisibleSize 有着 WinSize 大小(随WinSize 的大小改变而改变),还有着 FrameSize 的宽高比,它标示 在设计分辨率(WinSize)下,在屏幕中的可见区域大小。 而 VisibleOrigin 则标示在设计分辨率下被截取的区域大小,用点 K 标示,有了这些数据,咱们想让游戏元素始终在屏幕显示的区域以内不成难事。下面经过几个数值带入,加深这些概念的印象。
// 组[1] : FrameSize: width = 720, height = 420 WinSize: width = 720, height = 480 VisibleSize: width = 720, height = 420 VisibleOrigin: x = 0, y = 30 // 组[2] :相比 组 [1] FrameSize 不变 VisibleSize 和 VisibleOrigin 随着 WinSize 的变小而变小 FrameSize: width = 720, height = 420 WinSize: width = 480, height = 320 VisibleSize: width = 480, height = 280 VisibleOrigin: x = 0, y = 20 // 组[3] : 相比组 [1] WinSize 不变,VisibleSize 随着 FrameSize 的比例改变而改变 FrameSize: width = 720, height = 540 WinSize: width = 720, height = 480 VisibleSize: width = 640, height = 480 VisibleOrigin: x = 40, y = 0 // WinSize VisibleSize VisibleOrigin 与都设计的分辨率相关,知足以下关系 WinSize.width = (VisibleOrigin.x * 2) + VisibleSize.width WinSize.height = (VisibleOrigin.y * 2) + VisibleSize.height
NoBorder 具体的使用方法能够参考 cocos2d-x 自带例程 TestCpp ,有详细的使用方法,而且封装了 VisibleRect 类,能够获取设计分辨率,不一样比例屏幕之时的主要参考点,屏幕四个拐角,和边的中点等,让咱们设置元素位置时,使其总能显示在屏幕以内,这里就不详细介绍了。
基于这几种模式的程序使用方法,cocos2d-x 自带例程或者网上有不少教程,这里只详细解释了其中各类概念,而知道了这些概念,固然用起来就没有多大问题了。
kResolutionLeafsoar
!!!这是什么模式!好吧,Leafsoar 是 一叶 的 ID ,或者是本博客的一级域名而已 :P 在 cocos2d-x 中并无这种模式。除却 UnKnown 与 ExactFit 不说,ShowAll 的优点是,只须要一个设计分辨率,而后经过 WinSize 设置相对对位便可,并且位置的最大长宽都是肯定,方便了开发,但屏幕不能填满, NoBorder 模式的优点是在画面不变形的状况下,实现全屏,显示效果更好,但 WinSize 必定程度失效,须要经过运行时计算 VisibleSize 和 VisibleOrigin 来设置位置,因为是运行时计算,因此也就会出现,各类屏幕显示效果不同的状况。
ShowAll 和 NoBorder 各有所长,各有所短,而这里提出的新适配解决方案正是取二者之长,舍二者之短的 组合模式。简单说来就是用 NoBorder 去实现 ShowAll 的思想。NoBorder 能够保证全屏利用,ShowAll 能够更好的使用实际设计坐标固定位置,并且相对位置不会随宽高比的改变而改变,这在编写游戏的时候能方便很多。先上一个示意图,一目了然 (两个图,两个方向):
在原来 NoBorder 模式示意图上添加了新的概念,LsSize = X Y M N (leafsoar 简写了,为了避免跟 cocos2d-x 的一些概念混淆,什么名字不重要,只要了解其含义便可),在 NoBorder 模式下的 LsSize 相对于 FrameSize 而言,正如 在 ShowAll 模式下的 WinSize 相对于 FrameSize,因此说这是 ShowAll NoBorder 的组合概念,而这里的 LsSize 与 WinSize 的宽高比是一致的。
猛地一看,彷佛把问题复杂化了,仔细一看,还不如猛地一看 ~~
在 ShowAll 中,WinSize 做为最高的宽高,以此参照设置位置,由于在此范围内都能在屏幕上显示,用了 NoBorder 使得四周可能被截去一块区域,而这个区域大小不可控制,因此不能再使用 WinSize 做为参考点来设置位置,而这里的 LsSize 一样,由于 LsSzie 不论在什么状况下,总能显示在屏幕以内,咱们能够方便的使用 LsSize 做为坐标系参考,而且能够全屏显示,在配合 VisibleSize ,相比纯的 NoBorder 增强了很多。它能够怎么用?
能够把 LsSize 看成 ShowAll 中的 WinSize 来用,而黑边可使用稍大的图片填充,或者使用其它图片修饰边框,修饰的边框图案可大可小,可长可短,填充屏幕,保持全屏。
开始基于 LsSize 的游戏设计实现
为了可以准确实现基于 LsSize 的设计,初步计划将 LsSize 设定在 480x320 的分辨率方案,为此作了些准备,首先不使用任何模式状况下,在场景内调用以下:
CCSize size = CCDirector::sharedDirector()->getWinSize(); CCPoint center = ccp(size.width/2, size.height/2); // 大小 600x500 为了 NoBorder 看到效果,使用稍大的背景图 CCSprite* pb = CCSprite::create("Back.jpg"); pb->setPosition(center); this->addChild(pb, 0); // 480x320 此图为使用于设计分辨率 LsSize 的图片 CCSprite* pSprite = CCSprite::create("HelloWorld.png"); pSprite->setPosition(center); this->addChild(pSprite, 0); // 37x37 在 480x320 画面的四个拐角处,添加参照 CCSprite* p1 = CCSprite::create("Peas.png"); p1->setPosition(ccpAdd(center, ccp(-240, -160))); this->addChild(p1); CCSprite* p2 = CCSprite::create("Peas.png"); p2->setPosition(ccpAdd(center, ccp(240, 160))); this->addChild(p2); CCSprite* p3 = CCSprite::create("Peas.png"); p3->setPosition(ccpAdd(center, ccp(-240, 160))); this->addChild(p3); CCSprite* p4 = CCSprite::create("Peas.png"); p4->setPosition(ccpAdd(center, ccp(240, -160))); this->addChild(p4);
显示效果:(FrameSize = 640x540)
显示效果:(ShowAll; FrameSize = 520x320; WinSize = 480x320)
显示效果:(NoBorder; FrameSize = 520x320; WinSize = 480x320)
经过效果咱们能够看到,在相同 FrameSize 下 NoBorder 时,画面因为填充了黑边,将画面放大,以致于上下有部分显示不全,经过拐角四个精灵能够看出。
好!既然咱们知道是因为放大所致,那么咱们将画面缩小呢?cocos2d-x 提供了一个方法,咱们调用以下代码:
CCDirector *pDirector = CCDirector::sharedDirector();
pDirector->setContentScaleFactor(
CCEGLView::sharedOpenGLView()->getScaleY() );
为了弥补画面因须要不填空白出现的方法,咱们将画面缩小,放大系数能够经过 CCEGLView::sharedOpenGLView()->getScaleY() 取得。其实 setContentScaleFactor 方法是为了适配不一样资源而设计的,能够用此方法对不一样资源适配,缩放等。效果以下:
咱们看到 480x320 的图片显示彻底正确了,也正是咱们想要的效果,但惟一的缺点是 ~~ 拐角处四个精灵的位置依然不是咱们想要的,咱们设计的位置是以 480x320 设置位置的,而 WinSize 也是 480x320 ,而此时基于 480x320 的设计必然会显示到屏幕以外,而要想不修改精灵位置,而让其显示正确的位置,那么为了保证 LsSize 的固定,咱们须要一个方法,那就是动态设置 WinSize。
什么意思?咱们知道通常这些模式设计游戏时,是经过 setDesignResolutionSize 设置 WinSize 的,这个值在游戏运行其间是定植,动态改变的是 VisibleSize 等,而这里提出了 LsSize 的概念,可想而知,若是 WinSize 固定,那么 LsSize 会随着屏幕宽高比的改变而改变,那么咱们反其道而行,固定 LsSize 值,那么在运行时能够经过实际的宽高比来算得 WinSize 的值,这样动态算得的 WinSize 值就可以保证咱们的 LsSize 是一个定值了。
相对论,WinSize 与 LsSize 的值是相对的,与其经过固定 WinSize 在运行时动态得到 LsSize (这也是 NoBorder 的默认方式,而致使的结果是 WinSize 没有参考价值),不如咱们固定 LsSize 而在运行时算得 WinSize 设置来的要更妙一些。
如今不使用 setContentScaleFactor 方法,而修改 setDesignResolutionSize 这里的值,咱们知道 WinSize 是 480x320 时,LsSize 必然会小于此值,而 NoBorder 的放大系数咱们能够经过以下方式算得(能够参考setDesignResolutionSize方法内部实现),并在 AppDelegate 里执行:
CCSize frameSize = CCEGLView::sharedOpenGLView()->getFrameSize(); // 设置 LsSize 固定值 CCSize lsSize = CCSizeMake(480, 320); float scaleX = (float) frameSize.width / lsSize.width; float scaleY = (float) frameSize.height / lsSize.height; // 定义 scale 变量 float scale = 0.0f; // MAX(scaleX, scaleY); if (scaleX > scaleY) { // 若是是 X 方向偏大,那么 scaleX 须要除以一个放大系数,放大系数能够由枞方向获取, // 由于此时 FrameSize 和 LsSize 的上下边是重叠的 scale = scaleX / (frameSize.height / (float) lsSize.height); } else { scale = scaleY / (frameSize.width / (float) lsSize.width); } CCLog("x: %f; y: %f; scale: %f", scaleX, scaleY, scale); // 根据 LsSize 和屏幕宽高比动态设定 WinSize CCEGLView::sharedOpenGLView()->setDesignResolutionSize(lsSize.width * scale, lsSize.height * scale, kResolutionNoBorder);
显示效果:(NoBorder 模式 ;FrameSize = 520x320; LsSize = 480x320; WinSize = 动态获取)
咱们看到在没有修改源代码,而且在设计中使用 480x320 的参考系,也既是基于 LsSize 的设计显示效果如咱们预期,那么咱们换一个 FrameSize 来看看是否可以自动适应呢?以下:
显示效果:(NoBorder 模式 ;FrameSize = 600x480; LsSize = 480x320; WinSize = 动态获取)
到此,基于 LsSize 参考系的游戏设计已经完成了,这样作的好处是很明显的,集 ShowAll 和 NoBorder 的优势于一处,这里的图片元素是为了好定位,实现的须要而写的,具体场景可使用背景地图,或一张大的图片显示,而没有任何影响,也能够继续使用 VisibleSize 获得 LsSize 以外的部分区域大小,在 LsSize 以外可使用背景图片做为装饰,即保证了游戏的全屏,又保证了游戏设计时的方便,若是使用彻底基于 LsSize 的设计实现,除了显示背景装饰以外,咱们不想让 LsSize 的内部元素显示到 LsSize 以外如何作呢?咱们只须要设定 LsSize 层的的显示区域便可,咱们能够修改场景的实现:
// 这里先简单实现思路 CCScene* HelloWorld::scene() { CCScene *scene = CCScene::create(); // 建立背景层 CCLayer* b = CCLayer::create(); scene->addChild(b); // 添加背景图片和设置位置,可使用其它装饰,或者小图片屏幕都行 CCSize size = CCDirector::sharedDirector()->getWinSize(); CCPoint center = ccp(size.width/2, size.height/2); CCSprite* pb = CCSprite::create("Back.jpg"); pb->setPosition(center); b->addChild(pb, 0); // 建立 LsLayer 层 HelloWorld *lsLayer = HelloWorld::create(); scene->addChild(lsLayer); return scene; } // 在 HelloWorld 中重写 visit() 函数 设定显示区域 void HelloWorld::visit() { glEnable(GL_SCISSOR_TEST); // 开启显示指定区域 // 在这里只写上固定值,在特性环境下,以便快速看效果,实际的值,须要根据实际状况算得 glScissor(20, 0, 480, 320); // 只显示当前窗口的区域 CCLayer::visit(); // 调用下面的方法 glDisable(GL_SCISSOR_TEST); // 禁用 }
显示效果:(NoBorder 模式 ;FrameSize = 520x320; LsSize = 480x320; WinSize = 动态获取)
屏幕适配新解
看完这篇文章想必对 cocos2d-x 的屏幕适配方案及其原理有了至关的认识,从内部提供的三种模式,再到咱们自定义基于 LsSize 的 Leafsoar 模式 (好把,因该叫作 ShowAllNoBorder)。这里已经给出了彻底的实现原理以及实现方法,并配有效果图,固然这其中还有些细节须要注意,好比咱们基于 LsSize 的大小设计,那么实际的图片确定须要比 LsSize 的要大,大多少,过小了不够适应,太大了又浪费,如何取舍等问题,这一点取决的因素是什么,留给读者思考 ~~
一叶将在 GitHub 处创建一个ScreenSolutions 项目,读者能够从这里参考实现的方案。(也许此时在 GitHub 所看到的实现并不彻底,但已经有了简单的实现方法,而且可以运行,若有必要,将会新写一篇博客,去实现 ScreenSolutions 而且解说)