讓我們首先創建一個工程骨架--使用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里面增加一些屬性吧,在那個文件中做以下修改:
// Add inside the HelloWorld interface
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,然后作如下改動:
// At the top, under @implementation
@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幀和紋理
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:
@"AnimBear.plist"];
首先,調用CCSpriteFrameCache的addSpriteFramesWithFile方法,然后把Zwoptex生成的plist文件當作參數傳進去。這個方法做了以下幾件事:
- 尋找工程目錄下面和輸入的參數名字一樣,但是后綴是.png的圖片文件。然后把這個文件加入到共享的CCTextureCache中。(這我們這個例子中,就是加載AnimBear.png)
- 解析plist文件,追蹤所有的sprite在spritesheet中的位置,內部使用CCSpriteFrame對象來追蹤這些信息。
2) 創建一個精靈批處理結點
CCSpriteBatchNode *spriteSheet = [CCSpriteBatchNode
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) 收集幀列表
NSMutableArray *walkAnimFrames = [NSMutableArray array];
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) 創建動畫對象
CCAnimation *walkAnim = [CCAnimation
animationWithFrames:walkAnimFrames delay:0.1f];
接下來,我們通過傳入sprite幀列表來創建一個CCAnimation對象,並且指定動畫播放的速度。我們使用0.1來指定每個動畫幀之間的時間間隔。
5) 創建sprite並且讓它run動畫action
CGSize winSize = [CCDirector sharedDirector].winSize;
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文件中做如下修改:
// Comment out the runAction method in the init method:
//[_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坐標點
CGPoint touchLocation = [touch locationInView: [touch view]];
touchLocation = [[CCDirector sharedDirector] convertToGL: touchLocation];
touchLocation = [self convertToNodeSpace:touchLocation];
這里沒什么新東西--我們僅僅是把touch點轉換成我們要使用的局部坐標系點。
2) 設置熊移動速度
float bearVelocity = 480.0/3.0;
這里,我們設置了熊的移動速度。我假設熊要花3秒鍾時間才能從iphone屏幕(480個像素寬)的一頭移動到另一頭。因此,簡單地用480個像素除以3秒。
3) 計算x軸和y軸的移動量
CGPoint moveDifference = ccpSub(touchLocation, _bear.position);
接下來,我們需要計算出熊相當於x軸和y軸移動了多遠。我們簡單地使用touch坐標減去熊當前的坐標。這里使用了cocos2d的一個幫助函數ccpSub來實現這個功能。
4) 計算實際移動的距離
float distanceToMove = ccpLength(moveDifference);
我們需要計算出熊實際移動的距離(歐幾里德距離)。cocos2d里面也提供了一個幫助函數來做這個事情,這個函數就是ccpLength,用來求一個向量的長度。
5) 計算移動需要花費的時間
float moveDuration = distanceToMove / bearVelocity;
最后,我們需要計算出熊要花費多長時間來走完這段路程,只需要拿距離除以速率就可以了。
6) 按照需要翻轉動畫
if (moveDifference.x < 0) {
_bear.flipX = NO;
} else {
_bear.flipX = YES;
}
接下來,我們通過判斷移動的差值,如果小於0,那么就不需要翻轉動畫,否則,就需要翻轉。因為我們的原畫里面,熊就是往左移動的,因此,當熊往左移動時,我們不需要翻轉動畫,而往右移動的時候,只需要翻轉動畫。
我們的第一直覺可能是用圖片編輯器重新創建另一套朝向不同的熊的動畫序列圖,然后使用它們。但是,cocos2d里面有一種更容易的方式(也更高效)--我們僅僅翻轉已經存在的圖片就行了。
這種方式可行,實現上,我們只是設置了運行動畫的sprite的flip屬性,但是它會使所有相關的動畫幀也相應地翻轉。在這個例子中,當熊往右行走的時候,我們就設置熊的flipX為Yes。
7) 運行合適的action
[_bear stopAction:_moveAction];
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!