Cocos2d-x适配解决方案及原理分析

前言废话:

Cocos2d-x是一套跨平台开发的2D游戏引擎,我本人主要使用它开发IOS和Android两个平台。大部分手游都面临一个很重要的问题就是屏幕适配,Android碎片化问题尤为严重,屏幕分辨率多种多样。经常看到会有人在论坛问起适配相关的问题。2014年我本人打算开始写点博客来记录并分享一些技术方面的东西,眼下就从cocos2d-x开发的适配方案开始写吧。

本文程序开发环境:win7 + vs2012+cocos2d-x2.1.5

在cocos2dx 2.0以后的版本有提供官方的适配方案,我先从无适配模式开始讲,然后再解释官方的5种适配方案,最后给出2种自定义的适配的方案。

先看一段代码热身:

以下是生成的cocos2d-x工程中win32平台下main.cpp中main函数的代码,主要是红色的那句代码,通过修改传入不同的参数来模拟不同屏幕分辨率的手机。这也是在windows平台上调试适配方案最棒的一个优点~。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. AppDelegate app;  
  2. CCEGLView* eglView = CCEGLView::sharedOpenGLView();  
  3. eglView->setViewName("cocosTest");  
  4. <span style="color:#ff0000;">eglView->setFrameSize(800,480);//这句设置窗口大小</span>  
  5. return CCApplication::sharedApplication()->run();  

1.无适配模式

我们先来看下面两张图:



两张图都是在无适配模式下的运行结果,第一张图运行的窗口大小为480x320(模拟手机分辨率为480x320的设备),第二张图的窗口大小为800x480.
窗口中的图片就是默认生成的工程中自带的图片,大小为480x320。可以看到如果手机分辨率和图片大小是一样的,图片自然铺满整个屏幕, 如果屏幕分辨率变了呢?图中显示的结果也是我们能够理解的,窗口大小是800个像素宽,480个像素高,图片才480像素宽,320像素高,居中放置,自然是不能铺满屏幕的。而这也是我们适配模式需要解决的问题!(Android设备分辨率几十上百种,我相信没有美工妹纸愿意为了一个游戏出几十上百套不同尺寸的图,咳……即使妹纸愿意,App Size都不会同意的~各个游戏发布平台都是对程序安装包的大小有限定的。)

2.五种官方适配模式

在讲官方的五种适配模式之前,我们得先了解一些概念。 frameSize winSize  visibleSize visibleOrigin。
其中frameSize是最好理解的,它指的就是设备的分辨率尺寸。上图中图一的frameSize就是480x320,图二的frameSize就是800x480;
后三个概念,实际都是围绕概念winSize而产生的,而我个人认为,winSize概念也是解决适配问题的最核心的东西。那到底是什么是winSize呢?让我们暂时回到无适配模式下看程序出现了什么状况:实际屏幕分辨率各种各样,图只有一套,一套图并不能占满各种分辨率的屏幕。我们确实只有一套图,换我们自己来设计适配方案,应该怎么来解决这种问题呢?答案是:缩放!这个答案很显而易见。当在大分辨率的屏幕上时,我们缩放这张图让图片依然占满屏幕。而实际上cocos2d-x也是通过缩放来适应各种屏幕的。但如果适配模式就是单纯缩放的话,那也就没什么好讲的了。重点就在于缩放比例的控制。
下面这句话尤为重要,需要牢记:
cocos2d-x帮我们缩放了整个游戏布局,对于适配模式,我们只需要关心缩放的比例!而适配模式的核心也在于缩放比例的计算!
看了上面这句话,一个问题也被带出来了,假如我缩放比已经得到了,那我该从哪个尺寸开始缩放呢?!ok。答案其实上面已经提到了,这就是winSize!winSize这个尺寸就是我们设计游戏的一个基础尺寸。我们设定好winSize,引擎根据当前设备不同的分辨率会计算一个缩放比,然后用这个尺寸乘上计算出来的缩放比,就会得到真实的游戏界面的窗口大小。而visibleSize就是我们在设备上能看到的游戏界面的一个尺寸,visibleOrigin就是这个可见尺寸界面的左下角。到此,winSize和visibSize的理论概念已经解释了。但是!我相信没有我接下来这么几句话的解释,如果之前没做过适配,大部分人肯定会对visibleSize有错误的认识。
visibleSize和winSize是同一级的东西!它们是因为适配而设计出来的概念,与屏幕的真实像素分辨率尺寸无关!
比如现在我们使用了某个适配模式,设置的winSize为480x320.现在我们把程序拿到分辨率为960x640的设备上去运行。这个时候,引擎把我们的游戏界面缩放两倍展示出来,刚好占满屏幕,winSize是480x320,是我们自己设计的,大家都能理解。引擎就是在这个基础上进行缩放的。那此时的visibleSize是多少呢?根据上面的解释:visibleSize就是我们在设备上能看到的游戏界面的一个尺寸。我们能看到整个屏幕,屏幕的分辨率为960x640。可visibleSize并不是960x640而是480x320!请牢记,visibleSize是你能看到的整个游戏界面,这个游戏界面是指我们设计的winSize。visibleSize和winSize是同一级的,我们能看到整个winSize,那visibleSize的大小就是和winSize一样大的。
说了那么多,是时候看具体的代码和显示效果了。
下面代码是设置适配模式的关键几句,我先从最简单的ExactFit适配模式说起。
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. CCDirector* pDirector = CCDirector::sharedDirector();  
  2. CCEGLView* pEGLView = CCEGLView::sharedOpenGLView();  
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. pDirector->setOpenGLView(pEGLView);  
  2. pEGLView->setDesignResolutionSize(480,320,kResolutionExactFit);//主要是这句设置适配模式  
先简单解释下setDesignResolutionSize这个函数。这是设置适配模式的函数,前两个参数传的就是winSize的尺寸,第三个参数传的是适配模式类型(适配模式类型是引擎定义的一个枚举类型)。至于为什么前两个参数就是winSize我在介绍完几种官方适配模式的使用后,分析引擎源码的时候再详细解释。
ExactFit适配模式简单粗暴,它直接把我们给定的winSize拉伸到和屏幕真实尺寸一样大,在缩放过程中长和宽的缩放比是不一定相等的。例如下面两图:

左边图片是在960x640窗口下运行。很明显相对于480x320的winSize来说,长宽的缩放比都是2倍,这是一个等比的缩放!图片并未变形。右边图片是在800x480窗口下运行。这个窗口相对于winSize的缩放比:长->800/480 = 1.66666......宽->480/320 = 1.5.这不是一个等比的缩放。长的方向要缩放得多一些,很明显图片确实被压扁了。如果窗口再长点,图片形变将更明显。在真实的设备中,分辨率很明显不会都刚好相对于我们设计的winSize等比缩放。所以采用这种模式图片大部分会被拉伸变形。这个致命缺点就是绝大大部分项目不敢选择这个适配模式的原因。
看来这么简单粗暴的直接缩放并不是很好,我们再来看接下来的两种官方适配模式ShowAll和NoBorder。设置也很简单,就是设置适配模式的那个函数的第三个参数改下即可:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. pEGLView->setDesignResolutionSize(480,320,kResolutionShowAll);//ShowAll适配模式  
  2. pEGLView->setDesignResolutionSize(480,320,kResolutionNoBorder);//NoBorder适配模式  
为什么把这两个模式放一起来说呢,因为这两种模式实际上是刚好相对的两种,在了解了ExactFit适配模式后再来看这两种都很好容易理解。ExactFit模式的弊端在于没有等比缩放。长和宽的缩放比都不相等,确实让人家很难选择啊 快哭了!到底该怎么缩放啊?而ShowAll和NoBorder两种模式就很坚决,他们要做的就是保证是等比缩放!为什么说这两个模式是相对的呢?就是因为在出现上面那种情况的时候(winSize为480x320,实际分辨率为800x480),他们各自都是按一个缩放比来缩放游戏界面。ShowAll模式下,引擎是按照较小的那个缩放比来缩放游戏界面,也就是按1.5倍缩放,而NoBorder模式恰好相反,它会按照较大的缩放比来缩放,也就是按1.6666倍来缩放。既然都是按等比缩放,那图片肯定是不会变形的了。可屏幕的利用率就有问题了啊,请看下面两幅图:

左边的图片是在showAll模式下运行的。游戏界面是按较小的缩放比(1.5倍)来缩放的,不等比缩放的时候长应该缩放1.6666倍的,现在由于等比缩放,只放大了1.5倍,很显然长的实际宽度算出来应该是480 * 1.5 = 720个像素!所以显示出来的有游戏面两边是有黑边的(宽的缩放比大于长的缩放比的时候,是上下有黑边)。而且这个黑边还是无法通过图片遮住的,至于为什么,在之后源码解析的时候再做解释。
右边图片是在NoBorder模式下运行的,游戏界面按较大的缩放比(1.6666倍)来缩放的。同理:不等比缩放的时候宽只应该缩放1.5倍的,现在由于等比缩放,却放大了1.66666倍,所以宽计算出来的宽度为:320 * 1.666666 = 533个像素。所以整个游戏界面是超出了设备屏幕的真实大小。由于程序所用的图四周为纯色,所以看起来不明显。但是我们注意看helloworld这个label。我在设置其位置的时候是做了一定计算的,保证他能在屏幕内显示,在图中该label的位置明显已经和cocos2dx的logo重合了。所以背景图的显示大小实际上是比屏幕尺寸大的,上下两边有凸出去一部分。
相信ShowAll和NoBorder两种模式也是很好理解的。他们均保证了等比缩放。图片不被拉伸变形。在设定好了winSize的情况下,在真实设备运行中,他们分别选了其中一个缩放比来进行等比缩放。它们的优缺点也很明显。ShowAll:能保证所有的图片都显示在屏幕内,但是屏幕有可能不能充分利用,留下黑边。NoBorder:保证屏幕充分利用,但是游戏内容会超出屏幕,我们需要动态的计算精灵的坐标才能保证所有精灵显示在屏幕可视范围内。对于现在的游戏来说,基本上都是没法接受黑边的情况了,所以相比看来,NoBorder模式还算是个不错的模式。实际上最后提出的其中一个自定义适配模式就是在NoBorder的基础上修改的。

好了。官方的五种适配模式一下就讲了3种了,我觉得是时候看点引擎源码来深入了解适配方案的设计了。而且最后两种适配模式我觉得再看了源码之后再做介绍更容易理解。
我们首先要看的引擎源码是setDesignResolutionSize这个函数。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. void CCEGLViewProtocol::setDesignResolutionSize(float width, float height, ResolutionPolicy resolutionPolicy)  
  2. {  
  3.     CCAssert(resolutionPolicy != kResolutionUnKnown, "should set resolutionPolicy");  
  4.       
  5.     if (width == 0.0f || height == 0.0f)  
  6.     {  
  7.         return;  
  8.     }  
  9.   
  10.     m_obDesignResolutionSize.setSize(width, height);  
  11.       
  12.     m_fScaleX = (float)m_obScreenSize.width / m_obDesignResolutionSize.width;  
  13.     m_fScaleY = (float)m_obScreenSize.height / m_obDesignResolutionSize.height;  
  14.       
  15.     if (resolutionPolicy == kResolutionNoBorder)  
  16.     {  
  17.         m_fScaleX = m_fScaleY = MAX(m_fScaleX, m_fScaleY);  
  18.     }  
  19.       
  20.     if (resolutionPolicy == kResolutionShowAll)  
  21.     {  
  22.         m_fScaleX = m_fScaleY = MIN(m_fScaleX, m_fScaleY);  
  23.     }  
  24.   
  25.     if ( resolutionPolicy == kResolutionFixedHeight) {  
  26.         m_fScaleX = m_fScaleY;  
  27.         m_obDesignResolutionSize.width = ceilf(m_obScreenSize.width/m_fScaleX);  
  28.     }  
  29.   
  30.     if ( resolutionPolicy == kResolutionFixedWidth) {  
  31.         m_fScaleY = m_fScaleX;  
  32.         m_obDesignResolutionSize.height = ceilf(m_obScreenSize.height/m_fScaleY);  
  33.     }  
  34.   
  35.     // calculate the rect of viewport      
  36.     float viewPortW = m_obDesignResolutionSize.width * m_fScaleX;  
  37.     float viewPortH = m_obDesignResolutionSize.height * m_fScaleY;  
  38.   
  39.     m_obViewPortRect.setRect((m_obScreenSize.width - viewPortW) / 2, (m_obScreenSize.height - viewPortH) / 2, viewPortW, viewPortH);  
  40.       
  41.     m_eResolutionPolicy = resolutionPolicy;  
  42.       
  43.     // reset director's member variables to fit visible rect  
  44.     CCDirector::sharedDirector()->m_obWinSizeInPoints = getDesignResolutionSize();  
  45.     CCDirector::sharedDirector()->createStatsLabel();  
  46.     CCDirector::sharedDirector()->setGLDefaultValues();  
  47. }  


上面这个函数,就是cocos2d-x做适配的核心——缩放比例的计算。可以看到在函数的第10行先给m_obDesignResolutionSize赋了初值。这个初值就是我们传进来的参数。接下来引擎计算了缩放比,其中 m_obScreenSize是屏幕的实际分辨率尺寸,m_obDesignResolutionSize就是我们传进来的尺寸,在上一句代码赋的值。之前我有提到传进来的这两个值就是winSize的尺寸,我也说过,winSize就是我们设计的游戏基础尺寸,所以计算的这个缩放比就是整个游戏界面的缩放比,这不同分辨率的设备上我们乘以不同的缩放比来缩放我们的游戏界面以占满整个屏幕。解释下为什么传进来的值就是winSize,看以下源代码:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. CCSize CCDirector::getWinSize(void)  
  2. {  
  3.     return m_obWinSizeInPoints;  
  4. }  
这个函数就是导演类CCDirector获取winSize的方法。我们再看上面设置适配模式的函数,第44行代码,很明显,引擎把 m_obDesignResolutionSize的值赋给了winSize。
大致看来,传进来的两个参数就是winSize的值了,我继续细讲。设置适配的函数在第一次计算了缩放比之后,接下来就根据不同是适配模式参数进行了不同的操作了,我们先看ShowAll和NoBorder。它们分别令缩放比都等于一个值了,前者取小的缩放比,后者取大的。我们仔细一看,发现并没有找到ExactFit适配模式的处理分支代码,还记得前面最早说的吗,ExactFit适配模式简单粗暴,它用的缩放比就是最先计算出来的值,所以ExactFit适配模式下,引擎并没有重新去计算缩放比。再接着看源码,计算了缩放比后,我们就该使用了!接下来的两行代码:36,37行!引擎计算了viewPortW和viewPortH,这是视口的尺寸!很关键的一个东西!视口的概念很关键,简单的说:
视口就是OpenGL需要渲染的窗口大小
我们可以看到这个视口是怎么被计算出来的,它就是winSize的大小乘以我们处理后的两个缩放比。如果缩放比不处理( 在ExactFit模式下),那两个缩放比很可能不相等,那我们在计算视口大小的时候,就是winSize不等比缩放得到一个视口大小(这个时候计算出来的视口大小刚好是屏幕的分辨率大小,恰好铺满屏幕)。图片在不等比缩放的情况下自然就会变形了。而我们在ShowAll和NoBorder模式下,缩放比进行了统一,分别取小值或大值令两个缩放比相等了。所以在这种情况下,我们计算出来的视口大小就是winSize进行等比缩放得来的!所以图片是不会变形的,但会引起其他什么问题呢?由于其中一个缩放比被强行赋值和另一个相等了,那最后我们在计算视口大小的时候,肯定就不会和原屏幕分辨率一样大了。在ShowAll模式下,我们取小的缩放比,那另外一边乘以这个缩放比后得到的值肯定就比原本该有的长度要小!这个前面已经提到了。这个时候计算出来的视口大小是比屏幕分辨率尺寸小的,所以在两边(或上下)有黑边!造成这个黑边的原因是OpenGL的视口大小只有那么大,无论游戏中的图片怎么缩放或设置坐标,OpenGL始终只会渲染它视口内的内容的,所以这个黑边是无法通过图片遮盖住的!反之,NoBorder模式下游戏界面会超出实际屏幕的大小范围,道理是一样的,因为计算出来的OpenGL的视口大了。说到这里,相信大家已经能够理解ShowAll和NoBorder适配的原理了,也明白了造成他们缺点的原因了。
接下来我解释剩下的两种官方适配模式FixedHeight和FixedWidth。
这两种适配模式就像ShowAll和NoBorder一样,是一对好基友,所以我以FixedHeight为例来解释,大家就可以明白这两种适配模式了。
直接从源码setDesignResolutionSize这个函数来看。函数第25行开始,引擎进入 FixedHeight适配模式的处理分支,首先,它也把缩放比统一了!FixedHeight适配模式的直接翻译就是以高度为准,所以这个时候才不会去看缩放比谁大谁小呢!直接令缩放比都等于m_fScaleY,也就是高度方向的缩放比!接下来它又做了一个十分关键的操作!重新计算了m_obDesignResolutionSize.width!!这是什么?看看之前讲的,这就是winSize的尺寸啊!在FixedHeight模式下,他把winSize的宽度重新计算了!所以很重要的一点:
setDesignResolutionSize函数传入的前两个参数并不一定就是程序最后的winSize大小!在FixedHeight和FixedWidth模式下,引擎分别重新计算了它的宽和高!
它这样计算的目的是什么呢?我们接着往下看,来到了计算OpenGL视口的代码!我们知道,这个时候计算出来的视口大小肯定是winSize等比缩放得来的,因为缩放比在之前已经统一了。那计算出来的视口大小到底是多大呢?在 FixedHeight模式下,我们取的是高度方向的缩放比,所以我们可以肯定,视口的高度计算下来肯定是和屏幕高度一样的,那宽度呢?由于我们的winSize宽度被重新计算了,这个新的宽度乘上高度方向的缩放比到底是多少,我们咋一看还看不出个东西来。让我们来做个很简单的数学换算:源代码中:
viewPortW = m_obDesignResolutionSize.width * m_fScaleX;我们知道m_fScaleX已经被赋值为m_fScaleY了。而m_obDesignResolutionSize.width也是通过m_obDesignResolutionSize.width = ceilf(m_obScreenSize.width/m_fScaleX(此处m_fScaleX可以直接替换成m_fScaleY了));重新计算出来的。ceilf函数只是一个向上取整的函数罢了。我们忽略这个取整的误差计算。所以源码中的计算式重新换算带入如下:
viewPortW = m_obScreenSize.width/m_fScaleY * m_fScaleY!!!看看这是多少,这就是m_obScreenSize.width!是frameSize的宽度!屏幕的实际宽度!所以视口最终计算出来的大小适合屏幕大小一样大的!在往前想想,引擎为什么要重新计算winSize的宽度,那就是为了,在等比缩放的情况下,winSize乘以缩放比得出的视口大小刚好占满整个屏幕,不多也不多少!看到这,大家明白了吗?FixedWidth模式一样的道理,只不过它是取宽方向上的缩放比,重新计算了winSize的高度!通过重新计算得出新的winSize大小,使引擎在等比缩放的情况下能够得到一个刚好和屏幕一样大的视口!所以说在这两种模式下游戏界面也是等比缩放的,是不会变形的!
我接着把这个适配函数讲完,在设置了视口大小后,引擎又计算了视口的矩形!这个视口矩形就是真正意义上游戏要渲染的矩形大小。而这个矩形的左下角才是真正意义上游戏界面的左下角。在看完了5种适配模式的解释后,我们再来想想,如果现在是NoBorder模式,屏幕尺寸不标准,winSize没有等比缩放,那这个时候如果我们设置坐标(0,0)点,应该在哪儿呢?肯定在屏幕外,因为视口大小是超出屏幕了的而游戏界面的真实意义就是OpenGL渲染的视口,这个时候视口的左下角(0,0)实际上是在屏幕外的!我们之前还提到了visibleSize visibleOrigin这两个概念,一个是可视区域尺寸,一个是该区域的左下角。我们直接来看源码:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. CCSize CCDirector::getVisibleSize()  
  2. {  
  3.     if (m_pobOpenGLView)  
  4.     {  
  5.         return m_pobOpenGLView->getVisibleSize();  
  6.     }  
  7.     else   
  8.     {  
  9.         return CCSizeZero;  
  10.     }  
  11. }  
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. CCSize  CCEGLViewProtocol::getVisibleSize() const  
  2. {  
  3.     if (m_eResolutionPolicy == kResolutionNoBorder)  
  4.     {  
  5.         return CCSizeMake(m_obScreenSize.width/m_fScaleX, m_obScreenSize.height/m_fScaleY);  
  6.     }  
  7.     else   
  8.     {  
  9.         return m_obDesignResolutionSize;  
  10.     }  
  11. }  
可以看到在获取visibleSize的时候,除了在NoBorder的适配模式下,其他模式都是直接返回的 m_obDesignResolutionSize,而这个就是winSize的尺寸!在其他几种模式中,我们知道,游戏界面我们都是可以在屏幕内完全看到的!因为计算出来的视口大小除了ShowAll模式都是和屏幕一样大的(FixedHeight和FixedWidth模式重新计算了winSize大小所以等比缩放还是得到了和屏幕一样大的视口)!而ShowAll模式计算出来的视口大小是小于或等于屏幕大小的!总之都是能看全的!所以这几种模式下visibleSize的大小和winSize大小是一样大的!而在NoBorder模式下,计算出来的视口大小是大于或等于屏幕大小的!所以我们的可见区域只是游戏屏幕截取视口大小的一部分!所以这个时候的visibleSize大小是小于或等于winSize大小的!那具体等于多少呢?源码如果看起来不好想,我们可以反过来想,整个屏幕的大小就是可见区域,所以我们知道visibleSize乘上缩放比后肯定等于屏幕的实际尺寸(一定要切记visibleSize和winSize是同一级的东西,它们和真实的像素尺寸没有直接关系啊!)。所以有这个等式:frameSize = visibleSize * scale。当然这只是伪代码,意思就是说,屏幕的实际分辨率除以我们计算出来的缩放比就是我们的visibleSize了!同样的道理,visibleOrigin:可见区域的左下角。在除NoBorder模式外的其他模式中,永远都是(0,0)点,因为在这些模式中,视口用于都是小于或等于屏幕的尺寸的,我们用户能看到的左下角肯定就是视口的左下角,也自然肯定永远都是(0,0)点!但NoBorder就不一样了,视口大于屏幕尺寸的时候左下角在屏幕外面了,我们用户能看到的左下角实际上是屏幕的左下角。这可不是(0,0)点,我们需要重新计算,看一下源码便知:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. CCPoint CCDirector::getVisibleOrigin()  
  2. {  
  3.     if (m_pobOpenGLView)  
  4.     {  
  5.         return m_pobOpenGLView->getVisibleOrigin();  
  6.     }  
  7.     else   
  8.     {  
  9.         return CCPointZero;  
  10.     }  
  11. }  
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. CCPoint CCEGLViewProtocol::getVisibleOrigin() const  
  2. {  
  3.     if (m_eResolutionPolicy == kResolutionNoBorder)  
  4.     {  
  5.         return CCPointMake((m_obDesignResolutionSize.width - m_obScreenSize.width/m_fScaleX)/2,   
  6.                            (m_obDesignResolutionSize.height - m_obScreenSize.height/m_fScaleY)/2);  
  7.     }  
  8.     else   
  9.     {  
  10.         return CCPointZero;  
  11.     }  
  12. }  
道理很简单看明白了吗?

接下来讲两种用户自定义的适配模式!其实我说的自定义的意思仅仅是指在使用引擎默认适配函数的时候,用法上有点不同而已。
1.NoBorder改进模式。
这种模式并非博主原创,网上有其他博客也提到了其中这篇文章讲得比较详细,大家可以去看看:
谈下我的解释:NoBorder模式缺点就是视口过大————把winSize放得太大了,导致我们设置的游戏布局都超出了屏幕。怎么解决这个问题呢?我们既然还是在NoBorder的模式上修改,那我们从NoBorder的游戏界面入手,我们希望自己设计的游戏界面能够完整的展示出来,现在NoBorder的情况下视口已经超出了屏幕范围,那我们就竭尽所能在可视区域内截取一个最大的尺寸。怎么截取呢?想象一下,假如现在在NoBorder的情况下是左右两边超出了屏幕,我们现在开始整体缩放游戏界面!记住一定要等比例的缩放!当然,NoBorder情况下左右两边超出,那上下两边肯定是刚好合适的。我们这会开始整体缩放,上下两边肯定是在往内缩小的,没办法呢,因为左右两边还是超出屏幕的情况,我们还需要继续缩放。直到缩放到左右两边刚好贴着屏幕的左右两边,这个时候我认为这个尺寸就是在NoBorder模式下的界面中可以截取到最大的一个满足游戏界面设计比例的一个可视区域!因为如果这个尺寸放大的话,那么它的左右两边就会超出屏幕,如果它继续缩小的话,左右两边又没有完全使用到屏幕了,出现了“黑边”了,所以当前这个尺寸就是最大的一个可视区域!这个时候我们是没有考虑上下两边的,因为在一开始的时候左右两边超出,上下两边会是刚好贴着屏幕的。我们等比缩放的过程中,上下两边跟着缩放开始远离屏幕的上下两边了,这是没办法的,因为我们需要把左右两边也纳到屏幕中来,必须缩小。这个时候我们来看看最终截取到的尺寸大小,是不是很眼熟?!对!其实它就是在ShowAll模式下的一个尺寸!想想,如果我们用ShowAll的适配模式。我们需要缩放winSize来填充屏幕,具体该缩放多少呢,因为ShowAll的适配原理,我们以较小的缩放比为基准来缩放。当左右两边抵到屏幕边上的时候,这个时候上下两边还没抵到,但这个时候我们停止缩放了。我们就取这个较小的缩放比来放大。这个就是ShowAll的尺寸!那换言之!简单的来说,我们在NoBorder模式下要截取的一个最大的满足游戏界面设计比例的一个可视区域的尺寸就是ShowAll的尺寸!其实完整的NoBorder的尺寸和截取的ShowAll的尺寸是有一个可以计算的比例关系的。仔细想一想。屏幕分辨率frameSize除以winSize,分别得到了x和y两个方向上的缩放比。 NoBorder的尺寸是winSize乘上较大的缩放比所得,我们假设是x。而ShowAll的尺寸则是winSize乘上较小的缩放比所得,假设它就是y。这个时候我们用x除以y不就得到了NoBorder尺寸相对于ShowAll尺寸的一个比例关系了吗?!继续想想,现在我们可爱的美工妹纸已经帮我们设计好了游戏基础尺寸并且做好了一套是基于480x320的图片。游戏基础尺寸就是480x320.我们把这个尺寸当做是ShowAll的尺寸,乘上之前计算出来的比例系数,不是就能得到在Nobrder模式下winSize大小了吗?直接来看计算的代码:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /*NoBorder改进模式计算winSize大小*/  
  2. void AppDelegate::setupVisibleRect(CCSize frameSize, CCSize designSize)  
  3. {  
  4.     float scaleX =  frameSize.width / designSize.width;  
  5.     float scaleY =  frameSize.height / designSize.height;  
  6.   
  7.     float scale = 0.0f;  
  8.     if (scaleX > scaleY)  
  9.     {  
  10.         scale = scaleX / scaleY;  
  11.     }  
  12.     else  
  13.     {  
  14.         scale = scaleY / scaleX;  
  15.     }  
  16.   
  17.     mWinSize =  CCSize((int)(designSize.width * scale), (int)(designSize.height * scale));  
  18. }  
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. this->setupVisibleRect(pEGLView->getFrameSize(),mWinSize);//NoBorder改进模式计算winSize  
  2. pEGLView->setDesignResolutionSize(mWinSize.width,mWinSize.height,kResolutionNoBorder);  
看完代码之后再想想,我们通过设置适配模式为NoBorder模式,传入的winSize是我们计算过后的尺寸,这样就保证我们能够在NoBorder的模式下截取一个最大的可视区域,并且这个最大可视区域就使我们所预先设计的尺寸!这种适配方式再简单点的描述就是:固定一个我们所设计的基础游戏尺寸,根据不同屏幕的分辨率,计算出不同的winSize大小,保证在NoBorder模式下我们可以截取出我们所设计的游戏基础尺寸。  其实很简单,相信不清楚的多看几遍,自己敲敲代码就能想通了。

再来看第二种自定义的适配模式
2.自定义动态设置winSize模式
咋一看标题,这种模式好像和第一种模式一样,都是需要我们重新去计算winSize的大小。其实不然,第一种模式,我们还要依赖于cocos2d-x引擎的NoBorder的计算方式。也就是在适配模式中取一个较大缩放比来缩放winSize,只不过每次缩放winSize后我们都可以从其中截取一个满足我们设计比例的最大可视区域。但是第二种自定义适配模式已经完全不依赖cocos2dx默认适配的缩放比计算了!怎么动态设置呢?那就是让我们的winSize相对于设备的实际分辨率永远都是等比的!想一想,如果winSize相对于屏幕实际分辨率的长宽比永远是等比的,那我们具体采用何种适配模式不都是一样的了么?相当于设备的分辨率对我们来说永远都是一个标准比例的尺寸。代码实现其实也很简单,如下:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /*动态定义winSize大小以保证等比缩放*/  
  2. void AppDelegate::setupDynamicWinSize(CCSize frameSize, CCSize designSize)  
  3. {  
  4.     float scaleX =  frameSize.width / designSize.width;  
  5.     float scaleY =  frameSize.height / designSize.height;  
  6.     float scale = MIN(scaleX,scaleY);  
  7.     mWinSize = CCSizeMake((int)(frameSize.width/scale),(int)(frameSize.height/scale));  
  8. }  
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. this->setupDynamicWinSize(pEGLView->getFrameSize(),mWinSize);//动态计算winSize保证等比缩放  
  2. pEGLView->setDesignResolutionSize(mWinSize.width,mWinSize.height,kResolutionNoBorder);  

在这种适配模式下,其实第三个适配类型的参数随便传哪个都是无所谓的了,因为长宽缩放比是相等的!



在最后提到的两种自定义适配模式中,设备屏幕都是充分利用了的,但是我们默认设计的基础尺寸大小的背景图是无法占满全屏幕的,我们需要缩放背景图以占满全屏。所以在游戏开发时,建议背景要和游戏内容分层,让缩放背景不至于影响到游戏内容,当然也可用用另外一张更大的图来遮住黑边。

最后两种自定义适配模式讲解得都不算太详细们只是讲了原理,在实际使用中还需要注意一些细节(比如坐标偏移,比如游戏内容的缩放)。但是我相信,只要把前面官方的五种适配模式解析理解透彻了,后面的模式都是轻而易举的。
最后附上源码下载地址,大家如果要使用后两种适配模式,用熟了之后自然就可以完全理解了。

http://download.youkuaiyun.com/detail/u014075575/7057207

项目我是直接用脚本文件生成的一个多平台工程,大家下载下来要看,直接打开win32平台的工程就可以了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值