使用Cocos2D制作簡單iPhone游戲的教程
作者:Ray Wenderlich
Cocos2D是個用于iPhone的強大開發庫,可以為你的iPhone游戲開發節省大量的時間。它帶有精靈支持、炫麗的圖像效果、動畫、實體庫、音效引擎等等內容。
我剛剛開始學習使用Cocos2D,盡管有各種各樣的Cocos2D初學教程,但我無法找到我真正想要的教程,即制作一款帶有動畫、碰撞和音頻的簡 單游戲,不帶有過多的高級功能。最終,我自行制作了一款簡單的游戲,我覺得需要根據此次開發經驗編寫教程,以便能為其他初學者所使用。
該教程將知道開發簡單iPhone游戲的從頭到尾的步驟。你可以按順序學習,或者直接跳到文章末尾的示例項目上。
下載和安裝Cocos2D
你可以從Cocos2D Google Code頁面下載Cocos2D。
下載代碼后,你需要做的就是安裝有用的項目模板。打開末端窗口,輸入以下命令:./install-templates.sh -f -u。
必須注意的是,如果你有在非標準directory上安裝過XCode,你可以隨意將參數輸入安裝文本中(游戲邦注:就像在同一臺電腦上有多個SDK版本的做法一樣)。
開始
現在,讓我們先開始創建簡單的“Hello World”項目,并使用剛剛安裝的Cocos2D模板來運行。打開XCode并新建Cocos2D項目,選擇cocos2d中的“Application”模板,將項目命名為“Cocos2DSimpleGame”。

NewProject(from raywenderlich.com)
繼續構建并運行這個模板。如果不出差錯的話,你將會看到如下畫面:

HelloWorld(from raywenderlich.com)
Cocos2D中融入了“界面”的概念,就像游戲中的“關卡”或“屏”一樣。比如,游戲主菜單需要一個界面,主動作部分也需要一個界面,游戲結束同 樣需要一個界面。界面分為多個層(游戲邦注:類似于Photoshop),曾包含精靈、標簽、菜單等節點。節點也可能包含其他節點(游戲邦注:比如精靈中 可能有個子精靈)。
看下上述實例項目,你會發現其中只含有一個層,即HelloWorldLayer,我們將在此開始實施主要的游戲玩法。繼續將其打開,你會發現現在初始方法中有個名為“Hello World”的標簽。我們把這個標簽拿走,用精靈來替換。
添加精靈
在我們添加精靈之前,我們需要某些圖片。你可以用自己設計的圖片,也可以使用這個項目中使用的圖片,包括玩家圖片、拋射物圖片和目標圖片。
獲得圖片后,將它們拖到XCode的資源文件夾中,確認“Copy items into destination group’s folder (if needed)”前打鉤。
現在我們已經有圖片了,下一步就需要為玩家圖片指定位置。必須注意的是,在Cocos2D中屏幕左下角的坐標為(0, 0),往右或往上移動分別使X軸和Y軸值增加。因為這個項目采用的是風景模式,這意味著右上角的坐標為(480, 320)。
還必須注意的是,當我們設定某個物體的位置時,其位置的參考點所添加的精靈的中心點。因而,如果我們要讓玩家圖表在橫軸上與邊界對其,縱軸位于中心,我們應該:
1、位置的X軸坐標設置為“(玩家精靈寬度)/2”
2、位置的Y軸坐標設置為“(窗口高度)/2”
下圖清晰地顯示所設置的玩家精靈的位置:

Sprite Coordinates(from raywenderlich.com)
接下來,打開Classes文件夾,點擊HelloWorldLayer.m,將初始方法用以下代碼替換:
-(id) init { if( (self=[super init] )) { CGSize winSize = [[CCDirector sharedDirector] winSize]; CCSprite *player = [CCSprite spriteWithFile:@"Player.png" rect:CGRectMake(0, 0, 27, 40)]; player.position = ccp(player.contentSize.width/2, winSize.height/2); [self addChild:player]; } return self; }
編輯運行,你會看到精靈顯示完好,但背景默認為黑色。對于這個項目來說,白色的背景看起來會好很多。自定義Cocos2D中的層背景的方式之一是使用CCLayerColor。點擊HelloWorldLayer.h,將HelloWorld界面聲明定義如下:
@interface HelloWorldLayer : CCLayerColor
然后點擊HelloWorldLayer.m,對初始方法做以下些許修改就可以將背景設定為白色:
if( (self=[super initWithColor:ccc4(255,255,255,255)] )) {
繼續編輯運行,你應該會看到精靈正位于白色的背景之上。

精靈正位于白色的背景之上(from raywenderlich)
移動目標
接下來,我們將在界面中添加某些供忍者攻擊的目標。為讓游戲更為有趣,我們將目標設定為可以移動,否則游戲難度就太低了!因而,我們先將目標位置設定在右側屏幕之外,然后為其設定動作,逐漸向左移動。
在最初方法之前添加以下方法:
-(void)addTarget { CCSprite *target = [CCSprite spriteWithFile:@"Target.png" rect:CGRectMake(0, 0, 27, 40)]; // Determine where to spawn the target along the Y axis CGSize winSize = [[CCDirector sharedDirector] winSize]; int minY = target.contentSize.height/2; int maxY = winSize.height – target.contentSize.height/2; int rangeY = maxY – minY; int actualY = (arc4random() % rangeY) + minY; // Create the target slightly off-screen along the right edge, // and along a random position along the Y axis as calculated above target.position = ccp(winSize.width + (target.contentSize.width/2), actualY); [self addChild:target]; // Determine speed of the target int minDuration = 2.0; int maxDuration = 4.0; int rangeDuration = maxDuration – minDuration; int actualDuration = (arc4random() % rangeDuration) + minDuration; // Create the actions id actionMove = [CCMoveTo actionWithDuration:actualDuration position:ccp(-target.contentSize.width/2, actualY)]; id actionMoveDone = [CCCallFuncN actionWithTarget:self selector:@selector(spriteMoveFinished:)]; [target runAction:[CCSequence actions:actionMove, actionMoveDone, nil]]; }
在這里,我用較為冗長的代碼來使其中的動作更容易讓人理解。第一部分代表的是我們之前討論過的內容:我們做些簡單的計算來設定所創建物體的位置,并以與設定玩家精靈同樣的方式放置在場景中。
這里的新元素就是動作的添加。Cocos2D提供了大量內置動作,你可以用來設定精靈動作,比如移動、跳躍、消失及其他動畫動作。在這里,我們在目標上使用了以下三個動作:
CCMoveTo:我們使用CCMoveTo動作來指導物體從屏幕之外逐漸向左移動。你會注意到,我們可以指定移動的間隔時長,這里我們設定時長在2秒至4秒間隨意變化。
CCCallFuncN:CCCallFuncN功能允許我們指定在動作發生時物體發出回叫信號。我們指定一種稱為“spriteMoveFinished”的回叫信號。
CCSequence:CCSequence動作讓那個我們可以設置系列動作按次序發生,每次一個動作。以這種方式,我們可以首先執行CCMoveTo動作,等該動作結束后再執行CCCallFuncN動作。
接下來,便是添加我們在CCCallFuncN動作中提到的回叫功能。我們可以將以下代碼添加至addTarget之前:
-(void)spriteMoveFinished:(id)sender { CCSprite *sprite = (CCSprite *)sender; [self removeChild:sprite cleanup:YES]; }
該功能的目標在于,一旦精靈移動出屏幕后,就將其從屏幕中移除。這一點非常重要,這樣就不會有大量無用的精靈存在于屏幕之上了。應該注意的是,還有其他更好的辦法來處理這個問題,比如重復使用精靈,但在這個新手教程中我們采取這種較為簡單的做法。
最后需要做的事情是,我們需要調用創造目標的方法!為讓游戲變得有趣,我們讓目標隨時間不斷變化。在Cocos2D中,我們可以通過將回叫功能設定為周期性回叫來實現這個目標。在初始方法中添加以下回調代碼:
[self schedule:@selector(gameLogic:) interval:1.0];
然后以下列方法來執行回調功能:
-(void)gameLogic:(ccTime)dt { [self addTarget]; }
現在,如果你編輯并運行項目,你應該會看到目標在屏幕中移動,如下圖所示:

目標在屏幕中移動(from raywenderlich)
射擊拋射物
此時此刻,我們的忍者正希望能夠有所動作,因而讓我們添加射擊動作!執行射擊動作的方式有許多種,但是在這款游戲中,我們將設計成,當用戶點擊屏幕時,忍者就會朝點擊點的方向射出拋射物。
接下來,我將使用CCMoveTo動作來實現此目標,以使新手能夠容易理解。為了實現這個目標,我們需要做些數學運算。這是因為CCMoveTo需 要我們指定拋射物的落點,但是我們不能只使用接觸點,因為接觸點代表的只是玩家角色射擊的方向而已。我們想保證射出的子彈能夠通過接觸點然后射出屏幕。
下圖顯示我們所要做的工作:

保證子彈能夠通過接觸點然后射出屏幕(from raywenderlich)
因此,你可以看到原點和觸點以及X和Y軸平行線組成了一個小三角形。我們只需要以同等的比例來繪制出大三角形,將一個落點設置在屏幕之外。
接下來,要處理的是代碼問題。首先,我們要激活層上的觸點。在初始方法中添加下列代碼:
self.isTouchEnabled = YES;
激活層上的接觸之后,我們現在可以接收到接觸事件的回調函數。因而,讓我們執行ccTouchesEnded方法,當用戶接觸屏幕時就可以產生回調,使用下列代碼:
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { // Choose one of the touches to work with UITouch *touch = [touches anyObject]; CGPoint location = [touch locationInView:[touch view]]; location = [[CCDirector sharedDirector] convertToGL:location]; // Set up initial location of projectile CGSize winSize = [[CCDirector sharedDirector] winSize]; CCSprite *projectile = [CCSprite spriteWithFile:@"Projectile.png" rect:CGRectMake(0, 0, 20, 20)]; projectile.position = ccp(20, winSize.height/2); // Determine offset of location to projectile int offX = location.x – projectile.position.x; int offY = location.y – projectile.position.y; // Bail out if we are shooting down or backwards if (offX <= 0) return; // Ok to add now – we’ve double checked position [self addChild:projectile]; // Determine where we wish to shoot the projectile to int realX = winSize.width + (projectile.contentSize.width/2); float ratio = (float) offY / (float) offX; int realY = (realX * ratio) + projectile.position.y; CGPoint realDest = ccp(realX, realY); // Determine the length of how far we’re shooting int offRealX = realX – projectile.position.x; int offRealY = realY – projectile.position.y; float length = sqrtf((offRealX*offRealX)+(offRealY*offRealY)); float velocity = 480/1; // 480pixels/1sec float realMoveDuration = length/velocity; // Move projectile to actual endpoint [projectile runAction:[CCSequence actions: [CCMoveTo actionWithDuration:realMoveDuration position:realDest], [CCCallFuncN actionWithTarget:self selector:@selector(spriteMoveFinished:)], nil]]; }
在第一部分中,我們選擇了一個觸點,獲得觸點在當前視圖中的位置,然后調用convertToGL將坐標轉化成當前的布局。這一點很重要,因為我們使用的是風景模式。
接下來,我們裝載拋射物精靈,像往常那樣設置初始位置。隨后,我們決定希望拋射物移向何處,根據之前描述的運算法則,使用玩家和觸點間的矢量作為指導方向。
應該注意的是,運算法則并不是很理想。我們強迫子彈一直移動,直到其到達屏幕外的X點。
我們必須做的最后一件事是決定移動的間隔。我們想要讓子彈以恒定不變的速率沿某個方向射出,因而我們還得做些數學運算。利用Pythagorean Theorem,我們可以推導出移動的速度。從幾何學公式上看,三角形斜邊的平方等于兩邊的平方和。只要我們得到移動的距離,將其除以速度就可以得到間隔 時間。
剩下的工作就是像目標那樣設定動作。編輯運行,現在你的忍者可以以恒定的速率射出子彈了。

你的忍者可以以恒定的速率射出子彈了(from raywenderlich)
碰撞檢測
現在,我們已經將忍者的武器射擊設計完成了。但是在游戲中,忍者想要做的是擊落目標。所以,讓我們添加某些發現拋射物擊中目標的代碼。
在Cocos2D中的解決方案多種多樣,包括使用物理庫Box2D或Chipmunk。但是為保持這個教程的簡單性,我們決定自行設置一種簡單的碰撞檢測。
為實現這個目標,我們首先需要將目標和拋射物設計在當前界面中。
添加以下代碼至HelloWorldLayer中:
NSMutableArray *_targets; NSMutableArray *_projectiles;
然后用初始方法來初始化:
_targets = [[NSMutableArray alloc] init]; _projectiles = [[NSMutableArray alloc] init];
清除dealloc方法中的記憶:
[_targets release]; _targets = nil; [_projectiles release]; _projectiles = nil;
現在,修改addTarget方法,添加新目標至目標列表并設定供以后使用的標簽:
target.tag = 1; [_targets addObject:target];
然后修改ccTouchesEnded方法,添加新拋射物至拋射物列表并設定供以后使用的標簽:
projectile.tag = 2; [_projectiles addObject:projectile];
最后,修改spriteMoveFinished方法,根據標簽從恰當的列表中移除內容:
if (sprite.tag == 1) { // target [_targets removeObject:sprite]; } else if (sprite.tag == 2) { // projectile [_projectiles removeObject:sprite]; }
編輯運行項目,保證項目仍然運轉良好。現在應該還看不出什么很明顯的差別,但是我們需要執行某些接觸察覺。
添加下列方法至HelloWorldLayer:
- (void)update:(ccTime)dt { NSMutableArray *projectilesToDelete = [[NSMutableArray alloc] init]; for (CCSprite *projectile in _projectiles) { CGRect projectileRect = CGRectMake( projectile.position.x – (projectile.contentSize.width/2), projectile.position.y – (projectile.contentSize.height/2), projectile.contentSize.width, projectile.contentSize.height); NSMutableArray *targetsToDelete = [[NSMutableArray alloc] init]; for (CCSprite *target in _targets) { CGRect targetRect = CGRectMake( target.position.x – (target.contentSize.width/2), target.position.y – (target.contentSize.height/2), target.contentSize.width, target.contentSize.height); if (CGRectIntersectsRect(projectileRect, targetRect)) { [targetsToDelete addObject:target]; } } for (CCSprite *target in targetsToDelete) { [_targets removeObject:target]; [self removeChild:target cleanup:YES]; } if (targetsToDelete.count > 0) { [projectilesToDelete addObject:projectile]; } [targetsToDelete release]; } for (CCSprite *projectile in projectilesToDelete) { [_projectiles removeObject:projectile]; [self removeChild:projectile cleanup:YES]; } [projectilesToDelete release]; }
以上代碼看起來應該很清楚。我們只是不斷重復拋射物和目標,創造與其界限盒保持一致的矩形,利用CGRectIntersectsRect來尋找交 叉點。如果找到了交叉點,我們將他們從界面和列表中移除。應該注意的是,我們必須將物體添加至“toDelete”列表中,因為你無法在重復物體時將其從 列表中移除。依然有多種方法可以實現這個目標,我還是會闡述較為簡單的方法。
在我們開始運行項目之前,還必須做一件事情,添加以下代碼至初始方法中,安排該方法以盡量高的頻率運行:
[self schedule:@selector(update:)];
編輯運行,現在你的拋射物命中目標時,它們應該會同時消失!
末期工作
現在,我們幾乎快完成了這款游戲(游戲邦注:盡管游戲較為簡單)。我們只需要添加些許音效和音樂以及某些簡單的游戲邏輯。
如果你看過我關于iPhone游戲音頻編程設計的系列博文,你就會知道Cocos2D開發者用此工作來在游戲中添加基礎音效是多么的簡單。
首先,拖動背景音樂和射擊音效到資源文件夾中。你可以使用我所制作的背景音樂和音效,也可以自行制作。
然后,在HelloWorldLayer.m頂端添加下列導入:
#import “SimpleAudioEngine.h”
在初始方法中,以下列代碼來控制背景音樂的播放:
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@”background-music-aac.caf”];
在ccTouchesEnded方法中,以下列代碼來播放音效:
[[SimpleAudioEngine sharedEngine] playEffect:@”pew-pew-lei.caf”];
現在,讓我們創造一個新界面,用來顯示玩家的勝利或失敗。點擊Classes文件夾,然后點擊File\New File,選擇Objective-C類,確認子類NSObject已被選擇。點擊Next,然后輸入GameOverScene作為文件名,確認 “Also create GameOverScene.h”已選擇。
然后以下列代碼替換GameOverScene.h:
#import “cocos2d.h”@interface GameOverLayer : CCLayerColor { CCLabelTTF *_label; } @property (nonatomic, retain) CCLabelTTF *label;@end @interface GameOverScene : CCScene { GameOverLayer *_layer; } @property (nonatomic, retain) GameOverLayer *layer; @end
然后以下列代碼替換GameOverScene.m:
#import “cocos2d.h”@interface GameOverLayer : CCLayerColor { CCLabelTTF *_label; } @property (nonatomic, retain) CCLabelTTF *label;@end @interface GameOverScene : CCScene { GameOverLayer *_layer; } @property (nonatomic, retain) GameOverLayer *layer; @end
Then replace GameOverScene.m with the following code:
#import “GameOverScene.h”
#import “HelloWorldLayer.h”
@implementation GameOverScene
@synthesize layer = _layer;
- (id)init {
if ((self = [super init])) {
self.layer = [GameOverLayer node];
[self addChild:_layer];
}
return self;
}
- (void)dealloc {
[_layer release];
_layer = nil;
[super dealloc];
}@end
@implementation GameOverLayer
@synthesize label = _label;
-(id) init
{
if( (self=[super initWithColor:ccc4(255,255,255,255)] )) {
CGSize winSize = [[CCDirector sharedDirector] winSize];
self.label = [CCLabelTTF labelWithString:@"" fontName:@"Arial" fontSize:32];
_label.color = ccc3(0,0,0);
_label.position = ccp(winSize.width/2, winSize.height/2);
[self addChild:_label];
[self runAction:[CCSequence actions:
[CCDelayTime actionWithDuration:3],
[CCCallFunc actionWithTarget:self selector:@selector(gameOverDone)],
nil]];
}
return self;
}
- (void)gameOverDone {
[[CCDirector sharedDirector] replaceScene:[HelloWorldLayer scene]];
}
- (void)dealloc {
[_label release];
_label = nil;
[super dealloc];
}
@end
必須注意的是,這里有兩個不同的概念:界面和層。界面可以包含任意數量的層,雖然在這個示例項目中只包含一個。層只是在屏幕中間設立一個標簽,安排轉化發生3秒之后切回Hello World界面。
最后,讓我們添加某些基本的游戲邏輯。首先,讓我們先設置玩家已經摧毀的拋射物。將HelloWorldLayer.h中的HelloWorldLayer類做下列修改:
int _projectilesDestroyed;
在HelloWorldLayer.m中,添加GameOverScene類的導入:
#import “GameOverScene.h”
增加數量并在更新方法中檢查勝利的狀況,使用下列代碼:
_projectilesDestroyed++; if (_projectilesDestroyed > 30) { GameOverScene *gameOverScene = [GameOverScene node]; _projectilesDestroyed = 0; [gameOverScene.layer.label setString:@"You Win!"]; [[CCDirector sharedDirector] replaceScene:gameOverScene];
最后,讓我們設置成,某只目標觸及玩家時玩家就失敗。修改spriteMoveFinished方法,添加下列代碼到標簽中:
GameOverScene *gameOverScene = [GameOverScene node]; [gameOverScene.layer.label setString:@"You Lose :["]; [[CCDirector sharedDirector] replaceScene:gameOverScene];
繼續將項目編輯運行,現在你應該可以看到勝利或失敗界面。
后續發展
這個項目只是個最基本的示例,你可以使用Cocos2D在項目中添加更多新功能。或許你可以嘗試添加條狀圖顯示你還需要摧毀多少個目標才能獲得勝 利,也可以為怪物的死亡添加更加絢爛的動畫,或出于游戲趣味性目的添加更多音效、藝術或游戲邏輯。你完全可以盡量發揮自己的才華!