免责申明(必读!):本博客提供的所有教程的翻译原稿均来自于互联网,仅供学习交流之用,切勿进行商业传播。同时,转载时不要移除本申明。如产生任何纠纷,均与本博客所有人、发表该翻译稿之人无任何关系。谢谢合作!
原文链接地址:http://www.raywenderlich.com/1271/how-to-use-animations-and-sprite-sheets-in-cocos2d
教程截图:
在这个博客中,我收到了大量的读者来信说,你能不能写一个关于如何在cocos2d里面使用动画和spritesheet的教程。这篇教程就应运而生了!
在这个教程里,我将向大家展示如何用cocos2d来制作一只熊在走路的动画。同时,我会使用spritesheet来使动画运行效率更高,还有如何让用户鼠标点击决定熊的行走方向,以及怎样基于熊当前行进的方向改变熊的面朝方向。
如果你对cocos2d完全陌生的话,你可能需要先阅读《怎么使用cocos2d来制作简单的iphone游戏》这一系列的教程,但是也不一定!(如果说你已经有相关经验就另当别论了)
Getting Started
让我们首先创建一个工程骨架--使用cocos2d工程模板创建一个新的项目并取名为AnimBear.
接下来,下载一些由我的老婆制作的熊行走的图片。(老婆会美工多好啊!)
当你解压之后,看看那些图片---它们仅仅是一张张单个的熊在行走的动画帧。但是,当你把它们连续地放映,就会看到一只熊在移动。
现在,把这些图片加到工程里面,然后基于这些单个的图片来创建动画。然后,在cocos2d里面,还有另一种更加高效的方式来创建动画--那就是使用spritesheet。(也叫精灵表单)
精灵表单和熊
如果你从来没有使用过spritesheet,你可以把它看作是一张巨大的图片,你可以把许许多多的sprite放进去。与spritesheet对应的,还有一个plist文件,这个文件指定了每个独立的sprite在这张“大图”里面的位置和大小,当你在代码之间需要使用这个sprite的时候,就可以很方面地使用plist文件中的这些信息来获取sprite。
为什么这会提高效率呢?因为cocos2d对它进行了优化!如果你使用spritesheet来获取sprite,那么当场景中有许多sprite的时候,如果这些sprite共享一个spritesheet,那么cocos2d就会使用一次OpenGL ES调用来渲染这些sprite。但是,如果是单个的sprite的话,那么就会有N次OpenGL ES call,这个代价是相当昂贵的。
简而言之--使用spritesheet会更快,尤其是当你有很多的sprite的时候!(使用spritesheet还可以减少游戏占用的内存大小,具体参考我翻译的文章《在cocos2d里面如何使用TexturePacker和像素格式来优化spritesheet》)
由于要使用spritesheet,你当然可以手工用图片编辑器来创建,然后创建一个plist指定每一个sprite在spritesheet里面的位置和大小。然后,那样将会是一个非常傻比的行为,因为Robert Payne已经开发出了一个非常好用的工具,叫做Zwoptex,它可以帮助我们自动生成这一切!
Zwoptex To Victory!
如果你还没有这个工具,那么可以从 zwoptexapp.com上面下载。它有一个免费的Flash版本和一个收费的安装版,但是最近我使用的是可安装的版本。
安装完这个工具之后,选择File\New,然后你将会看到一个空白窗口。打开你先前下载的熊的图片,并把它们拖到这个窗口里面。
你会看到,所有的熊的图片都层叠在一起。我们需要将他们摊开放在spritesheet上面,因此在Layout部分点击“Apply”来排序。
当你这样做以后,你会注意到,默认的画布(512×512)太小了,不足以把所有的熊图片装下。所以,还会有一些图片层叠在一起。因此,我们在Canvas部分把画布改成512×1024,然后在Layout部分点击“Apply”来重新排序它们。
我们马上要完成了--但是,请注意,有些熊的图片比其它宽一些。如果你看一下原图,你会发现和原图尺寸不一样了--这是因为,Zwoptex在默认情况下会把图片周围的透明区域剪裁掉。
对于这些图片,它们并不是我们最终需要的,因为对于动画来说,这些图片的位置信息已经错乱了(由于透明区域的裁剪)。还好,这非常容易解决--在工具栏上选择”Untrim“,然后再点”Apply“。
这时,你的窗口可能和下图类似:
就这么多,让我们保存spritesheet图片和定义,这样我们就可以在程序中使用它们啦.
点击Export部分的”Save.png“,把这个文件取名为”AnimBear.png“并保存到你的resources文件夹下面。然后点击”Save.plist“,命名为”AnimBear.plist“,同样保存到你的resources文件夹下面。
更新:当我们在Zwoptex里面点击保存的时候,确保选择”Cocos2d“作为导出格式,否则你的plist文件就不能正确在cocos2d里面使用!谢谢Muhammad在评论部分给我指出来了!
现在,让我们回到XCode,然后把刚刚这两个文件加进去。或击点击Resources文件夹,选择”Add\Existing Files。。。“,选择AnimBear.png和AnimBear.plist文件,然后点增加。
好了,让我们打开AnimBear.plist文件,看看Zwoptex到底为我们做了些什么事。你将会看到它仅仅是一个包含两个section的属性文件--两个部分分别为frames和metadata。在frames部分,包含了一系列的对spritesheet中每个图片的描述信息,这些描述信息里面包含了图片在spritesheet中的位置、大小和名字等信息。很cool,不是吗?
但是,如果你能让这只熊动起来,那将会更酷!下面就跟着我,一步步地让熊动起来!
简单的动画
首先,让我们把熊放在屏幕中间,然后循环播放所有的动画帧,这样看起来熊就在永远的移动,这里仅仅是先让代码可以跑起来。
因此,让我们在HelloWorldScene.h里面增加一些属性吧,在那个文件中做以下修改:
CCSprite *_bear;
CCAction *_walkAction;
CCAction *_moveAction;
BOOL _moving;
// Add after the HelloWorld interface
@property (nonatomic, retain) CCSprite *bear;
@property (nonatomic, retain) CCAction *walkAction;
@property (nonatomic, retain) CCAction *moveAction;
实际上,我们并不是马上需要所有的这些属性,但是,我们把它们先定义在这里,这样,等下我们就不用回过头来再改代码了。
现在,有趣的部分来了!打开HelloWorldScene.m,然后作如下改动:
@synthesize bear = _bear;
@synthesize moveAction = _moveAction;
@synthesize walkAction = _walkAction;
// In dealloc
self.bear = nil;
self.walkAction = nil;
// Replace the init method with the following
-(id) init {
if((self = [super init])) {
// Add the stuff from below!
}
return self;
}
为了获得动画效果,我们有5个步骤需要做。接下,将会一个步骤一个步骤给大家讲解。把下面的一些代码片断按顺序增加到你的init的Add the stuff from below注释后面。
1) 缓冲sprite帧和纹理
@"AnimBear.plist"];
首先,调用CCSpriteFrameCache的addSpriteFramesWithFile方法,然后把Zwoptex生成的plist文件当作参数传进去。这个方法做了以下几件事:
- 寻找工程目录下面和输入的参数名字一样,但是后缀是.png的图片文件。然后把这个文件加入到共享的CCTextureCache中。(这我们这个例子中,就是加载AnimBear.png)
- 解析plist文件,追踪所有的sprite在spritesheet中的位置,内部使用CCSpriteFrame对象来追踪这些信息。
2) 创建一个精灵批处理结点
batchNodeWithFile:@"AnimBear.png"];
[self addChild:spriteSheet];
接下来,创建CCSpriteBatchNode对象,把spritesheet当作参数传进去。spritesheet在cocos2d中的工作原理如下:
- 你创建一个CCSpriteBatchNode对象,通过传递一个包含所有sprite的spritesheet的名字作为参数,并把它加入到当前场景之中。
- 接下来,你从spritesheet中创建的任何sprite,你应该把它当作CCSpriteBatchNode的一个孩子加进去。只要sprite包含在spritesheet中,那么就没问题,否则会出错。
- CCSpriteBatchNode可以智能地遍历它的所有的孩子结点,并通过一次OpenGL ES call来渲染这些孩子,而不是以前每个sprite都需要一个OpenGL call,这样渲染速度就会更快。
注意:CCSpriteBatchNode以前叫做CCSpriteSheet,你可能会在一起比较老的代码里面看见它。
3) 收集帧列表
for(int i = 1; i <= 8; ++i) {
[walkAnimFrames addObject:
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
[NSString stringWithFormat:@"bear%d.png", i]]];
}
为了创建一系列的动画帧,我们简单地遍历我们的图片名字(它们是按照Bear1.png-->Bear8.png的方式命名的),然后使用共享的CCSpriteFrameCache来获得每一个动画帧。记住,它们已经在缓存里了,因为我们前面调用了addSpriteFramesWithFile方法。
4) 创建动画对象
animationWithFrames:walkAnimFrames delay:0.1f];
接下来,我们通过传入sprite帧列表来创建一个CCAnimation对象,并且指定动画播放的速度。我们使用0.1来指定每个动画帧之间的时间间隔。
5) 创建sprite并且让它run动画action
self.bear = [CCSprite spriteWithSpriteFrameName:@"bear1.png"];
_bear.position = ccp(winSize.width/2, winSize.height/2);
self.walkAction = [CCRepeatForever actionWithAction:
[CCAnimate actionWithAnimation:walkAnim restoreOriginalFrame:NO]];
[_bear runAction:_walkAction];
[spriteSheet addChild:_bear];
我们首先通过spriteframe来创建一个sprite,并把它放在屏幕中间。然后,生成CCAnimationAction,并赋值给场景的walkAction属性,最后让熊来运行这个action。
最后,我们把熊加个场景中--把它当作spritesheet的孩子加到spritesheet中去。注意,如果在这里我们没有把它加到spritsheet中,而是加到当前层里面的话。那么我们将得不到spritesheet为我们带来的性能提升!!!
完成了!
就这么多!编译并运行,你将会看到一只熊欢快地在屏幕上面走动!
基于熊的移动方向改变熊的朝向
一切看起来好极了--除了我们并不想让熊自己独自一个人走之外,那太危险了!如果我们能够通过点击屏幕就可以想让熊往哪走,它就会往哪走的话,那就太棒了.
因此,在HelloWorldScene.m文件中做如下修改:
//[_bear runAction:_walkAction];
// And add this to the init method
self.isTouchEnabled = YES;
// Add these new methods
-(void) registerWithTouchDispatcher
{
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0
swallowsTouches:YES];
}
-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
return YES;
}
-(void) ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event {
// Stuff from below!
}
-(void)bearMoveEnded {
[_bear stopAction:_walkAction];
_moving = FALSE;
}
开始之前,我们先把init方法中的运行行走action的代码注释掉,因为我们并不想让熊自己动,直到我们发出指令之后,它才能动!
我也设置了层能够接收touch事件,然后实现了registerWithTouchDispatcher和ccTouchBegan方法。如果你对使用这个方法的好处感到好奇的话(为什么不使用ccTouchesBegan呢?),你可以查看《如何在cocos2d里面制作基于Tile地图的游戏教程》。(当前是英文,以后会更新)
当bearMoveEnded方法被调用的时候,我们想让熊停止任何正在运行的动画,并且设置标记为不再移动。
看到ccTouchEnded方法,那里就是待会要实现功能的地方。那儿有许多东西要实现,因此,让我把它们分解成一些小片断,一步步向众位看官道来:
1) 计算touch坐标点
touchLocation = [[CCDirector sharedDirector] convertToGL: touchLocation];
touchLocation = [self convertToNodeSpace:touchLocation];
这里没什么新东西--我们仅仅是把touch点转换成我们要使用的局部坐标系点。
2) 设置熊移动速度
这里,我们设置了熊的移动速度。我假设熊要花3秒钟时间才能从iphone屏幕(480个像素宽)的一头移动到另一头。因此,简单地用480个像素除以3秒。
3) 计算x轴和y轴的移动量
接下来,我们需要计算出熊相当于x轴和y轴移动了多远。我们简单地使用touch坐标减去熊当前的坐标。这里使用了cocos2d的一个帮助函数ccpSub来实现这个功能。
4) 计算实际移动的距离
我们需要计算出熊实际移动的距离(欧几里德距离)。cocos2d里面也提供了一个帮助函数来做这个事情,这个函数就是ccpLength,用来求一个向量的长度。
5) 计算移动需要花费的时间
最后,我们需要计算出熊要花费多长时间来走完这段路程,只需要拿距离除以速率就可以了。
6) 按照需要翻转动画
_bear.flipX = NO;
} else {
_bear.flipX = YES;
}
接下来,我们通过判断移动的差值,如果小于0,那么就不需要翻转动画,否则,就需要翻转。因为我们的原画里面,熊就是往左移动的,因此,当熊往左移动时,我们不需要翻转动画,而往右移动的时候,只需要翻转动画。
我们的第一直觉可能是用图片编辑器重新创建另一套朝向不同的熊的动画序列图,然后使用它们。但是,cocos2d里面有一种更容易的方式(也更高效)--我们仅仅翻转已经存在的图片就行了。
这种方式可行,实现上,我们只是设置了运行动画的sprite的flip属性,但是它会使所有相关的动画帧也相应地翻转。在这个例子中,当熊往右行走的时候,我们就设置熊的flipX为Yes。
7) 运行合适的action
if (!_moving) {
[_bear runAction:_walkAction];
}
self.moveAction = [CCSequence actions:
[CCMoveTo actionWithDuration:moveDuration position:touchLocation],
[CCCallFunc actionWithTarget:self selector:@selector(bearMoveEnded)],
nil];
[_bear runAction:_moveAction];
_moving = TRUE;
接下来,我们停止任何正在运行的action。(因为我们将要覆盖任何已经存在的命令,让它移动到其它地方去!)。当然,如果我们没有移动,我们也需要停止任何动画action。(防止意外情况)。如果我们已经在移动了,那么我们当然需要停止,因为这样就不会影响后面运行的action。(这段话有些绕口,大家仔细体会,就是说,我们在让一个sprite运行一个atcion之前,最好先让它停止任何已经在运行的action。)
最后,我们创建移动action,指定移动的位置,花费的时间,并且指定一个回调函数,这个函数会在熊移动到指定位置之后被调用。我们也需要记录,我们移到那个点了!
完成啦!
写了好多代码啊---但是值得,不是吗?编译并运行,然后点击屏幕,你将会看到一只熊在屏幕上面移动。
何去何从?
这里有这个教程的完整源代码。
现在,你应该知道如何在项目里面使用spritesheet了吧。你可以在你的项目中创建自己的动画,然后看看你到底能做些什么有趣的事情!just do it!