這一周接觸到Cocos2D開發,在它的官網上看到Ray Wenderlic寫的關于cocos2d開發的文章,感覺寫的挺好,翻譯了一下。
原文鏈接地址
大家可以在上面看到作者的更多內容
初次翻譯文章,望各位關照,想說的話在作者的文章里邊也有表述,就直接開始吧
游戲截圖
?
例子下載:
Cocos2DSimpleGame.zip
(776 K) 下載次數:348
????? Cocos2D是iPhone開發中一個非常有用的庫,它可以讓你在創建自己的iPhone游戲時節省很多的時間。它具有很多的功能,比如sprite(精靈)扶持,非常酷的圖形效果,動畫效果,物理庫,音頻引擎等等。
????? 我是一個Cocos2D開發的新手,盡管有很多有用的教程來介紹如何開始利用Cocos2D開發,但我不能找到一個教程是我期待的那樣,它可以創建一個簡單但功能豐富的游戲,這個游戲具有動畫,碰撞還有音頻,不需要其它更多的高級功能。我最終自己完成了一個簡單的例子,并且在我自己的經驗下寫了這篇教程以便于它對于其它的新手會有用。
???? 這篇教程將帶你從頭到尾的來了解用Cocos2D來創建一個簡單的iPhone游戲的過程。你可以一步步的按教程來,也可能跳過直接從文章的最后來下載例子工程。當然,里邊會有ninjas(忍者)
。
下載與安裝Cocos2D
??? 你可以從
the Cocos2D Google Code page
下載Cocos2D,現在的最新版本是0.99.0-final(這也是這篇教程使用的)。?
?
??? 在你下載完代碼后,你應該安裝有用的工程模板。打開Terminal window(終端窗口),找到下載的Cocos2D所在的目錄,輸入下面的命令:./install_template.sh。
如果你的XCode不是安裝在默認的目錄下面(比如說你的機器上面可能安裝了多個版本的SDK),你可以在安裝腳本里邊手工的添加一個參數。(譯者注,我沒試過,試過的大大可以給指明一下,17樓寫明了, 謝謝17樓的提示)
Hello, Cocos2D
????? 讓我們開始來用剛剛安裝的Cocos2D工程模板來建立并運行一個簡單的Hello World 工程。啟動XCode ,選中 cocos2d-0.99.0 Applications模板創建一個新的Cocos2D工程,給工程命名為“Cocos2DSimpleGame
?
”.
?
?
繼續編譯并運行該工程。如果一切正常,你將看到下圖:
???? Cocos2D被組織到”scenes”(場景)的概念中,有點類似于游戲中的”levels”(等級)或是”screens”(屏幕).比如你需要有一個場景來為游戲初始化菜單,一個場景為游戲的主要動作,一個場景為游戲結束。?
?
?
???? 在場景里邊,你要有許多的圖層(就像Photoshop里邊的一樣),圖層可能包含多個(nodes)結點,比如sprites(精靈),labels(標簽),menus(菜單)及其它。當然結點也包含其它的結點(比如,一個精靈可以有一個子精靈)。
???? 在這個例子工程中,你可以看到有一個場景-HelloWorldScene,我們也將在它里邊開始實現我們的游戲。繼續打開源文件,你會看到在init這個方法中,它加入了一個label來在場景中顯示”Hello World”。我們將要放入一個精靈來代替它。
?
添加一個精靈
???? 在添加精靈之前,我們需要即將用到的圖片。你可以自己創建,或者是用Ray Wenderlich妻子為這個工程專門繪制的圖片:
a player Image
a Projectile Image
a Target Image
???? 當你得到這些圖片后,把它們直接拖到XCode里邊的resources文件夾里邊去,一定要
選中"Copy items into destination group’s folder (if needed)”。
?
??? 既然我們已經有了自己的圖片,我們就要找出應該在哪來放置玩家。請注意,在
Cocos2D里邊屏幕的左下角是坐標原點(0,0),x和y值向右上角遞增
。因為工程是在橫向模式,這意味著右上角的坐標值是(480, 320)。
????
還需要注意的是在默認狀態下當我們為一個物體設置position屬性時,position屬性是和我們添加的精靈的中心點關聯起來的。
因此如果我們想把玩家精靈放置在屏幕水平方向的左邊,垂直方向的中間:
position的X坐標,要設置成[player sprite's width]/2.
Position的Y坐標,要設置成[window height]/2
下面這張圖可以幫助我們更好的理解
???? 讓我們試一下吧!打開Classes文件夾選中HelloWorldScene.m,用下面的代碼來代替init方法:
-(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中,把一個圖層的的背景顏色更改成為一個自定義顏色的簡單方法是利用
CCColoredLayer
這個類。來嘗試一下吧。選中HelloWorldScene.h并且改變HelloWorld接口省明像下面的那樣:
@interface HelloWorld : CCColorLayer
?
?? 然后選中HelloWorldScene.m并對init方法進行一個細微的修改來把背景色改為白色。
?
if( (self=[super initWithColor:ccc4(255,255,255,255)] ))
?
????? 繼續編譯并運行工程,你會看到你的玩家精靈在白色的背景上。噢,我們的忍者已經準備表演了。????
移動目標
???? 下面我們需要在場景中添加一些目標讓忍者去打擊。為了讓事情變的更有趣一些,我們要讓這些目標移動起來-要不然沒什么挑戰性。我們在稍稍偏屏幕右邊的地方創建一些目標,并為它們建立動作來讓它們向左移動。
在init方法之前添加下面的方法:
-(void)addTarget { CCSprite *target = [CCSprite spriteWithFile:@"Target.png"rect:CGRectMak(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]; }
??
????? 該函數的目的是從場景中移除精靈,一旦該精靈離開屏幕。這一點很重要,這樣我們不會隨著時間的推移,有許許多多的無用精靈在場景之外而內存泄漏。請注意,還有其他(更好)的方式來解決這個問題諸如具有可重復使用Sprite的數組,但這個初級教程,我們正在采取簡單的方法。
????? 最后一件事情在我們運行程序前。我們需要實際調用的方法來創建目標!為了讓事情更有趣點,我們讓目標隨著時間的推移持續大量的出現。我們可以在Cocos2D中通過安排一個回調函數的定期調用來完成這個任務。每1秒執行一次。因此,
在init函數返回之前調用下面的函數調用。
?
[self schedule:@selector(gameLogic:) interval:1.0];
?
現在像下面這樣簡單的實現這個回調函數:
?
-(void)gameLogic:(ccTime)dt { [self addTarget]; }
??
?
?
????? 就是這樣!所以,現在,如果你編譯并運行該項目,現在你應該看到目標愉快地在屏幕上移動:
射擊子彈
????? 在這時,忍者希望有一些動作-讓們添加射擊吧!我們有很多的方法來實現射擊,但在這個游戲中我們要在用戶點擊屏幕時來進行射擊,從玩家射出的子彈將按照點擊的方向前進。
????? 我想用一個CCMoveTo動作去保持事情還在初級層面上,但為了實現這個,我們必須做一些數學。這是因為CCMoveTo要求我們必須為子彈目的地,但我們不能只使用觸摸點,因為接觸點僅僅代表相對于玩家的射擊方向。事實上,我們希望保持子彈通過觸摸點,直到移出屏幕。
用一張圖來解釋這個事情:
?
因此,大家可以看到,我們利用起點到觸摸點的X和Y方向的偏移創造了個小三角形。我們只需要以同樣的比例大三角形 - 我們知道我們需要一個離開屏幕的結束點。
?
好了,上代碼。首先,
我們必須使我們的層可以支持觸摸
。添加下面一行到您的init方法:
?
?
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方向上離開屏幕-即使首先在Y方向上已經離開了屏幕!有不同的方法來解決這個,包括檢查離開屏幕的最小長度,讓游戲的邏輯回調核查離開屏幕的子彈和消除回調,而不是使用回調方法,等等。但這個初級教程,我們將保持原樣。
????? 我們最需要做的就是確定為運動時間。我們希望,子彈會于一個恒定的速率按照射擊方向前進,所以我們再次做一些數學。我們可以找出利用勾股定理來算出移動的距離。記得在幾何學里邊,這是規則,指出了一個直角三角形的斜邊長度等于兩直角邊的平方的和的開方。
???? 一旦我們有了距離,我們只是除以速度,以獲得所需的時間。這是因為速度=距離/時間,或換句話說 時間=距離/速度。
??? 剩下的事情就是設置動作,就想給目標設置動作一樣。編譯并運行,現在你的忍者可以向前來的一大群目標開火了
!
碰撞檢測
???? 所以現在我們已經讓shurikens滿天飛了-但我們的忍者真正想要做的是放倒一些目標。因此,讓我們加入一些代碼,以檢測當我們的子彈和目標的相交。
???? 要做到這一點,我們首先要在目前的場景中更好的跟蹤目標和子彈。把以下的代碼添加到你的HelloWorldScene類聲明中:
?
NSMutableArray *_targets; NSMutableArray *_projectiles;
?
在
init方法中初始化這兩個數組
:
?
?
_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]; }
?
?
?
????? 編譯并運行該項目以確保一切正常。在這個時候應該沒有什么明顯的不同,但現在我們有標記,我們要實現碰撞檢測。
現在在HelloWorldScene類中添加下面的方法:
- (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數組中,因為你不能在一個正在迭代的數組中刪除一個對象 。同樣,有更多的最佳方法來實現這種事情,但我采用了這個簡單的方法
????? 在你準備要運行前你只需要做一件事-通過添加下面的代碼到init方法中去安排上面的方法盡可能多的運行
???
[self schedule:@selector(update:)];
?
讓它編譯并運行,現在當你的子彈和目標碰撞時它們就會消失!
最后的潤色
????? 我們非常接近擁有一個可行的(但非常簡單)的游戲了。我們只需要添加一些聲音效果和音樂(因為什么類型的游戲沒有音樂的!)和一些簡單的游戲邏輯。
???? 如果您一直關注我的blog series on audio programming for the iPhone
blog series on audio programming for the iPhone
,關于iPhone的一系列音頻編程博客,你會非常高興地知道,對于Cocos2D開發者來說,在游戲中實現基本的聲音效果是多么的簡單。
?
第一步:
????? ?拖動一些背景音樂和一個射擊聲音效果到你的resources文件夾中。隨意使用
cool background music I made
background-music-aac.caf.zip
(252 K) 下載次數:52
或是 awesome pew-pew sound effect
pew-pew-lei.caf.zip
(40 K) 下載次數:42
,或者制作你自己的。
然后:
?????? 添加下面的代碼到你的HelloWorldScene.m文件的頭部:
#import "SimpleAudioEngine.h"
?
???? 在你的init方法,像下面的代碼所示啟動背景音樂:
?
[[SimpleAudioEngine sharedEngine]playBackgroundMusic:@"background-music-aac.caf"];
?
0.99-final update:(關于0.99-final更新):
??????
看起來在0.99-final版本中有一個小小的bug,背景音樂只能播放一次(而它本應該循環)-要么是它的錯要么就是我弄錯了。對于一個變通方法,請參閱本文結尾的意見。
關于??0.99-final版本中有一個小小的bug,
在 CDAudioManager.m 的第72行加入以下代碼,??可以解決背景音樂只能播放一次(而它本應該循環)
? - ( void ) setNumberOfLoops : ( NSInteger ) theNumberOfLoops { numberOfLoops = theNumberOfLoops; audioSourcePlayer.numberOfLoops = theNumberOfLoops; } ? |
在你的ccTouchesEnded方法中播放下面的聲音效果:
[[SimpleAudioEngine sharedEngine] playEffect:@"pew-pew-lei.caf"];
?
????? 現在,讓我們創建一個新的場景,將作為我們的“你贏了”,或“你輸”的指示。點擊Classes文件夾,進入File\New File,并選擇Objective-C class,并確定了NSObject類被選中。單擊Next,然后輸入GameOverScene作為文件名,并確保“Also create GameOverScene.h”被選中。
然后用下面的代碼來代替GameOverScene.h中的內容:
#import "cocos2d.h" @interface GameOverLayer : CCColorLayer { CCLabel *_label; } @property (nonatomic, retain) CCLabel *label; @end @interface GameOverScene : CCScene { GameOverLayer *_layer; } @property (nonatomic, retain) GameOverLayer *layer; @end
?
再用下面的代碼來代替GameOverScene.m中的內容
#import "GameOverScene.h" #import "HelloWorldScene.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 = [CCLabel 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:[HelloWorld scene]]; } - (void)dealloc { [_label release]; _label = nil; [super dealloc]; } @end
?
????? 請注意,這里有兩個不同的對象:
一個場景(scene)和一個圖層(layer)。
場景可以包含多個圖層,盡管在這個例子是它只有一個。圖層里邊只在屏幕中心放了一個標簽,并安排了一個3秒中的過渡,然后返回到HelloWorld場景中。
?
????? 最后,讓我們添加一些非常基本的游戲邏輯。首先,讓我們跟蹤玩家破壞的目標。添加一個成員變量到您的HelloWorldScene.h中 HelloWorld類如下:
?
int _projectilesDestroyed; ?
在HelloWorldScene.m中,添加GameOverScene類的聲明:
?
#import "GameOverScene.h"
??
???? 在update方法中removeChile:target:后面的targetsToDelete循環中增加計數并檢查獲勝條件
_projectilesDestroyed++; if (_projectilesDestroyed > 30) { GameOverScene *gameOverScene = [GameOverScene node]; [gameOverScene.layer.label setString:@"You Win!"]; [[CCDirector sharedDirector] replaceScene:gameOverScene]; }
?
?
???? 最后我們這樣來規定,即使只有一個目標過去了,你就輸了。修改spriteMoveFinished方法,在removeChild:sprite:方法的后面的tag == 1條件里邊添加下面的代碼:
?
GameOverScene *gameOverScene = [GameOverScene node]; [gameOverScene.layer.label setString:@"You Lose :["]; [[CCDirector sharedDirector] replaceScene:gameOverScene];
?
繼續編譯并運行該項目,這樣你有了羸和輸的判斷條件并會在適當的時候看到游戲結束的場景。
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
