适配调整参考:使用Cocos2D制作打鼹鼠游戏的教程(1)

作者:Ray Wenderlich

本系列教程共分为2部分,我将在第1部分中完成游戏的基本元素,即让可爱的小鼹鼠能够从地洞中窜出来。我们将着重思考如何规划图像和坐标轴的设置,从而让游戏在iPhone,iPad和Retina屏幕中能够有效地呈现出来。(请点击此处查看第2部分

规划图像设置:综述

因为我们希望这款游戏能够同时运行于普通的iPhone,拥有Retina屏幕的iPhone以及iPad上,我们便需要在深入发展之前多花点时间仔细规划图像的设置。

为了掌握图像的规模和设置,我们首先需要考虑到以下要点:

Retina屏幕和UIKit

Retina屏幕和Cocos2D

iPad,iPhone和纵横比

Retina屏幕和UIKit

普通iPhone与拥有Retina屏幕的iPhone的区别在于,后者可以通过Retina屏幕呈现出双倍的像素。也就是如果在普通iPhone上所呈现出的是480×320像素,那么在Retina屏幕上将出现960×640像素。

iPhone-vs-Retina(from raywenderlich)

iPhone-vs-Retina(from raywenderlich)

但是这时候你便会感到疑惑,即“像素的双倍增加难道不会破坏了那些基于480×320像素所编写的应用?”当然会(游戏邦注:特别是当你在使用UIKit进行编程时),除非你在UIKit中明确了帧的大小,否则你便是在以点而非像素设置大小。

在普通iPhone中,一个点代表一个像素。而在Retina屏幕中,一个点则等于两个像素。所以当我们将点的位置设置为(10,10)时,在普通iPhone上它的位置便是(10,10),而在Retina屏幕中则变成了(20,20),这是一种相对的位移。

如果你正在使用苹果的控制方式或Core Graphics,你便会发现苹果已经编写了合适的代码而让所有内容能够更好地呈现在Retina屏幕上。

只有你真正针对一张图像进行操作时你才能够发现问题的所在。假设你的iPhone应用中有一张200×200的图像,如果你不设置其它元素而将其放在Retina屏幕中,那么这张图像将被放大2倍,而最终显示出来的效果便会非常糟糕,因为你没有使用额外的分辨率去提升图像的质量。

No-HD-Res(from raywenderlich)

未使用高清分辨率的图像(from raywenderlich)

所以我们需要做的便是提供另外一个版本的图像:即共有普通版本和双倍规格版本。如果你在双倍大小的图像中添加了“@2x”扩展名,那么当你尝试着去加载[UIImage imageNamed:...]图像或类似的应用程序界面时,Retina屏幕上将自动加载@2x图像。

所以使用Retina屏幕创造UIKit应用非常简单,即只要添加@2x图像便算你完成了大部分的工作。

那么Cocos2D又是怎样的情况?

Retina屏幕与Cocos2D

有一个好消息是,最新的Cocos2D版本完全支持于Retina屏幕,并且让整个过程操作更加简便:

1.基于CCDirector调用enableRetinaDisplay能让你在启动应用时确保Retina屏幕的有效运行。如果你正在使用Cocos2D项目模版,你便可以在你的应用委托类中取消这一批注。

2.在你的应用上添加双倍精灵,而不是使用“@2x”扩展名,在Cocos2D中你可以使用“-hd”作为扩展名。当你加载精灵时,你只需使用正常名称(游戏邦注:即没有“-hd”扩展名),而Cocos2D将自动帮助你在Retina屏幕上加载高清图像。

3.现在,当我们开始在Cocos2D中为精灵定位时,我们便可以使用点而不是像素了。但是我们需要注意的是一些应用程序界面(不多)仍然在使用像素进行定位——如果是这样的话我们便需要在方法名称中强调像素,而不是点。

其实最简单的方法便是让美工尽可能提高图片像素(例如将图像规格扩大2倍以适应Retina屏幕),这时候你便能够在面对普通iPhone时轻松地压缩图像比例了。

你可能会好奇为何要纠结于设置两种不同规格的图像,为什么不只加载较大的图像然后在面对其它设备时有规律地进行缩放?在内存中加载纹理是最耗应用内存的做法,所以如果你运行的是一个不能利用较高像素图像的设备,你只有加载适合设备的较小图像才能有效地节省内存。

不要担心,你并不需要不断地在Photoshop中缩放图像。Texture Packer 拥有一个非常棒的功能可帮助你轻松地缩放图像,而这也是我们将在本教程中所使用到的功能。

iPad,iPhone和纵横比

好吧,现在看来Retina屏幕的图像设置已经不成问题了,那iPad呢?

我们都清楚,同时针对于iPhone和iPad制作游戏是件烦人的事,因为这两款设备拥有完全不同的纵横比!

iPhone的屏幕规格是480×320或960×640——即纵横比是1.5。而iPad则是768×1024——纵横比为1.33。

这就意味着如果你的一张图像能够填满整个iPad背景(768×1024),而你想要在iPhone上再次使用这张图像,那图像便不可能吻合地平铺于iPhone屏幕上。如果说你压缩了图像规格以适应iPhone的宽度(乘以0.9375):图像规格便会变成720×960,这时候便会多出一些空白区域而需要我们进行裁剪。

Aspect-Ratio-Diff(from raywenderlich)

Aspect-Ratio-Diff(from raywenderlich)

这就让事情变得更加麻烦了。因为你不仅需要处理背景图像的问题,同时你还会发现不同的纵横比将导致你难以在多种设备中使用相同的坐标轴。

关于这一问题存在着多种策略,我将在此列出我所看到/听到/使用过的几种:

在屏幕中央设置一个符合iPhone的Retina屏幕规格(640×960)的“可游戏区域”。如此我们便能够在边缘留出一些额外的空间——你可以用背景去覆盖这些空间,并且玩家也不会轻易注视到这一点。这种方法能够帮助你在不同设备间轻松地转变坐标轴并重复使用图像(在iPad和Retina屏幕上使用高分辨率的图像而在普通的iPhone上使用正常分辨率图像)。这也是我们在本篇教程中所使用的方法。

如果你在iPad屏幕上的每一边使用42像素分隔线,并将“主要内容”设置为684×1024,你便能够让iPad拥有与iPhone相同的纵横比。如果你将你的内容都控制在684×1024规格下,你便能够面向不同设备去压缩你的图像。

你可以面对iPhone,iPad以及Retina屏幕设置不同的图像和不同的坐标轴。这么做虽然能够让我们更加灵活地应对不同设备,但是我们还需要考虑到不同设备所具有的更大的二进制规格以及重置对象位置的问题。

另外一个问题是,从自动加载带有“-hd”扩展名的图像以及转换坐标轴等来看,现在的Cocos2D还不足以提供给iPad一些有益的帮助。

规划图像:结论

基于以上讨论,让我们开始规划这篇教程:

将图像限制在960×640规格的“可游戏区域”中,使用带有Retina屏幕的iPhone的全屏设置,并被居中置于iPad屏幕上。

基于Texture Packer将图像压缩为普通iPhone规格的一半。

将完整的图像命名为带有“-hd”扩展名的名称,而压缩后的文件则不带这一扩展名,Cocos2D将基于是否启用Retina屏幕而加载合适的图像。

背景便是一种特殊情况,因为它们总是需要全屏展开。我们需要将背景设置为1024×768规格(iPad的规格标准),从而确保整个屏幕都能被填满。因为规格的近似我们也可以将同一图像再次用于iPhone上。虽然有些背景会超出屏幕范围,但是这种情况却不会出现在这一特殊背景上。

iPad版本中将包含一些特殊代码,能够帮助我们使用带有“-hd”扩展名的图像,转换坐标轴以适应内部“可游戏区域”,以及使用合适的字体等。

首先请先下载针对于本教程的图像。打开文件并观察内部设置:

在“前景”文件夹中,前景的规格是1024×768(iPad的规格),但是实际上这个规格却被分成了两个部分:下面部分和上面部分。如此我们便能够将鼹鼠放置在下面和上面部分的中间段,让玩家觉得鼹鼠好像真的藏在地下似得。

在“背景”文件夹中,背景的纵横比是1.33(也就是iPad的纵横比),但实际上却只有一半的规格(512×384)。主要是因为背景很少显示出来(即只通过3个鼹鼠洞进行呈现),所以我们没有必要创造出一个1024×1024规格的纹理去加载这一背景。也就是较小的纹理更加合适。

在“精灵”文件夹中,所有的精灵规模都巧妙地设置在960×640“可游戏区域”内。我们需要注意的是,1只鼹鼠对应2个动画(也就是鼹鼠窜出来时便会遭遇敲打)。

好了,让我们正式开始进行教程分析!

开始

打开XCode,在主菜单中选择“File\New Project…”,并选择“User Templates\cocos2d\cocos2d Application”,点击“Choose…”。将项目命名为“WhackAMole”,并点击“保存”。

接下来,使用Finder将你之前所下载的“图像”文件夹复制到你的“WhackAMole”项目的目录中,同时确保这一文件夹必须与“build”和“Classes”文件夹处于同等级别,如下所示:

Directory-Structure(from raywenderlich)

Directory-Structure(from raywenderlich)

接下来确保你安装了Texture Packer,并能够用于你的设备中。

现在你便设置了TexturePacker,并能够以此创造你在之后所需要的精灵列表。只要使用TexturePacker的命令行工具以及Xcode集合你便能够完成各种任务,而不再需要依赖于TexturePacker的图形用户界面。

右击“资源”,选择“Add\New File…”,选择Mac OS X\Other\Shell Script,并点击“下一步”。将文件命名为“PackTextures.sh”,点击“完成”。

用以下代码取代PackTextures.sh的内容:

#!/bin/sh

TP=”/usr/local/bin/TexturePacker”

if [ "${ACTION}" = "clean" ]
then
echo “cleaning…”

rm resources/background*
rm resources/foreground*
rm resources/sprites*

else
echo “building…”

${TP} –smart-update \
–format cocos2d \
–data resources/background-hd.plist \
–sheet resources/background-hd.pvr.ccz \
–dither-fs \
–opt RGB565 \
Art/background/*.png

${TP} –smart-update \
–format cocos2d \
–data resources/background.plist \
–sheet resources/background.pvr.ccz \
–dither-fs \
–scale 0.5 \
–opt RGB565 \
Art/background/*.png

${TP} –smart-update \
–format cocos2d \
–data resources/foreground-hd.plist \
–sheet resources/foreground-hd.pvr.ccz \
–dither-fs-alpha \
–opt RGBA4444 \
Art/foreground/*.png

${TP} –smart-update \
–format cocos2d \
–data resources/foreground.plist \
–sheet resources/foreground.pvr.ccz \
–dither-fs-alpha \
–scale 0.5 \
–opt RGBA4444 \
Art/foreground/*.png

${TP} –smart-update \
–format cocos2d \
–data resources/sprites-hd.plist \
–sheet resources/sprites-hd.pvr.ccz \
–dither-fs-alpha \
–opt RGBA4444 \
Art/sprites/*.png

${TP} –smart-update \
–format cocos2d \
–data resources/sprites.plist \
–sheet resources/sprites.pvr.ccz \
–dither-fs-alpha \
–scale 0.5 \
–opt RGBA4444 \
Art/sprites/*.png

fi
exit 0

这一脚本将能够运行TexturePacker而创造出背景图像,前景图像以及脚本图像的精灵列表——即高清和一般质量的图像。

我们必须以pvr.ccz格式保存每一份图像——因为基于内存和磁盘空间来看这是最有效的格式。同时关于每种图像设置我们还必须明确合适的像素格式和递色选项,而以此权衡图像质量和储存空间的使用。

如果你不清楚TexturePacker选项到底能够做什么,那就加载Terminal并运行TexturePacker,而以此获得每个选项的完整描述内容。

接下来你需要让你的项目能够在编译时运行这一列表脚本。在“目标”文件夹中右击并选择“Add\New Target…”,然后选择“外部目标”(也就是非列表脚本目标!),点击“下一步”。将其命名为“Target TexturePacker”,并点击“完成”。

双击TexturePacker上的目标并如下进行设置:

Target-Settings(from raywenderlich)

Target-Settings(from raywenderlich)

最后一步便是将这一目标依附于你的应用中。双击TextureFun上的目标,找到“General”标签,在“Direct Dependencies”中点击“+”按钮,在列表中选中Texture Packer然后点击“添加目标”。

Dependencies(from raywenderlich)

Dependencies(from raywenderlich)

编译应用,如果一切设置合理的话你便能够从创建结果中看到Texture Packer的输出。

TexturePackerResults(from raywenderlich)

TexturePackerResults(from raywenderlich)

接下来你需要在项目中添加新生成的精灵列表和属性列表。在“资源”文件夹中右击并选择“Add\Existing Files…”,然后从“资源”中选择背景,前景和精灵文件(总共12个文件)然后相继点击“添加”。你必须确保“组和文件”结构如下所示:

SpriteSheetsAdded(from raywenderlich)

SpriteSheetsAdded(from raywenderlich)

你可以双击任何.pvr.ccz文件去加载预览内容。这便是Texture Packer的新功能!

设置背景

打开HelloWorldScene.m并找到你的初始方法。删除用于创造“Hello World”标签的4行代码,并用以下代码进行替换:

// Determine names of sprite sheets and plists to load
NSString *bgSheet = @”background.pvr.ccz”;
NSString *bgPlist = @”background.plist”;
NSString *fgSheet = @”foreground.pvr.ccz”;
NSString *fgPlist = @”foreground.plist”;
NSString *sSheet = @”sprites.pvr.ccz”;
NSString *sPlist = @”sprites.plist”;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
bgSheet = @”background-hd.pvr.ccz”;
bgPlist = @”background-hd.plist”;
fgSheet = @”foreground-hd.pvr.ccz”;
fgPlist = @”foreground-hd.plist”;
sSheet = @”sprites-hd.pvr.ccz”;
sPlist = @”sprites-hd.plist”;
}

// Load background and foreground
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:bgPlist];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:fgPlist];

// Add background
CGSize winSize = [CCDirector sharedDirector].winSize;
CCSprite *dirt = [CCSprite spriteWithSpriteFrameName:@"bg_dirt.png"];
dirt.scale = 2.0;
dirt.position = ccp(winSize.width/2, winSize.height/2);
[self addChild:dirt z:-2];

// Add foreground
CCSprite *lower = [CCSprite spriteWithSpriteFrameName:@"grass_lower.png"];
lower.anchorPoint = ccp(0.5, 1);
lower.position = ccp(winSize.width/2, winSize.height/2);
[self addChild:lower z:1];

CCSprite *upper = [CCSprite spriteWithSpriteFrameName:@"grass_upper.png"];
upper.anchorPoint = ccp(0.5, 0);
upper.position = ccp(winSize.width/2, winSize.height/2);
[self addChild:upper z:-1];

// Add more here later…

让我们重温这一过程——因为这里存在一些很有帮助的新内容。

明确并加载精灵列表中的名称。这一部分主要罗列出Texture Packer所生成的精灵列表的名称,并进行加载。我们需要注意的是在iPhone上,Cocos2D将基于Retina屏幕是否运行而自动选择“-hd”版本和普通版本。而iPad则不会加载“-hd”版本,除非你命令它这么做。所以我们有必要明确我们所面对的是否是iPad,或者是否需要使用“-hd”版本。

加载背景和前景。接下来我们就需要在精灵帧缓存中加载背景和前景的相关信息,有备于今后的使用。我们还需要注意的是不能将这些精灵添加到CCSpriteBachNode——因为这些图像只有一次使用价值。

添加背景。我们必须确保背景图像是置于所有图层之下。为了保存空间并确保图像居中,我们压缩了一半的图像规格。

添加前景。前景的添加主要分为两个部分。较为简单的方式便是图像的设置,即在顶层图像的中间/底部以及底层图像的中间/顶端设置定位点,并将这一定位点对准屏幕中心。在这一过程中你无需使用任何复杂的数学运算,所有的定位点都会出现在不同设备的合理位置上。虽然对于iPhone来说背景总是会超出屏幕范围,但是对于这一背景来说却不是什么大问题,并且甚少玩家会注意到这一点。除此之外我们还必须牢记我们使用的是不同z值去添加图像,所以必须适度降低最高图像的位置。

编译并运行代码,这时你便能够在屏幕上看到背景和前景了。并观察这一代码是否能够同时有效地运行于iPhone和iPad模拟器上。

Background(from raywenderlich)

Background(from raywenderlich)

如果你尝试着在Retina屏幕上运行代码并放大显示,你仍将看到普通的图像设置:

HD-vs-Normal(from raywenderlich)

HD-vs-Normal(from raywenderlich)

主要是因为我们未在早些时候完成“Retina屏幕和Cocos2D”的第一个步骤:在CCDirector上调用enableRetinaDisplay,确保你在启动应用时能够运行Retina屏幕。

为了做到这一点你需要打开WhackAMoleAppDelegate.m,并在applicationDidFinishLaunching中取消以下内容的批注:

if( ! [director enableRetinaDisplay:YES] )
CCLOG(@”Retina Display Not supported”);

编译并运行代码,现在当你运行Retina屏幕时便可以看到它自动使用高清文件了——主要归功于Cocos2D对于Retina屏幕的支持。

放置鼹鼠

在这款游戏中我们共需要添加三只鼹鼠——一个洞里一只。通常情况下鼹鼠都是藏在草地底下,但是当它们突然“窜出来”时你便需要想办法敲击它们。

首先我们需要在每个洞的底端添加鼹鼠。我们需要暂时让它们出现在其它图层之上,以确保将其设置在正确的点上;随后将它们放置在地下,并调整到最合适的位置上。

打开HelloWorldScene.h并添加一个数组以追踪鼹鼠所在的层面,如下:

// Inside @interface HelloWorld
NSMutableArray *moles;

通过将鼹鼠的定位点储存在这一数组中,我们便能够在之后轻松地设置每一个鼹鼠的位置了。

接下来我们需要在初始方法的最末处添加代码以设置鼹鼠的位置:

// Load sprites
CCSpriteBatchNode *spriteNode = [CCSpriteBatchNode batchNodeWithFile:sSheet];
[self addChild:spriteNode z:999];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:sPlist];

moles = [[NSMutableArray alloc] init];

CCSprite *mole1 = [CCSprite spriteWithSpriteFrameName:@"mole_1.png"];
mole1.position = [self convertPoint:ccp(85, 85)];
[spriteNode addChild:mole1];
[moles addObject:mole1];

CCSprite *mole2 = [CCSprite spriteWithSpriteFrameName:@"mole_1.png"];
mole2.position = [self convertPoint:ccp(240, 85)];
[spriteNode addChild:mole2];
[moles addObject:mole2];

CCSprite *mole3 = [CCSprite spriteWithSpriteFrameName:@"mole_1.png"];
mole3.position = [self convertPoint:ccp(395, 85)];
[spriteNode addChild:mole3];
[moles addObject:mole3];

这便是我们首次创造出属于精灵的CCSpriteBatchNode,如此我们便能更有效地绘制鼹鼠并将其设置在地底层。我们需要注意的是我们只是暂时将z值设置为999,如此我们才能在鼹鼠出现在顶层时确保它们的位置是否合理。

然后在属性列表中加载所有的精灵帧到缓存中,以确保我们之后能够用到这些内容。

为每个鼹鼠创造一个精灵并放置在场景中,然后添加到鼹鼠列表上。每只鼹鼠的坐标值都被控制在480×320(iPhone的规格)的“可游戏区域”中。而如果面向的是iPad,我们就需要修改这一定位,即我们需要调用辅助功能convertPoint。

在初始方法的右上方添加以下方法:

- (CGPoint)convertPoint:(CGPoint)point {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
return ccp(32 + point.x*2, 64 + point.y*2);
} else {
return point;
}
}

这一方法将帮助我们将“可游戏区域”点移至iPad屏幕上最合适的位置。我们需要记住:

我们正在iPad上使用高清图像,所以所有点都是双倍的。

我们将在1024×968的iPad屏幕上划出960×640的中心区域,并在左右两边和上下两端各留下32像素和64像素的边缘空间。

所以这一方法只是通过简单的数学运算而明确对象在iPad上的正确位置。

除此之外,我们必须添加以下代码清除我们用于分配鼹鼠数组的内存:

[moles release];
moles = nil;

编译并运行代码,这时候你便能够在正确的地点看到三只欢快的鼹鼠。并观察这一代码是否能够同时有效地运行于iPhone,iPhone Retina和iPad上。

Moles(from raywenderlich)

Moles(from raywenderlich)

窜出的鼹鼠

既然我们明确了鼹鼠的位置,接下来我们便需要添加代码让它们能够从洞里窜出来。

首先我们需要将鼹鼠精灵列表的z值从999重新调回0,以确保鼹鼠一开始是待在地下的。

然后在你的初始方法最底断添加以下代码:

self schedule:@selector(tryPopMoles:) interval:0.5];

如果你之前未看过这些内容,你可以在节点上运行调度方法并推动Cocos2D调用另外一种方法。就像在这种情况下,我们便希望每0.5秒就有鼹鼠窜出来。

接下来我们需要添加tryPopMoles的执行:

- (void)tryPopMoles:(ccTime)dt {
for (CCSprite *mole in moles) {
if (arc4random() % 3 == 0) {
if (mole.numberOfRunningActions == 0) {
[self popMole:mole];
}
}
}
}

我们将每隔0.5秒调用一次这一方法,并且每次调用将面向每一只鼹鼠而让它们平均拥有三分之一次跳出机会。但是鼹鼠们也只有在不能够移动时才能够窜出洞口——所以一种简单的检查方法便是确保行动数值是否为0.

最后,添加popMole的执行:

- (void) popMole:(CCSprite *)mole {
CCMoveBy *moveUp = [CCMoveBy actionWithDuration:0.2 position:ccp(0, mole.contentSize.height)]; // 1
CCEaseInOut *easeMoveUp = [CCEaseInOut actionWithAction:moveUp rate:3.0]; // 2
CCAction *easeMoveDown = [easeMoveUp reverse]; // 3
CCDelayTime *delay = [CCDelayTime actionWithDuration:0.5]; // 4

[mole runAction:[CCSequence actions:easeMoveUp, delay, easeMoveDown, nil]]; // 5

这一代码正是使用Cocos2D行动而促使鼹鼠能够窜出洞口,时隔半秒再钻回去。让我们进一步明确这些内容:

1.创造一个行动而推动着鼹鼠沿着纵坐标(如鼹鼠般高)向上移动。因为我们已经将鼹鼠准确安置在洞的正下方,所以它们窜出的位置也是合理的。

2.确保鼹鼠的移动看起来足够自然,将他们的移动行动与CCEaseInOut行动结合在一起。这么做能够确保开始和结束时的行动较慢,即鼹鼠能够加速/减速,以此让整体看起来更加自然。

3.创造一个行动让鼹鼠能够再次回到洞里。一个简单的方法便是调用行动的反方法。

4.创造一个行动让鼹鼠能够在窜出后一秒停下行动。

5.既然完成了行动设置,我们便能够运行它们而完成鼹鼠的一系列行动:窜出,延迟然后缩回洞里。同时我们还需要在最后终止这一系列行动以表示动作的完成。

编译并运行代码,你将能够看到鼹鼠们欢快地窜出洞口了!

Mole-Pop(from raywenderlich)

Mole-Pop(from raywenderlich)

游戏邦注:原文发表于2011年1月10日,所涉事件和数据均以当时为准。(本文为游戏邦/gamerboom.com编译,拒绝任何不保留版权的转载,如需转载请联系:游戏邦

posted @ 2013-05-16 08:50  bxddno1  阅读(249)  评论(0)    收藏  举报