游戏邦在:
杂志专栏:
gamerboom.com订阅到鲜果订阅到抓虾google reader订阅到有道订阅到QQ邮箱订阅到帮看

使用Cocos2D制作打鼹鼠游戏的教程(1)

发布时间:2012-06-25 18:23:49 Tags:,,

作者: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编译,拒绝任何不保留版权的转载,如需转载请联系:游戏邦

How To Create A Mole Whacking Game with Cocos2D: Part 1/2

10 January 2011

One of the students in the iOS Programming 101 workshop that Shawn Grimes and I recently offered requested that I write a tutorial on how to write a mole whacking game with Cocos2D.

I thought this was a great idea for a tutorial for three reasons:

1.We’ve had a lot of Cocos2D tutorials on this site, but it might be hard to see how to combine everything together to make a game. So this tutorial does exactly that!

2.It will be a good opportunity to cover a new topic: how to make a game that works on the iPhone, iPad, and iPhone retina display.

3.And of course the most important reason – whacking moles is fun!

This tutorial builds on the following Cocos2D tutorials:

How to Make a Simple Game with Cocos2D,

How to Use Animations and Sprite Sheets in Cocos2D

How to Create and Optimize Sprite Sheets in Cocos2D with Texture Packer and Pixel Formats

How to Drag and Drop Sprites with Cocos2D

If you have not reviewed these tutorials already (or have similar knowledge), I recommend you go through them first.

This is a two-part tutorial series. In this first part, we’ll create the basics of the game – cute little moles popping out of holes. We’ll spend a lot of time thinking about how to organize the art and coordinates so that the game looks good on the iPhone, iPad, and Retina display – and be efficient too! (Jump to the second part.)

Planning the Art: Overview

Since we want this app to work on both the normal iPhone, retina-display iPhone, and the iPad, we need to take some time to carefully plan out how the art is going to be set up before we proceed any further.

In order to understand how to propery size and set up the art, we need to cover three topics first:

Retina Display and UIKit

Retina Display and Cocos2D

iPad, iPhone, and Aspect Ratios

So let’s get started!

Retina Display and UIKit

The difference between a normal iPhone and a retina-display iPhone is the Retina display can show double the pixels. So (in landscape) instead of the display size being 480×320 px as it is on a normal iPhone, it’s 960×640 px on a Retina display.

“But wait a minute”, you may think, “wouldn’t doubling the number of pixels break all of the apps that were written assuming the 480×320 display size?” It would have (especially since when you’re programming with UIKit it’s common to hard-code frame sizes etc), except when you specify frame sizes in UIKit, you’re actually setting the sizes in points, not pixels.

On a normal iPhone, a point is defined to be exactly one pixel. But on a retina-display iPhone, a point is defined to be two pixels. So when you specify a location as (10,10) in points, it will be (10,10) on a normal iPhone, and (20,20) on a retina-display iPhone, so will appear to be at the same relative offset. Cool, eh?

When you’re using Apple’s controls or Core Graphics, Apple has already written the code to make things look nice and crisp on the Retina display.

The only trick comes into play when you use an image. Say you have a 200×200 image in an iPhone app. If you don’t do anything, on the Retina display it will just scale the image to be 2x larger – which won’t look that great because you aren’t taking advantage of the extra resolution available to you.

So what you need to do is provide another version for all of your images: a normal version, and one that is double the size. If you name your image with double the size with an “@2x” extension, whenever you try to load an image with [UIImage imageNamed:...] or similar APIs, it will automatically load the @2x image instead on the Retina display.

So making a UIKit app use the Retina display is pretty easy – just add @2X images and you’re done for the most part.

But what about Cocos2D?

Retina Display and Cocos2D

Well, there’s good news – the latest version of Cocos2D contains full support for the retina display, and makes it as easy as 1-2-3!

1.Call enableRetinaDisplay on CCDirector to enable retina display support when your app starts up. If you’re using the Cocos2D project templates, you can just uncomment the lines that do this in your app delegate.

2.Add double-sized sprites to your app, but instead of using the “@2x” extension, you use an “-hd” extension for Cocos2D. When loading your sprites, use hte normal name (without the “-hd” extension) – Cocos2D will automatically load the hd images on the Retina display.

3.Now you can use points instead of pixels when positioning your sprites in Cocos2D. Note some APIs (but not many of them) still have to deal with pixels – when that is the case, they will have pixel in the method name to make it clear, otherwise assume points.

When the rubber hits the road, the easiest thing to do is to have your artist make images at the highest-necessary resolution (i.e. the 2X size for the Retina display), and you can easily scale down the images for the normal iPhone yourself from there.

You might wonder why even bother having two different sized images – why not just always load the bigger image and just scale it programatically? Well, loading textures into memory is one of the most memory intensive aspects of an app, so if you’re running on a device that isn’t going to take advantage of the higher resolution images, it’s a big saving to load the smaller images that are intended for the device.

But don’t worry – you don’t need to be constantly scaling images down in Photoshop. Texture Packer actually has a nice feautre that makes it easy to create scaled down images given a full-resolution image, and that’s what we’ll be using in this tutorial.

iPad, iPhone, and Aspect Ratio

OK so dealing with the retina display is pretty easy, but what about the iPad?

Well, it turns out that there is a very annoying thing about making a game that works on both the iPhone and the iPad – the aspect ratio between the devices is different!

The iPhone is 480×320 or 960×640 – a 1.5 aspect ratio. However, the iPad is 768×1024 – a 1.33 aspect ratio.

This means that if you have an image that fills up the entire background of the iPad (768×1024) and want to re-use it for the iPhone too, it’s not going to fit exactly. Say you scale it down so it fits the width of the iPhone (multiply by 0.9375): you’ll get 720×960, so there will be extra stuff to the side that will get cut off!

This makes things kind of annoying, because not only do you run into problems with background images, but the aspect ratio also makes it difficult to use the same coordinates across devices.

There are several strategies for how to deal with this, here are a few I’ve seen/heard/used (feel free to chime in with any of your own solutions in the comments):

Have a “playable area” in the middle of the screen that is the size of the iPhone retina display (640×960). This will leave a little extra are around the edges – you can just cover that with a background and the player probably won’t even notice. This allows you to easily convert coordinates between devices and re-use art (high res used on iPad and retina, and normal res used on normal iPhone). This is what we’ll be doing in this tutorial.

You can make the iPad have a similar aspect ratio to the iPhone if you take 42 pixel gutters on each side of the iPad screen, and put the “main content” inside as 684×1024. If you make your content to fit within the 684×1024 box, you can scale down images for each device from there.

You could have different images for the iPhone, iPad, and Retina display (i.e. 3 sets) and different coordinates too. This allows maximum flexibility, but larger binary sizes and having to redo the positions of objects on different devices.

On and another complication – Cocos2D doesn’t currently have any helpful support for the iPad, in terms of loading images with the “-hd” extension automatically, converting coordinates, etc. That is up to you!

Planning the Art: Conclusion

OK, so based on the above discussion, here is the plan for this tutorial.

The art has been designed to be within a 960×640 “playable area”, used full-screen on retina-display iPhones, and centered in the iPad screen.

The art will then be scaled by Texture Packer to be 1/2 the size for normal iPhones.

The full sized-art will be named with the “-hd” extension, and the half size without, and Cocos2D will load the appropriate art based on whether the Retina display is enabled.

Backgrounds are a special case because they need to be fullscreen always. The backgrounds will be made to the 1024×768 size (iPad size) so the entire screen is filled. The same images will actually be used on the iPhone too since it’s close enough. Some of the background will be offscreen, but that doesn’t matter for this particular background.

The iPad version will contain code to use the “-hd” images, convert coordinates to inside the “playable area”, use the appropriate font sizes, etc.

Go ahead and download the art for this tutorial, made by my lovely wife. Unzip the file and take a look at how things are set up:

In the “foreground” folder, the foreground is 1024×768 (the size of the iPad), but it is actually split into two parts: the lower part, and the upper part. It’s split into two parts so we can place the mole in-between the lower and upper parts, to make him look like he’s going underground.

In the “background” folder, the background has the 1.33 aspect ratio of the iPad, but is actually half sized (512×384). This is becase the background barely shows (just through the three mole holes), so it’s not worth the cost of a large 1024×1024 texture load. Instead a small texture is loaded and scaled up.

In the “sprites” folder, all sprites were sized to fit nicely within the 960×640 “playable area”. Note there’s a mole, and two animations for him (the mole laughing, and the mole being hit).

Ok – enough background info – it’s time to get started!

Getting Started

Open up XCode, select “File\New Project…” from the main menu, choose “User Templates\cocos2d\cocos2d Application”, and click “Choose…”. Name the project WhackAMole, and click Save.

Next, take the “Art” folder that you downloaded earlier and copy it into your WhackAMole project directory using Finder. It should be a sibling to the build and Classes folders, as you can see below:

Next, make sure you have Texture Packer installed and ready to go on your machine. If you don’t have it already or know how to use it, check out this tutorial for more information.

You will now set up TexturePacker to create the sprite sheets you’ll need for this project. You’ll be doing everything by TexturePacker’s command line tools and XCode integration, so no need to use the TexturePacker GUI at all!

Right click on Resources, choose “Add\New File…”, choose Mac OS X\Other\Shell Script, and click Next. Name the file PackTextures.sh, and click Finish.

Then replace the contents of PackTextures.sh with the following:

This script runs TexturePacker to create a sprite sheets for the background image, the foreground images, and the sprite images – an HD and regular-quality image for each.

Note that each image is saved in the pvr.ccz format since it is the most efficient in terms of memory and disk space usage. Also the pixel format and dithering options were chosen to get a good tradeoff of quality and memory usage for each set of images.

If you’re unsure what the TexturePacker options do, load up Terminal and run TexturePacker –help to get a full description of each option.

Next, you need to set up your project to run this shell script when you compile. Right click on Targets, choose “Add\New Target…”, and choose “External Target” (not Shell Script Target!), and click Next. Name the Target TexturePacker, and click Finish.

Then double click on your TexturePacker target and set up the settings as follows:

The final step is to set this target as a dependency of your app. Double click on your TextureFun target, go to the General tab,click the + button in Direct Dependencies, choose Texture Packer from the list, and click Add Target.

Compile your app, and you should see the output from Texture Packer from your build results if everything is working OK.

Next, add the newly generated sprite sheets and property lists to your project. Right click on Resources and choose “Add\Existing Files…” and select the background, foreground, and sprite files (12 files total) from Resources and click Add, and then Add again. When you’re done your Groups & Files tree should look similar to the following:

If you’d like, you can double click on any of the .pvr.ccz files to load up a preview of what’s inside. This is a new (and handy) feature of Texture Packer!

Setting the Background

Next, open up HelloWorldScene.m and find your init method. Remove the four lines of code that create the Hello World label, and replace those lines with the following:

OK let’s go over this section by section, since there is a good amount of new material here.

Determine names of sprite sheets and plists to load. This section lists the names of the sprite sheets and plists generated by Texture Packer to be loaded. Note that on the iPhone, Cocos2D will automatically choose between the “-hd” versions and the normal versions based on whether the Retina display is enabled. However, the iPad won’t load the “-hd” version unless you tell it to, so we check if it’s an iPad and use the “-hd” versions if so.

Load background and foreground. The next step is to load the information about each sprite for the background and foreground into the sprite frame cache so that they can be used later. Note that these sprites won’t actually be added to a CCSpriteBachNode anywhere – since these images are just used once each it would be kind of pointless.

Add background. The background image is added as a child of the layer (with a z of -2 so it appears beneath everything else) next. It scales the image by 2 because we made it smaller on purpose to conserve space, and centers the image.

Add foreground. The foreground is added in two parts. As an easy way to place the image, it sets the anchor point to the middle/bottom for the top image, and the middle/top for the bottom image, and matches that anchor point up to the center of the screen. That way you don’t have to do any complicated math, and it shows up in the right place on all devices. Note that part of the background will be offscreen for iPhones, but that is OK for this background and barely even noticable. Also note that the images are added with different z values, so the lower image appears on top.

Compile and run the code, and you should now see the background and foreground on the screen! Give it a try on both the iPhone and iPad simulators to make sure that it appears OK on both devices.

If you try the code on the retina display and zoom in, however, you’ll notice that it’s still using the normal artwork:

That’s because we still haven’t done step 1 from the “Retina Display and Cocos2D” section earlier: call enableRetinaDisplay on CCDirector to enable retina display support when your app starts up.

To do this, open up WhackAMoleAppDelegate.m, and inside applicationDidFinishLaunching uncomment the following three lines:

Compile and run your code, and now when you try it on the retina display you should see it automatically make use of the HD files, due to Cocos2D’s built in retina display support!

Placing the Moles

For this game, you’re going to add three moles to the scene – one for each hole. The moles will usually be “underground” beneath the lower part of the grass – but occasionally they will “pop up” so you can try to whack them.

First, let’s add the moles to the level underneath each of the holes. We’ll temporarily make them appear above all the other art so we can make sure they’re in the right spot, then we’ll put them underground once we’re happy with their position.

Open up HelloWorldScene.h and add an array to keep track of the moles in the level, as shown below:

By storing the moles in this array, it will make it easy to loop through each of the moles later on.

Next, add the code to place the moles at the end of your init method (where it says “Add more here later…”), as shown below:

This first creates a CCSpriteBatchNode for the sprites, so that drawing the three moles is done more efficiently, and adds it as a child of the layer. Note it’s setting the z value to 999 temporarily, so that the moles apepar on top so we can make sure they’re set up OK.

It then loads all of the sprite frames from the property list to the cache, so they can be pulled out later.

Then it goes through and creates a sprite for each mole, places them in the scene, and adds them to the list of moles. Note the coordinate for each mole is within the 480×320 “playable area” of the game (the size of the iPhone). For the iPad, these points will need to be converted, so it calls a helper function convertPoint which we’ll write next.

Add the following method right above the init method:

This method converts a point in the “playable area” to the appropriate screen positionon the iPad. Remember that:

We’re using the HD graphics on the iPad, so all points are doubled.

We’re centering that 960×640 area in the 1024×968 iPad screen, so that leaves 32 pixel margins on the left and right, and 64 pixel margins on the top and bottom.

So this method simply does that math to give the right position on the iPad.

One more thing – before we forget, add the following lines to clean up the memory we allocated for the moles array in the dealloc method:

Compile and run your code, and you should see the three moles happily in the scene at the correct spots! You should try the code on the iPhone, iPhone Retina, and iPad to make sure that they’re in the right spot on each device.

Popping the Moles
Now that we’re sure the moles are in the right place, let’s add the code to make them pop out of their holes.

First things first – switch the z-value of 999 for the mole sprite sheet back to 0 so the moles are underground.

Once that’s done, add the following line of code to the bottom of your init method:

If you haven’t seen this before, you can run the schedule method on a node to tell Cocos2D to call another method every so many seconds. In this case, we want to try popping some moles out of their holes every 1/2 second.

Next, add the implementation of tryPopMoles:

This method will be called every 1/2 second, and each time it will loop through each mole and give it a 1 in 3 chance of popping out of its hole. But it will only pop out if it isn’t moving already – and one easy way to check for this is to see if the numbef running actions is 0.

Finally, add the implementation of popMole:

This code uses some Cocos2D actions to make the mole pop out of it’s hole, pause for half a second, then pop back down. Let’s go through this line by line to make sure we’re on the same page:

1.Creates an action to move the mole move up along the Y axis as much as the mole is tall. Since we placed the mole right below the hole, it will look right.

2.To make the movement look more natural, it wraps the move action with a CCEaseInOut action. This causes the action to go slower at the beginning and end, as if the mole is accelerating/decellerating, as it naturally would.

3.To create an action to move the mole move back down again, an easy way is to call the reverse method on an action, which will give its opposite.

4.Creates an action to pause for one second after the mole pops out.

5.Now that the actions are ready to go, it runs them on the mole in a sequence: move up, delay, and finally move down. Note it has to terminate the sequence with a nil to show it’s done.

That’s it! Compile and run the code, and you’ll see the moles happily popping out of their holes!(source:raywenderlich)

 


上一篇:

下一篇: