多分辨率的自适应应该是大家比较蛋疼的问题了吧,我之前去找工作面试的时候几乎每一家公司都会问我这个问题!那么什么是自适应呢?我认为就是使用1套或者2套(SD,HD)分辨率的资源自动去适应任何分辨率的设备,画面完整清晰!
一.前辈们的文章
关于多分辨率的自适应,网上你会搜到一大篇的文章,但其实都是大家转来转去的文章,有好些转载的文章甚至没有说明出处,真的很无耻唉!尤其是使用百度搜索的时候,可能得翻好几页才能找到原文,我整理了下讲的好的就这么5篇文章:
1.官网上的这篇:http://www.cocos2d-x.org/projects/cocos2d-x/wiki/Multi_resolution_support
英文文章,用心看的话应该都能看懂,讲的是cocos2d 2.x版本自带的3种适配方案,推荐看下!
2.Alex Zhou的程序世界上的这篇:http://codingnow.cn/cocos2d-x/975.html
这篇文章主要讲的也是cocos2d 2.x版本自带的3种适配方案,讲的还是蛮清晰,图文并茂还是很值得一看的!
3.我是妖怪大大的这篇:http://dualface.github.io/blog/2012/08/17/cocos2d-x-2-dot-0-multi-resolution/
我是妖怪大大的这篇文章是主要讲的是多分辨率的原理,写的非常棒,我的很多想法都是从这篇文章中学习到的,就是源码中的实现我不是很赞同,毕竟现在手动去编写UI真的不多了!
4.无间落叶大大的这篇:http://blog.leafsoar.com/archives/2013/05-10-19.html
大家看到的最多的应该就是无间落叶大大的这篇文章了,泰然上曾经顶置了一段时间的,文章讲的十分详细,从理论知识到具体实现都有讲到,如果你能耐心看完的话,受益匪浅!
5.还有K.C大大的讲cocosbuider的这篇:http://i.kimiazhu.info/?p=119
网上讲ccb多分辨率的文章真的好少啊(几乎就这一篇文章),大部分文章就一句话:按照屏幕的四个角去设置位置或者按百分比去设置,也不知道他们到底试过了没有…….这篇文章算是稀品中的战斗机了!推荐大家看一下!
哈哈哈,前面唠叨了这么多,并不是说我的文章比他们好,鲁班门前岂容我等小辈班门弄斧?
小弟只是试着从一颗屌丝的角度出发,看能否写出一篇更通俗,全面一些的文章!如果有什么错误,欢迎前辈们不吝赐教!
二.宽高比与分辨率的知识
分辨率:一般来说我们是指设备横向和纵向的像素点的熟练,如:480×320,即横向有480个像素点,纵向有320个像素点!
宽高比:一般iphone我们是用分辨率的除它们的最大公约数得出的!如:480×320的最大公约数是160,这样它们的宽高比就是3:2! android的分辨率太多太过诡异,只能单独google了!
这样,我整理了下常用设备信息,如下:
设备 | 分辨率 | 宽高比 |
iphone4/4s | 960×640 | 3:2 |
iphone5 | 1136×640 | 16:9 |
ipad1/ipad2/ipadmini | 1024×768 | 4:3 |
ipad3/ipad4 | 2048×1536 | 4:3 |
android 1 | 800×480 | 5:3 |
android 2 | 854×480 | 16:9 |
android 3 | 1280×720 | 16:9 |
android 4 | 960×540 | 16:9 |
表1
三.cocos2d-x 2.x版本自带的自适应方法分析
请看下图,这是在DesignResolutionSize为480×320,设备尺寸480×320正常效果下的HelloCpp:
1.开启自适应
大家打开HelloCpp工程的AppDelegate.cpp文件中的applicationDidFinishLaunching()函数,会发现下面这行代码:
1
2
|
// Set the design resolution
pEGLView->setDesignResolutionSize(designResolutionSize.width,designResolutionSize.height,kResolutionNoBorder);
|
这个函数是用来设置我们的设计尺寸和适配模式的:
1).什么是设计尺寸(DesignResolutionSize)呢?
不同的设备拥有不同的分辨率,我们不可能为每一个分辨率都去设计一种界面,准备一套素材,所以我们会有一个设计尺寸,然后将设计尺寸以某种规则投影到实际尺寸上去!更详细解释请移步上文中的第一篇官方的文章!
2).适配方案(ResolutionPolicy)有哪些?
kResolutionExactFit 强制拉伸到全屏幕显示,如果设计宽高比和实际宽高比不同时画面会变形扭曲!如下图:
kResolutionShowAll按照宽和高的缩放系数的较小值去缩放画面,因为是等比缩放,所以不会失真,但是较大缩放系数的方向会出现黑边!如下图:
kResolutionNoBorder 按照宽和高的缩放系数的较大值去缩放画面,这样也不会失真,但是较小缩放系数的方向因为放大的过多会导致超出屏幕边界!如下图:
这三种适配策略是最简单的,如果必须在这3中方案中选择一个的话,我会选择kResolutionShowAll,它的效果相对来说是可以理解的,而且也不用做任何额外的工作!如果想要达到更好的效果的话呢,我们就需要费点心思了!
2.默认自适应详解
好吧,让我们来分析下上面的这几种适配方式,先看下面这行代码(CCEGLViewProtocol::setDesignResolutionSize函数):
1
2
|
m_fScaleX=(float)m_obScreenSize.width/m_obDesignResolutionSize.width;
m_fScaleY=(float)m_obScreenSize.height/m_obDesignResolutionSize.height;
|
从这个片段中可以看出cocos2d-x会用屏幕的实际尺寸(m_obScreenSize)除设计尺寸(m_obDesignResolutionSize)得到x(横)方向和y(纵)方向上的缩放系数!
1
2
3
|
// calculate the rect of viewport
floatviewPortW=m_obDesignResolutionSize.width*m_fScaleX;
floatviewPortH=m_obDesignResolutionSize.height*m_fScaleY;
|
这段代码是用来计算视口大小(可视区域外的东西是看不到的)的,不难得出如果m_fScaleX,m_fScaleY不做处理的话视口大小(viewPort)会等与屏幕的尺寸(m_obScreenSize).
1).为什么kResolutionExactFit会出现变形失真呢?
如果我们算出的m_fScaleX不等于m_fScaleY的话,将画面映射时会给宽高乘不同的缩放系数,这样就不是等比缩放了,自然会出现变形的问题!
2).为什么kResolutionShowAll会有黑边呢?
1
2
3
4
|
if(resolutionPolicy==kResolutionShowAll)
{
m_fScaleX=m_fScaleY=MIN(m_fScaleX,m_fScaleY);
}
|
从代码中可以看到kResolutionShowAll选择了较小的缩放系数作为最终的缩放因子,这样m_fScaleX恒等于m_fScaleY,不会出现变形扭曲的问题,但是原本缩放系数较大的方向因为使用了较小的缩放系数导致算出的视口尺寸的宽或高(viewPortW or viewPortH)小于屏幕的尺寸的宽或高(m_obScreenSize.width or height),于是我们的画面无法映射到全屏幕,因此会出现黑边!
3).为什么kResolutionNoBorder绘制的东西为什么会超出边界呢?
1
2
3
4
|
if(resolutionPolicy==kResolutionShowAll)
{
m_fScaleX=m_fScaleY=MAX(m_fScaleX,m_fScaleY);
}
|
从代码中可以看到kResolutionNoBorder选择了较大的缩放系数作为最终的缩放因子,所以较小缩放系数方向因为使用了较大的缩放系数从而导致算出的视口尺寸的宽或高(viewPortW or viewPortH)大于屏幕的尺寸的宽或高(m_obScreenSize.width or height),于是我们的画面的某个方向就映射到了屏幕外面,因此我们看到的效果就是画面上的有些东西一半在屏幕中,一半在屏幕外!
四.思路分析
1.去除黑边
让我们看下下面这段代码(节选自2.14版本CCEGLViewProtocol::setDesignResolutionSize函数):
1
2
3
4
5
6
7
8
9
10
|
//这是新增加的以高度为基准的适配策略,将x方向取的缩放系数设置为y方向的缩放系数
if(resolutionPolicy==kResolutionFixedHeight){
m_fScaleX=m_fScaleY;
m_obDesignResolutionSize.width=ceilf(m_obScreenSize.width/m_fScaleX);
}
//这是新增加的以宽度为基准的适配策略,将y方向取的缩放系数设置为x方向的缩放系数
if(resolutionPolicy==kResolutionFixedWidth){
m_fScaleY=m_fScaleX;
m_obDesignResolutionSize.height=ceilf(m_obScreenSize.height/m_fScaleY);
}
|
细心的你一定会发现当resolutionPolicy == kResolutionFixedWidth或kResolutionFixedHeight时,视口大小会横等于屏幕尺寸(m_obScreenSize),这也正是去除黑边的办法(如果你的版本没有kResolutionFixedWidth和kResolutionFixedHeight的话,将相关的东西复制过去即可),这样我们就可以把原来是黑边废物利用了!
这里有两种方式:
1).将黑边部分用图片填充,那里仍然不能有触摸事件,这样游戏内容的可用空间并没有增加,只是美观了些许!比如MT:
2).显示更多的游戏内容,一般用在原本游戏内容就大于视口的游戏中!比如COC类游戏:
这两种模式,一种是固定设计时的高,一种是固定设计时的宽,如果你的游戏是横屏的,我们肯定希望无用区域在左右两边,即固定高度(kResolutionFixedHeight),而不是上下,因为上下本来就很小!竖屏游戏则反之!
2.不同的东西有不同的适配策略
1).背景层
一般来说我们并不十分关心背景层上的东西,我们只希望它能够平铺整个画面
- 因此我们可以准备一张很大的背景图,足以覆盖任何设备的屏幕!
- 准备几张用来填充多余区域的图片,贴在多余区域即可!
- 考虑使用CCLayerColor,CCLayerGradient作为背景,这样会自动填充满整个窗口的!
我选择的是1的方法,这样与背景的融合图更高(点击查看大图):
2).游戏核心内容
这里才是我们要关心的,这里一般会有两种做法:
- 按照实际的屏幕尺寸的百分比去设置每个元素的位置,这样做的代价非常大!
- 如果你的游戏内容就大于屏幕尺寸的话,恭喜你,你不用做任何操作,只是玩家看到的东西更多罢了!
- 还是按照正常的方式去设置元素位置,只不过将所有的元素加入到一个节点上,最后将这个节点设置在屏幕中心位置!
我采用的是3的方法来的,所有的元素都加入到CGameLaye身上:
3).UI布局
- 一种做法是按照”米”这样9个基准点去设置相对位置(上,右上,右,右下,下,左下,左,左上,中心)!
- 按照屏幕的%百分比去设置位置.
我采用的是第二种做法,因为我的UI是在cocosbuilder中编辑的,大家可以对比下面两张图:
在分辨率差距很大的情况下,UI在两张图片上的相对位置是不变的!
(上文完,下文将详细讲述如何实现)
3).为什么kResolutionNoBorder绘制的东西为什么会超出边界呢?
里面的代码
m_fScaleX = m_fScaleY = MIN(m_fScaleX, m_fScaleY);
应该改成
m_fScaleX = m_fScaleY = MAX(m_fScaleX, m_fScaleY);
吧
@HoJanHoi
啊,十分感谢指出,确实是我的失误,已改正!
@bilt看了你两篇 COCOS2D-X 游戏实战经验(三) 多分辨率的自适应 知道了横屏是一张长长的背景 适配模式为kResolutionFixedHeight DesignResolutionSize是960×760 我想问一下那竖屏呢?DesignResolutionSize依然是960×760适配模式 改为kResolutionFixedWidth无用区域在上下,如果我想像你那篇COCOS2D-X 游戏实战经验(三) 多分辨率的自适应 (上)文章中的 MT 那张图那样 是竖屏的 无用区域在左右的 适配多分辨率,DesignResolutionSize 最佳是多少 ?适配模式是什么?
一般来说,如果打算出iapd版的话会将设计分辨率定为960×720,这样所有的设备的自适应模式都是一样的,缺点是在手机上的无效区域面积会略大! 要么我们采用960×640的设计分辨率但是在ipad上采取另一种适配方案!
@bilt还有你说 :准备一张比设计分辨率长很多,足以覆盖任何设备的屏幕!(或高很多,取决于你游戏的方向,另一个方向的尺寸和设计尺寸相同!)的图片即可! 额!足以覆盖任何设备的屏幕????这要长多少???? 或高多少??? 背景图设计多少最合适,横屏(?,720),竖屏(960,?),像MT那样的竖屏呢?DesignResolutionSize?适配模式?背景图?
这样吧,我给你举个例子,我的游戏设计分辨率是960×640,背景图的尺寸是1360×640! 这样几乎所有的设备都能满足,除非出iphone10!
设置了 kResolutionFixedHeight 后界面左对齐了,没有居中,请教下如何处理啊,界面是cocosbuilder制作的
pLyer->setPosition(屏幕中间位置);
pLyer->serAnchorPoint(ccp(0.5f,0.5f));
如果还不行:
pLyer->ignoreAnchorPointForPosition(false);