新篇开始前简单回顾一下学习过的章节,环境搭建、项目创建、项目解析、游戏原型……
一、本篇前提:
完成前一课的内容。
具体参考:Cocos2d-x 3.3塔防游戏《保卫萝卜》教程04:实现简单的游戏原型
二、本篇目标:
说说关于Cocos2d-x手机分辨率适配
对前一篇完成的塔防游戏原型进行屏幕适配完善
三、内容:
说说关于Cocos2d-x手机分辨率适配
在上一篇的结尾我们遗留了一个问题,在真机上运行时女主角、色狼的位置相当于道路都有点偏上了,并且好像背景地图也没有显示全背景的顶部和底部有一部分没有显示出来,但是在windows下运行确正常,这个是什么原因呢,该怎么调整呢?我的手机分辨率是:960x540 而我们的地图素材图片分辨率是:960x640,两个尺寸的不同导致了这个问题,这个是关于不同手机屏幕分辨率适配问题,Android手机品种繁多屏幕尺寸分辨率也同样繁多,为了让游戏适应不同的Android手机需要多做很多的工作,这个做IOS的游戏要幸福多毕竟就这么几个尺寸分辨率。
影响游戏的两个因素屏幕大小(分辨率)、宽高比,屏幕大小从小屏的480×320手机到大屏甚至平板的2048×1536,如果用低分辨率的素材图在高分辨率的设备上就会图像模糊,如果用高分辨率的素材在低分辨率设备上时增加系统负担,所有一般我们采取多套不同分辨率素材进行匹配,这个问题还算容易解决。但是宽高比就麻烦的多,手机有3:2,16:9 标准宽屏等,本篇中我测试用的华为手机为16:9的宽屏,宽高比会造成游戏不按比例的压缩或者拉伸造成游戏上的元素显示、位置等发生变异甚至导致游戏无法使用,所以宽高比造成的问题比屏幕大小要严重的多,比如我们的这个塔防游戏就因为这个问题造成了游戏人物位置偏移。那宽高比是否也可以像分辨率一样采用多套宽高比的素材解决?肯定可以!但是如果不同的宽高比结合不同的分辨率这样得提供多少套的素材啊?并且新的宽高比的手机在不断的新出来为了每出一款手机就做一套素材这样的成本太大。
在cocos2d-x-3.3/tests目录下有一个名为cpp-empty-test示例工程,用Microsoft Visual Studio 2012打开proj.win32下的工程,可以看到它对于这个问题的解决方案,参考其中的AppMacros.h、AppDelegate.cpp、Resources目录下的素材。
通过这个示例项目可以如下总结:
1、屏幕大小(分辨率)解决方案
按照上面的思路提供多套的素材,一般游戏做法提供低、中、高、超高四套不同的分辨率的素材,低的应付一般小屏手机、中的应付高分辨率手机、高的应付平板、超高的应付高清平板或者电视之类的设备。四套素材分4个文件夹放置到项目Resources文件下比如叫iphone、iphonehd、ipad、ipadhd。在设备载入游戏时,判断当前设备的分辨率然后选择不同文件夹下的素材进行载入以适应不同分辨率的设备。
2、宽高比解决方案
为了适应设备各种屏幕宽高比,在 Cocos2dx中,提供了相应的解决方案,以方便我们在设计游戏时,能够更好的适应不同的屏幕。Cocos2dx提供了ResolutionPolicy(分辨率策略),通过给GLView设置不同ResolutionPolicy来解决这个问题。
ResolutionPolicy的五种类型:
1、EXACT_FIT
2、NO_BORDER
3、SHOW_ALL
4、FIXED_HEIGHT
5、FIXED_WIDTH
对前一篇完成的塔防游戏原型进行屏幕适配完善
1、屏幕大小(分辨率)适配
第一步:按照上面解决方案的做法,先制作iphone、iphonehd两套素材,然后拷贝到项目的Resources文件下,我们这款游戏的目标是手机类的设备,所以就只提供了两套分辨率的素材,如果需要对更高分辨率的设备进行支持那么需要提供更多套的素材。个人建议如果你的游戏需要支持平板,那么建议单独出个HD版,虽然通过代码和素材的适配能同时支持手机和平板设备,但是这样的实现还是有一定的限制,会一定程度上降低某类设备的可玩性。
第二步:新建AppMacros.h文件,把cpp-empty-test示例工程下同名的文件代码直接拷贝过来进行修改,只需要保留两种不同的分辨率代码即可:
#define DESIGN_RESOLUTION_480X320 0
#define DESIGN_RESOLUTION_960X640 1
/* If you want to switch design resolution, change next line */
#define TARGET_DESIGN_RESOLUTION_SIZE DESIGN_RESOLUTION_960X640
typedef struct tagResource
{
cocos2d::Size size;
char directory[100];
}Resource;
static Resource smallResource = { cocos2d::Size(480, 320), "iphone" };
static Resource mediumResource = { cocos2d::Size(960, 640), "iphonehd" };
#if (TARGET_DESIGN_RESOLUTION_SIZE == DESIGN_RESOLUTION_480X320)
static cocos2d::Size designResolutionSize = cocos2d::Size(480, 320);
#elif (TARGET_DESIGN_RESOLUTION_SIZE == DESIGN_RESOLUTION_960X640)
static cocos2d::Size designResolutionSize = cocos2d::Size(960, 640);
#else
#error unknown target design resolution!
#endif
// The font size 24 is designed for small resolution, so we should change it to fit for current design resolution
#define TITLE_FONT_SIZE (cocos2d::Director::getInstance()->getOpenGLView()->getDesignResolutionSize().width / smallResource.size.width * 24)
第三步:打开AppDelegate.cpp文件,添加对AppMacros.h的引用,然后applicationDidFinishLaunching方法里添加对当前设备屏幕分辨率进行判断然后设置不同的图片素材:
#include
#include
#include "AppMacros.h"
……
//获取当前设备屏幕尺寸
Size frameSize = glview->getFrameSize();
vector searchPath;
//如果屏幕尺寸宽>smallResource素材尺寸宽
if (frameSize.width > smallResource.size.width)
{
//使用mediumResource目录下的素材
searchPath.push_back(mediumResource.directory);
float scale=mediumResource.size.width/designResolutionSize.width;
director->setContentScaleFactor(scale);
}
else
{
//使用smallResource目录下的素材
searchPath.push_back(smallResource.directory);
float scale=smallResource.size.width/designResolutionSize.width;
director->setContentScaleFactor(scale);
}
// 设置素材路径目录
FileUtils::getInstance()->setSearchPaths(searchPath);
……
通过这段代码,我们解决了低分辨率手机和高分辨率手机的图片素材适配问题。如何测试效果呢?分别找个低分辨率的的手机和高分辨率的手机?这样太麻烦了,其实只需要一行代码就可以直接在调试时模拟不同分辨率的手机效果,在applicationDidFinishLaunching方法中添加一行glview->setFrameSize(960,440);代码即可实现,当游戏开发完成时删除本行代码。
if(!glview) {
glview = GLViewImpl::create("DefendTheGirl");
//设置模拟器分辨率大小
glview->setFrameSize(960,440);
director->setOpenGLView(glview);
}
2、宽高比适配
第一步:按照上面解决方案的做法,在AppDelegate.cpp的applicationDidFinishLaunching方法中,添加如下代码:
1
2//设置游戏的设计尺寸以及分辨率策略
glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height,ResolutionPolicy::FIXED_HEIGHT);
ResolutionPolicy有五种类型,我们先分别对这5种类型进行测试,看看有什么区别,并且决定我们这个游戏应该采用哪种类型效果最佳,为了能比较明显看出区别我们通过设置模拟器不同分辨率进行效果测试。
EXACT_FIT: 会拉伸素材进行显示,充满整个屏幕,最简单最粗暴,但是可能会出现图像变形。
1glview->setFrameSize(960,440);
结论:看游戏截图效果确实像描述的一样出现了图像变形,无论手机屏幕是何种宽高比的,里面是素材一律按照填满手机屏幕的宽高比对素材进行拉伸变形,我想这个方法肯定不可取。
NO_BORDER:短边占满屏幕,另外一侧超出屏幕,一部分画面在屏幕外,无法显示。
1glview->setFrameSize(960,440);
对应的分析图:
1glview->setFrameSize(460,640);
对应的分析图:
结论:我们分别用960,440和460,640两种手机尺寸进行测试。通过分析图可以知道第一种尺寸的时候是素材宽(960px)占满了整个手机屏幕,而素材高(640px)只显示了中间的440px,素材的上部分被手机屏幕遮住了100px下部分被遮住了100px;第二种尺寸的时候是素材高(640px)占满了整个手机屏幕,而素材的宽(960px)只显示了中间的460px, 素材的左边部分被手机屏幕遮住了250px右边部分被遮住了250px。对于NO_BORDER解释:短边占满屏幕,另外一侧超出屏幕,一部分画面在屏幕外,无法显示。这里说的短边并不是指素材本身宽和高中短的那边,而是素材宽和手机宽进行比较,素材高和手机高进行比较,那个差值小就算是短边,比如第一种尺寸的时候,宽差值=|素材的宽960px-手机屏幕宽960px|=0,高差值=|素材的高640px-手机屏幕宽440px|=200,所以短边是素材的宽。我想这个方法宽高适应和设备的宽高比有关,也就是哪个是短边不一定由设备决定。
SHOW_ALL:保持原比例,让一边占满屏幕,另外一侧黑边。
1glview->setFrameSize(960,540);
结论:这个方式好像看起来相对合理点,画面肯定保持原来素材的比例不会拉伸变形等,但是比较遗憾的是手机的左右两边或者上下两边会出现黑色的空白区除非素材的宽高比恰好和手机的宽高比相同。我想这个方式对我们这个游戏来说也可选,但是有黑边不是最佳的用户体验,但是用这个方式最简单可以在任何设备中保持游戏真实的画面。
FIXED_HEIGHT:NO_BORDER类似,但是指定高占满屏幕,宽部分超出屏幕外,无法显示。
1glview->setFrameSize(960,440);
对应的分析图:
1glview->setFrameSize(460,640);
对应的分析图:
结论:这个方式好像和NO_BORDER有点像,上面两种尺寸屏幕测试结果均表现为素材高占满手机屏幕的高,而宽要不超出屏幕要不小于屏幕,素材仍旧维持本身的宽高比,同时游戏背景上的人物位置有一定的偏移,这个方式对我们这个游戏来说也不是很合适。
FIXED_WIDTH:NO_BORDER类似,但是指定宽占满屏幕,高部分超出屏幕外,无法显示。
1glview->setFrameSize(960,440);
对应的分析图:
1glview->setFrameSize(460,640);
对应的分析图:
结论:这个方式好像和NO_BORDER有点像,上面两种尺寸屏幕测试结果均表现为素材宽占满手机屏幕的宽,而高要不超出屏幕要不小于屏幕,素材仍旧维持本身的宽高比,同时游戏背景上的人物位置有一定的偏移。
我们游戏的选择:
5种类型的测试结果和分析结果均已经完成了,现在要为我们的这个塔防游戏找一个最合适的类型,EXACT_FIT这个肯定不可取直接否定,SHOW_ALL这个倒是个最简单的方案虽然有点小缺陷,但是这个最简单效果也能接受,并且很多知名游戏前期的一些版本均是这个模式,但是个人觉得我们的游戏得有点高追求,所以准备 NO_BORDER、FIXED_HEIGHT、FIXED_WIDTH这3个中选择一个,虽然会增加编码和素材设计的难度,但是通过合理素材设计和代码配合能完全实现全屏并且游戏比例不扭曲的效果,这3个其实差不多属于一个类型,NO_BORDER其实就包括了FIXED_HEIGHT、FIXED_WIDTH两种类型,只不过具体表现为哪种类型由实际设备的宽高比确定,也就是带有一定的不确定性,而FIXED_HEIGHT、FIXED_WIDTH就是由开发者直接指定要高适应要么宽适应,这样至少确定了一样不确定因素可以一定程度的降低编码和素材设计的难度。但是FIXED_HEIGHT、FIXED_WIDTH这2个中应该选哪个这应该跟要实际开发的游戏有关系,比如横屏游戏比较合适FIXED_WIDTH而竖屏游戏比较合适FIXED_HEIGHT,同时有可能和游戏素材设计也有一定的关系,比如我们的这个游戏地图背景,至少要保证地图中道路部分应该完整的呈现在手机屏幕的可视区域,其他部分可以允许被一定的遮盖。由此可见我们的游戏应该选择FIXED_WIDTH。
对前一篇完成的塔防游戏原型进行屏幕适配完善
上面的测试和分析决定了我们的游戏采用FIXED_WIDTH类型解决问题,现在展开具体的编码工作。
第一步:在AppDelegate.cpp的applicationDidFinishLaunching方法中,添加如下代码:
1
2//设置游戏的设计尺寸以及分辨率策略
glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height,ResolutionPolicy::FIXED_WIDTH);
第二步:设置模拟器屏幕尺寸glview->setFrameSize(960,540);然后运行游戏
会发现游戏道路完整显示,背景图上部分和下部分有一定区域被遮盖没有显示出来,这样我们在设计素材的时候可以刻意的把背景图的上下区域加高,尽量让素材高度很高道路等有效部分尽量居中,比如我们要适应960x960的正方形屏幕,只需要通过把地图的上部分和下部分同时加高直至960或者更高,并且加高部分只需要平铺绿色的底纹就可以了,这样游戏就能和谐的满屏幕显示。
第三步:上面这个游戏还有个问题,看下图由于背景素材底部有部分被挡住了,导致了设计时候的坐标原点和实际游戏坐标原点在高度上发生了偏移。
但是我们在前一篇时候,道路的路点坐标是按照相对设计原点进行计算的,现在原点坐标发生了偏移导致了这些路点坐标不准,这样就需要在代码上对这个偏移高度进行修正。
dw:素材宽 dh:素材高 sw:屏幕宽 sh:屏幕高 偏移高度值:x
x=( sh *0.5- (sw /dw)* dh *0.5) /( sw /dw)
偏移高度通过这个公式进行计算,我们在applicationDidFinishLaunching的时候用这个公式把偏移高度计算出来,然后保存到一个静态变量中,方便后续可以直接使用。
进行GameMediator.h、GameMediator.cpp类,用来保存一些游戏中经常使用的变量,比如我们的偏移高度和素材缩放比例就保存在这个类中,这个类实现一个单实例的模式,功能很简单以单实例静态变量的方式存放变量。
GameMediator.h:
class GameMediator : public cocos2d::CCObject
{
public:
GameMediator(void);
~GameMediator(void);
bool init();
//获取单实例
static GameMediator* sharedMediator();
//偏移高度
CC_SYNTHESIZE(float,_offsetHeight,OffsetHeight);
//缩放比例
CC_SYNTHESIZE(float,_scaleHeight,ScaleHeight)
};
GameMediator.cpp:
//静态实例
static GameMediator _sharedContext;
GameMediator* GameMediator::sharedMediator(){
static bool s_bFirstUse =true;
if (s_bFirstUse)
{
_sharedContext.init();
s_bFirstUse=false;
}
return &_sharedContext;
}
GameMediator::GameMediator(void)
{
}
GameMediator::~GameMediator(void)
{
}
bool GameMediator::init(){
bool bRet=false;
do
{
_offsetHeight=0;
_scaleHeight=1;
bRet = true;
} while (0);
return bRet;
}
第四步:在AppDelegate类中引入GameMediator的头文件,然后在applicationDidFinishLaunching方法中加入如下代码:
//以宽为标准计算素材缩放比例
float scaleHeight=frameSize.width/designResolutionSize.width;
//高度偏移值计算
//x=( sh *0.5- (sw /dw)* dh *0.5) /( sw /dw)
float offsetHeight=(frameSize.height*0.5f-scaleHeight*designResolutionSize.height*0.5f)/scaleHeight;
//保存缩放比例
GameMediator::sharedMediator()->setOffsetHeight(offsetHeight);
//保存高度偏移值
GameMediator::sharedMediator()->setScaleHeight(scaleHeight);
第五步:找到MainScene.cpp中init方法声明12个路点坐标的地方做如下修改:
//获得保存的偏移高度
float offsetHeight=GameMediator::sharedMediator()->getOffsetHeight();
//获得保存的缩放比例
float scaleHeight=GameMediator::sharedMediator()->getScaleHeight();
……
//添加地图1号路径点到集合中
Waypoint *waypoint1=Waypoint::nodeWithTheLocation(Point(920, 435+offsetHeight));
……
//添加地图12号路径点到集合中
Waypoint *waypoint12=Waypoint::nodeWithTheLocation(Point(50, 350+offsetHeight));
……
第六步:这样路点坐标均已经校正完毕,现在我们还需要对色狼和女主角做一下提高半个身位的校正使得他们的脚底部刚刚在道路的中央。找到MainScene.cpp中init方法中初始化色狼和女主角部分的代码修改如下:
……
//获得色狼大叔的高
float dsh=dsSprite->getTextureRect().size.height;
……
//女主高
float nzh=nhSprite->getTextureRect().size.height;
……
//获取集合中的最后一个点,12号点
Waypoint *waypoint0=wayPositions.back();
//设置运动的开始点
beginningWaypoint=waypoint0;
//设置运动的目标点为12号点的下一个点,11号点
destinationWaypoint=waypoint0->getNextWaypoint();
//设置色狼当前位置值
myPosition=waypoint0->getMyPosition();
//提高半个色狼身位
myPosition.add(Vec2(0,dsh/2.0f));
//设置色狼在地图的初始位置
dsSprite->setPosition(myPosition);
//设置女主角在地图的初始位置,为集合中的1号点
Point pos=wayPositions.front()->getMyPosition();
//提高半个女主角身位
pos.add(Vec2(0,nzh/2.0f));
nhSprite->setPosition(pos);
……
找到MainScene.cpp中update方法中色狼沿着道路移动部分的代码修改如下:
//获得色狼大叔的高
float dsh=dsSprite->getTextureRect().size.height;
Point destinationPos=destinationWaypoint->getMyPosition();
//提升色狼半个身位
destinationPos.add(Vec2(0,dsh/2.0f));
//判断色狼大叔是否和目标点碰到
if (this->collisionWithCircle(myPosition,1,destinationPos,1) )
{
//是否还有下一个目标点
if (destinationWaypoint->getNextWaypoint())
{
//重新设定开始点和目标点
beginningWaypoint=destinationWaypoint;
destinationWaypoint=destinationWaypoint->getNextWaypoint();
}
}
//获取目标点的坐标
Point targetPoint=destinationWaypoint->getMyPosition();
//提升色狼半个身位
targetPoint.add(Vec2(0,dsh/2.0f));
第七步:开始测试修改的效果,通过glview->setFrameSize(960,540);不断修改各种尺寸的屏幕看看这些尺寸屏幕下的游戏效果。并且编译打包一下so文件在android真机上看看之前的问题解决没有,打包前记得把所有新加的cpp文件添加到Android.mk的编译列表里。
修正后真机上的效果:
结束语:
本篇花了很长的篇幅来解决上一篇遗留的一个问题,但是我觉得这个非常值得因为这个屏幕适配问题是一个很重要的问题,尽早合理的选择方案能减少后续很多的返工量,现在我们解决了这个问题,后续在开发写代码的时候就会让编码适应这个方案。