用Cocos2D開發的iPhone遊戲的教程
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),你可以在安裝腳本里邊手工的添加一個參數。(譯者注,我沒試過, 試過的大大可以給指明一下) 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"} 在這裏我以一種詳細的方式來闡明事情以便讓 事情更容易理解。第一部分我們應該理解目前已經討論了:我們做一些簡單的計算,以確定我們要創建對象,設置對象的位置,並把它以添加玩家精靈的相同方式添 加到場景中去。 這裏邊的新元素是添加動作。Cocos2D提供了很多非常方便內置的行動可以用來製作動畫的行動,如移動,跳躍的行動, 褪色的行動,動畫動作及更多。在這裏我們爲目標使用了三項動作。 •CCMoveTo:我們使用CCMoveTo動作來指導物體屏幕左邊。請注 意,我們可以指定運動應採取的持續時間,在這裏,我們採用2-4秒的隨機速度。 •CCCallFuncN: 該CCCallFuncN函數允許我們指定一個回調到我們的對象出現時,執行操作。我們正在指定一個回調稱爲"spriteMoveFinished”我 們還沒有寫呢 - 更多如下 •CCSequence: 該CCSequence動作讓我們創建一系列的動作,一次一個。這樣,我們可以先執行CCMoveTo動作,一旦完成執行CCCallFuncN動作。 下 面,添加前面我們已經在CCCallFuncN動作中已經提過的回調函數。你可以在addTarget函數前面添加: -(void)spriteMoveFinished:(id)sender { CCSprite *sprite = (CCSprite *)sender;} 該函數的目的是從場景中移除精靈,一旦該精靈離開屏幕。這一點很重要,這樣我們不會隨着時間的推移,有許許多多 的無用精靈在場景之外而內存泄漏。請注意,還有其他(更好)的方式來解決這個問題諸如具有可重複使用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 } 在第一部分,我們選擇一個觸摸點來使用,先得到在當前視圖中的位置,然後調用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]; 還 要修改你的ccTouchesEndec方法,把新子彈添加到子彈數組中給它設置一個標記以便在以後使用: projectile.tag = 2; [_projectiles addObject:projectile]; 最後,修改你的 spriteMoveFinished方法,根據標記的不同在適當的數組中移除精靈: if (sprite.tag == 1) { // target [_targets removeObject:sprite];} 編譯並運行該項目 以確保一切正常。在這個時候應該沒有什麼明顯的不同,但現在我們有標記,我們要實現碰撞檢測。 現在在HelloWorldScene類中添加下 面的方法: - (void)update:(ccTime)dt { NSMutableArray *projectilesToDelete = [[NSMutableArray alloc] init]; } 以上應該很清楚了。我們只是通過子彈和目標數組,按照它們的邊界框創建相應的矩形,並使用 CGRectIntersectsRect方法來檢查交叉。如果發現有,我們從場景和數組中把它們移除。請注意,我們是把這些對象添加到一個 toDelete數組中,因爲你不能在一個正在迭代的數組中刪除一個對象。同樣,有更多的最佳方法來實現這種事情,但我採用了這個簡單的方法 在 你準備要運行前你只需要做一件事-通過添加下面的代碼到init方法中去安排上面的方法儘可能多的運行 [self schedule:@selector(update:)]; 讓它編譯並運行,現在當你的子彈和目標碰撞時它們就會消失! 最後的潤色 我 們非常接近擁有一個可行的(但非常簡單)的遊戲了。我們只需要添加一些聲音效果和音樂(因爲什麼類型的遊戲沒有音樂的!)和一些簡單的遊戲邏輯。 如 果您一直關注我的blog series on audio programming for the iPhoneblog series on audio programming for the iPhone,關於iPhone的一系列音頻編程博客,你會非常高興地知道,對於Cocos2D開發者來說,在遊戲中實現基本的聲音效果是多麼的 簡單。 第一步,拖動一些背景音樂和一個射擊聲音效果到你的resources文件夾中。 然後,添加下面的代碼到你的HelloWorldScene.m文件的頭部: #import "SimpleAudioEngine.h" 在你的init方法,像下面的代碼所示啓動 背景音樂: [[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"background-music-aac.caf"]; 0.99-final update:(關於0.99-final更新):看起來在0.99-final版本中有一個小小的bug,背景音樂只能播放一次(而它本應該循環)-要 麼是它的錯要麼就是我弄錯了。對於一個變通方法,請參閱本文結尾的意見。 在你的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])) {} - (void)dealloc { [_layer release];} @end @implementation GameOverLayer @synthesize label = _label; -(id) init { if( (self=[super initWithColor:ccc4(255,255,255,255)] )) {} - (void)gameOverDone { [[CCDirector sharedDirector] replaceScene:[HelloWorld scene]]; } - (void)dealloc { [_label release];} @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];} 最 後我們這樣來規定,即使只有一個目標過去了,你就輸了。修改spriteMoveFinished方法,在removeChild:sprite:方法的 後面的tag == 1條件裏邊添加下面的代碼: GameOverScene *gameOverScene = [GameOverScene node]; [gameOverScene.layer.label setString:@"You Lose :["]; [[CCDirector sharedDirector] replaceScene:gameOverScene]; 繼續編譯並運行該項目,這樣你有了羸和輸的判斷條件並會在適當的時候看到遊戲結束的場景。 |