您的位置:首页 > 移动开发 > Cocos引擎

Cocos2d-x 屏幕适配

2015-09-18 20:27 483 查看
转载:http://blog.leafsoar.com/archives/2013/05-13.html

为了适应移动终端的各种分辨率大小,各种屏幕宽高比,在 cocos2d-x(当前稳定版:2.0.4) 中,提供了相应的解决方案,以方便我们在设计游戏时,能够更好的适应不同的环境。

而在设计游戏之初,决定着我们屏幕适配的因素有哪些,简而言之只有两点:屏幕大小 和 宽高比。这两个因素是如何影响游戏的:

屏幕大小: 从小分辨率 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,以及它们存在的意义,并且最后灵活运行这些概念 创建出一个不属于这些模式而超越这些模式的新适配解决方案,这是最终目的。


kResolutionUnKnown 认识 FrameSize

这是 cocos2d-x 编写的默认模式,没有做任何处理,在这种情况下,游戏画面的大小与比例都是不可控的,在程序运行之初,由各个平台入口函数定义画面大小:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

// 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 宽度,在 ½ 正中间处,而类似这样的设置也不用依赖 FrameSize 的具体数值。而这样的做法,使得内部元素像弹簧一样,随着 FrameSize 的大小改变而改变,伸缩或者挤压,对于图片资源大小也是完全不可控,如果根据屏幕大小放大缩小,那我们可以考虑用下面要说的模式,在此不推荐使用 cocos2d-x 的无模式方案。


kResolutionExactFit and kResolutionShowAll 认识 WinSize

在 AppDelegate.cpp 处可以通过设置:

1
2
3

CCEGLView::sharedOpenGLView()->setDesignResolutionSize(720, 480, kResolutionShowAll);
// 或者
CCEGLView::sharedOpenGLView()->setDesignResolutionSize(720, 480, kResolutionExactFit);

DesignResolutionSize!顾名思义,也就是逻辑上的游戏屏幕大小,在这里我们设置了其分辨率为 720x480 为例,那么在游戏中,我么设置精灵的位置便可以参照此值,如 左下角 ccp(0,0),右上角 ccp(720, 480),而不论 FrameSize 的大小为多少,是 720x480 也好,是 480x320 也罢,总能正确显示其位置,左下角和右上角。能够实现这一点的原因是,固定了设计分辨率大小,从而确定了其固定的宽高比,它的 优势 是可以使用具体的数值摆放精灵位置,不会因为实际屏幕大小宽高比而是内部元素相对位置关系出现混乱。

而为了保持画面的宽高比,cocos2d-x 做了些牺牲,牺牲了什么呢?kResolutionExactFit 牺牲了画质而保持了全屏显示,对画面进行了拉伸,这意味着什么?意味着相对极端情况下,本来精灵是方形的,显示出来变成长方形,本来圆形的变成了椭圆,固此模式不推荐使用。kResolutionShowAll 为了保持设计画面比例对四周进行留黑边处理,使得不同比例下画面不能全屏。鱼和熊掌不能兼得也 ~

我们可以通过如下方法获取到 setDesignResolutionSize 所设置的值:

1

CCSize winSize = CCDirector::sharedDirector()->getWinSize();

我们可以用 Cocos2d-x 程序是如何开始运行与结束的 一文的方法,跟踪 WinSize 的初始化,获取过程,在这里简单提一下,如下步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2122
23
24
25
26
27
28
29

// 获得 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

// 组[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 的分辨率方案,为此做了些准备,首先不使用任何模式情况下,在场景内调用如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2122
23
24
25
26
27
28
29
30

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 提供了一个方法,我们调用如下代码:

1
2
3

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 里执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2122

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 层的的显示区域即可,我们可以修改场景的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2122
23
24
25
26
27
28
29
30
31

// 这里先简单实现思路

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 并且解说)

在读这篇文章之前,先读前一篇文章 Cocos2d-x 屏幕适配新解 是必要的。

如果说前一篇文章文章在 LsSize 提出之前的是基础,LsSize 是应用,那么对于这篇文章来说,LsSize 是基础,而这里是其的综合应用,我之初衷是其扩展性和兼容性,激发读者思维。也许你并没有体会出 LsSize 的强大,而实际上,它能做的比你想象的要多的多,这是前话
~


ShoAll 模式的兼容

首先 LsSize 能满足 ShowAll 模式的需要,因为开始就是把 LsSize 当作 ShowAll 中的 WinSize 来设计的。 并且可以为背景做装饰,而在游戏设计之时并没有什么区别,LsSize 可以设定显示区域的大小,使背景层与 LsSize 分离(这一点在上一篇文章最后已经提到),从而保证了游戏的元素不会超出 LsSize 而露出到 VisibleSize 的区域内。


NoBorder 模式兼容

为什么说 NoBorder 兼容模式,它本身不就是 NoBorder 么,它与实际的 NoBorder 区别又在何处,有何优势?首先说说兼容,使用此模式,并不影响 你继续使用
VisibleSize 和 VisibleOrigin(以后简写 Visible),你可以不使用 LsSize 的参考点,而使用 Visible 的相关值获取屏幕的拐点,游戏元素按照
Visible 来设置也可。下面详细介绍基于 LsSize 的 NoBorder 和原油 NoBorder 的区别以及其优势。

我们设想这样一个 实际情况 。我们需要一套资源图片,做为在适合分辨率的资源展示,当屏幕的大小分辨率在 854x480,800x480,728x480 (横屏下:为什么高度同样是
480 而宽度有这么多值 : P 这也是屏幕适配万恶之一了吧) 时,我们使用一套资源,当高度小于 480 时,我们使用另一套小的资源是合理的设计。而这里我们的资源宽姑且先不论,高一定是 480 最为合适了,最接近此分辨率的图片。那我们使用 NoBorder 的时候该 设置
WinSize 为多少 了呢?

基于 854x480 设计!好,那么当程序跑在 854x480 的屏幕上,正好满屏显示,而图片资源并没有放大或者缩小,或者说基于像素点点对点显示的。但是当这样设计的 WinSize 跑在 800x480
和 728x480 分辨率会如何?也许已经知道了,为了保证小于 854 那一小块区域的显示,画面将会缩小那么一点点,也许在如今屏幕的 ppi 日益渐高的情况下,并不十分明显,但画面一定是有那么一点模糊了。同理可以遇见,不论
WinSize 如何设置,在 三种少许不同分辨率下,显示的效果肯定略有不同。 而分辨率差别越大,这种效果就越明显。

注:细心的朋友已经读出上文描述中出现的 Bug ,并多谢指出问题的朋友。下面修复 Bug 并重新描述问题的情况 ~

折衷方案,我们基于 800x480 设计,那么此时出现的情况是,当跑在 800x480 的屏幕上时,正好满屏显示,而图片并没有任何放大或者缩小,或者说基于像素点点对点显示的。而当这样设计的 WinSize 跑在 728x480 和 854x480 的分辨率会如何? 854x480 相比 800x480,前者的宽高比要大于后者,所以它是宽对齐的,这意味着,画面有所放大,而上下将会有一部分残缺,此时设计高度将会失去参考价值。728x480
相比 800x480 ,前者的宽高比小于后者,所以它是高对齐的,此时画面并没有缩放,只是横向截取了小部分,这样的情况是由于 NoBorder 的实现机制所决定。当然我们可以将设计的宽度设置的很宽很宽,以保证高对齐,哈~但是魔(设计宽度)高一尺,道(实际宽度)高一丈,倒不如使用后文提到的“固定高度”方式了~ 而这里的
854x480,800x480,728x480 等数据只是屏幕适配等问题的“缩影”。

读到这里也许已经发现了,LsSize 已经完美 (这里的完美,并非只此特殊情况下的解决方法,而是总览全文,基于 LsSize 的设计理念,其兼容性和可扩展性,显然在此时,固定高度是更好的实现方式) 的解决了这个问题,动态
WinSize ,一个合理的设计,我梦将 LsSize 设定为 720x480,并且使用 高度为 480 的图片资源,而宽度可以往大了设计,比如 854x480 的图片资源,读过前文 LsSize 的实现原理,我们可以知道,在这三种情况下,屏幕的画面并没有缩放,因为实际的宽度总是大于
720 ,从而达不到缩放的条件。480 高度的图片,能够正好填充 480 高度的屏幕,而图片的宽度往大了设计,在宽度稍微小的屏幕下,会被截取一部分,但就显示效果来说,并没有什么损失,而游戏的元素位置可以用原来的方法基于 Visible 来设计。

为什么可以做到如此!原本的 NoBorder 通过固定 WinSize 根据屏幕宽高比缩放,所以会有不同程度的缩放,而 LsSizeNoBorder 的设计实现,通过屏幕宽高比来获得 WinSize 的值,以保证 LsSize 总能在屏幕上正好全部显示。

LsSizeNoBorder 比 NoBorder 好,好多少,这就仁者见仁,智者见智了 ~


kResolutionFixedHeight,kResolutionFixedWidth 扩展兼容模式

FixedHeight 和 FixedWidth 是什么模式,如果你试用了最新版的 cocos2d-x (2.1.3)就能发现这两种模式,一种是固定设计时的高,一种是固定设计时的宽。而在当前的 2.0.4 并没有这两种模式,而现在,你可以通过 LsSize 来实现这两种模式。存在既是合理,FixedHeight 和 FixedWidth 的存在是合理的,比如我们写一个横版过关游戏,同样是三种分辨率 854x480,800x480,728x480 使用固定设计高度的方法,可以避免在
NoBorder 中会根据宽度而做的缩放。

而在 LsSizeNoBorder 中好似也实现了相同的功能!但注意有一点区别,在 LsSizeNoBorder 中,实际屏幕高度为 480 ,如果宽度小于 720 时,那么画面会缩放,而这里新模式固定高度不会。如果我们想让 LsSize 实现这种功能怎么做,我们将对 LsSize 的设计稍作扩展,上篇文章的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2122

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);

我们的实际缩放系数,是根据 scaleX 和 scaleY 的大小来判断,依据哪个方向缩放,从而在显示效果上是高对齐还是宽对齐。而想要固定是高度对其还是宽度对其,那只要换用如下方法即可:

1
2
34
5
6
7
8
9
10
1112
13
14
15

// 要实现这种功能,我们需要做的就是算得 缩放系数,缩放系数由 原来的设计稍作演变即可
// 由于 NoBorder 的缩放是根据 scaleX 和 scaleY 的熟大熟小来判断缩放系数是参照横向还是竖向
// 固我们需要两个先决条件,固定的方向 和 缩放的参照方向,而得到如下算法

// 固定高度
if (scaleX > scaleY)
scale = scaleX / (frameSize.height / (float) lsSize.height);
else
scale = scaleX / (frameSize.width / (float) lsSize.width);

// 固定宽度
if (scaleX > scaleY)
scale = scaleY / (frameSize.height / (float) lsSize.height);
else
scale = scaleY / (frameSize.width / (float) lsSize.width);

下面通过几张效果图展示 固定高度 和 固定宽度 效果:

显示效果:(NoBorder 固定高度模式 ;FrameSize = 520x320; LsSize = 480x320; WinSize = 动态获取)


显示效果:(NoBorder 固定高度模式 ;FrameSize = 520x360; LsSize = 480x320; WinSize = 动态获取)


显示效果:(NoBorder 固定宽度模式 ;FrameSize = 480x360; LsSize = 480x320; WinSize = 动态获取)


显示效果:(NoBorder 固定宽度模式 ;FrameSize = 480x300; LsSize = 480x320; WinSize = 动态获取)


如图所示,我们固定了一个方向,使得这个方向上的设计长度正好填充屏幕,而另一个方向上会有所延伸或截取,而此时如果想或者屏幕拐点,可以配合 Visible 的显示区域算得。而这也正式 cocos2d-x 2.1.3 所实现的功能,而如果你此时为了稳定而使用 2.0.4 stable 版本,那么就可以通过这种基于
LsSize 的设计方法实现 FixedHeight 与 FixedWidth。 而在将来后续版本稳定,也可以很平滑的升级到使用自带的方式替换,其显示效果一样,只是后续版本 cocos2d-x 在内部将它封装了而已。


kResolutionLeafsoar 模式的核心思想

透过现象看本质!基于固定 LsSize 的动态 WinSize 设计。之所以能够兼容这么多模式并且有所加强,在于 LsSize 在 FrameSize、WinSize、VisibleSize、VisibleOrigin等概念之外的存在,并且通过动态计算 scale 而游走于此等之间。它的存在并不依赖于 这些已有概念,而反过来,让已有的概念去依赖 LsSize 。从而保持设计上的灵活性与扩展性。
以下转载:http://blog.justbilt.com/2013/08/02/cocos2d-x-游戏实战经验三-多分辨率的自适应下#

首先我们要设置我们的适配模式为kResolutionFixedHeight:

1

pEGLView->setDesignResolutionSize(960, 720, kResolutionFixedHeight);

假定我们的游戏为分辨率(960,720)的横版游戏,至于为什么是960x720,最后再告诉大家,这里且让小弟卖个关子!

1.背景层

对于背景层来说这个问题So Easy,我们只用准备一张比设计分辨率长很多(或高很多,取决于你游戏的方向,另一个方向的尺寸和设计尺寸相同!)的图片即可!

这里我们随便找张素材:





大家在hellocpp工程中的HelloWorld::init函数末尾加入如下代码:

1
2
3

CCSprite* pSprite = CCSprite::create("background.jpg");
pSprite->setPosition(ccp(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
this->addChild(pSprite, 0);

下图是的main.cpp中设置的窗口大小为标准960x720和其他分辨率下的效果:













哈哈哈,看我们的设计区域是不是都被完整的显示出来了呢?大家可以用(上)文章中提到的分辨率挨个试一遍,都是没有问题的!(Yes,妈妈再也不用担心我的多分辨率适配了!)

2.游戏内容

关于这部分的内容,太多太复杂,真的不好讲啊!我简单讲来一下,要有什么不懂得,请在评论中说明或者@justbilt私信给我,我会在第一时间回复的!我们需要一个节点(CCNode),用来将游戏的所有内容都添加到上面,再将这个节点添加到屏幕的中心! 这个节点可以是CCLayer,CCNode,或者CCTMXTiledMap都行!这里要注意以下几点:

1).**选择**根节点

如果根节点选择的是CCLayer,CCNode之类的,要去设置它们的setContentSize(designResolutionSize);

1
2
34
5

CCNode* pGameRoot=CCNode::create();
pGameRoot->setAnchorPoint(ccp(0.5f,0.5f));
pGameRoot->setPosition(ccp(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
pGameRoot->setContentSize(designResolutionSize);
addChild(pGameRoot);

如果根节点是自身CCTMXTiledMap(注:这里我们地图的内容不要超过设计尺寸),就不用进行上边的操作了!

2).坐标设置

游戏中尽量不要用绝对坐标(其实绝对坐标木任何问题),让我们加入几个精灵试试:

1
2
34
5
6
7
8
9
10
1112
13
14
15
16
17
18
19
20

//右上角
CCSprite* pHero=CCSprite::create("hero.png");
pHero->setPosition(ccp(pGameRoot->getContentSize().width,pGameRoot->getContentSize().height));
pGameRoot->addChild(pHero);
//左下角
pHero=CCSprite::create("hero.png");
pHero->setPosition(ccp(0,0));
pGameRoot->addChild(pHero);
//右下
pHero=CCSprite::create("hero.png");
pHero->setPosition(ccp(pGameRoot->getContentSize().width,0));
pGameRoot->addChild(pHero);
//左上
pHero=CCSprite::create("hero.png");
pHero->setPosition(ccp(0,pGameRoot->getContentSize().height));
pGameRoot->addChild(pHero);
//中心
pHero=CCSprite::create("hero.png");
pHero->setPosition(ccp(pGameRoot->getContentSize().width/2,pGameRoot->getContentSize().height/2));
pGameRoot->addChild(pHero);

让我们看下效果:













游戏中的我们尽量使用相对坐标,可以通过以下几种方式求得:

①.相对于父亲的尺寸比例

②.相对于另一个精灵的坐标的偏移

③.相对于屏幕的尺寸的比例

这样日后修改起来了会很方便!

哈哈哈,大家可以把上段代码中的pGameRoot->getContentSize().width,pGameRoot->getContentSize().height换成960x720的绝对坐标试下,结果是没有任何问题的!

3).介绍几个坐标转换的函数

1

CCPoint CCDirector::convertToGL(const CCPoint& obPoint);

将UI坐标(左上角为原点)转化为OpenGL(左下角为原点)坐标,CCTouch中的坐标为UI坐标,我们使用时要转换为OpenGL坐标

1

CCPoint CCDirector::convertToUI(const CCPoint& obPoint);

将OpenGL坐标转化为UI坐标,主要用在设置CCTouch的触点(讲触摸的那篇文章有提到)!

1
2
3

CCPoint CCNode::convertToNodeSpace(const CCPoint& worldPoint);

CCPoint CCNode::convertToWorldSpace(const CCPoint& nodePoint);

世界坐标空间和Node空间的转化,请看下图:





如图,我们用中间的小人getPosition()得到的是相对与父亲的坐标(即红框),如果我们想得到它相对于原点的坐标,就可以这样:

1

CCPoint worldpos=pHero->convertToWorldSpace(pHero->getPosition());

如下图:





假如我们在红色圆圈位置点击,想判断落点在哪一格,但是我们从pTouch->getLocation();得到的坐标是相对于原点的,这样我们要减去红框(pGameRoot)的左下角坐标才行,很麻烦的,我们可以这个样子做:

12

CCPoint touchPoint = pTouch->getLocation();
CCPoint touchInGameRoot=pGameRoot->convertToNodeSpace(touchPoint);

哈哈哈,这样子是不是很简单?这样子touchInGameRoot就是以pGameRoot左下角的偏移量了!

游戏内容部分就讲到这里!

3.UI层

UI层的实现相对来说比较简单,目前来说大概有两种实现方式:

①.根据”米”来设设置UI控件的位置.

②.根据屏幕的%来这设置UI控件的位置.

1).让我们直接上代码吧:

1
2
34
5
6
7
8
9
10
1112
13
14
15
16
17
18

CCMenuItemImage* pItem=CCMenuItemImage::create("red.png","blue.png");
pItem->setPosition(ccp(visibleSize.width*0.0f,visibleSize.height*0.0f));

CCMenuItemImage* pItem1=CCMenuItemImage::create("red.png","blue.png");
pItem1->setPosition(ccp(visibleSize.width*1.0f,visibleSize.height*0.0f));

CCMenuItemImage* pItem2=CCMenuItemImage::create("red.png","blue.png");
pItem2->setPosition(ccp(visibleSize.width*0.0f,visibleSize.height*1.0f));

CCMenuItemImage* pItem3=CCMenuItemImage::create("red.png","blue.png");
pItem3->setPosition(ccp(visibleSize.width*1.0f,visibleSize.height*1.0f));

CCMenuItemImage* pItem4=CCMenuItemImage::create("red.png","blue.png");
pItem4->setPosition(ccp(visibleSize.width*0.5f,visibleSize.height*0.5f));

CCMenu* pMenu=CCMenu::create(pItem,pItem1,pItem2,pItem3,pItem4,NULL);
pMenu->setPosition(CCPointZero);
this->addChild(pMenu, 1);

这个是按照比例去设置UI位置的,下面是效果图,大家注意不同分辨率下红色圆饼的位置:









关于如何用”米”子来布局的实现,大家去这里看http://dualface.github.io/blog/2012/08/17/cocos2d-x-2-dot-0-multi-resolution/,这个不是我要讲的重点,毕竟手写坐标的人不会很多,我们主要讲如何在编辑器中实现UI布局的自适应!

2).在cocosbuilder中如何实现自适应

如果你用过cocosbuilder的话,你一定对下图不陌生:





这个是我们设置物体坐标的一个界面,功能介绍我引用K.C的文章中的一段话:





他讲的非常好,我就不赘述了,我们一般情况下只会使用第一个,当时为了适配多分辨率,我们不能这么做了!在这里我们还是有两种做法来实现,一个是相对布局,一个是%比,但是我推荐将两种方案结合起来.

①.相对位置负责四个角落的位置,即:左下,左上,右下.右上!

②.而中间的几个位置就只能交给%,即:上,下,左,右,中!

这里我们要注意几点(都是血淋淋的坑啊):

①.对于CCMenu,CCNode,CCSprite都能够实用上边的两个方法来设置坐标,唯独CCMenuItem不行,只能使用绝对坐标,这点大家一定要注意.那么对于下面的情况该怎么办呢:





我的解决方案是给”每一堆”按钮都添加一个CCMenu,然后CCMenuItem按照相对位置来,上图中我就使用了3个CCMenu!

②.对于要”扎堆”的控件(即控件间的相对位置保持不变),一定要加入到一个公共的父亲上!

③.灵活使用AnchorPoint属性!


六.设计尺寸的设定

之前文章中有提到设置游戏的分辨率为(960,720),那么为什么呢?

首先我们来考虑选择什么样的宽高比,宽高比越小,画面就越方,比如ipad(4:3=1.33)的宽高比就比iphone(3:2=1.5)的小,所以ipad的屏幕更方一些!因此我们设计游戏是应该按照最小的宽高比去设计,这样在大的宽高比的机型上都能够看到完整的画面,从表1中可知宽高比最小的是4:3!

下面我们来考虑分辨率,分辨率要符合大多数设备的要求,经过分析960的宽是最符合要求的,转为4:3就是960x720!.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  cocos2d 游戏