原文地址:http://www.raywenderlich.com/25736/how-to-make-a-simple-iphone-game-with-cocos2d-2-x-tutorial
Ray要說:經過一周的投票表決,大家希望我將此套Cocos2D經典入門系列教程從Cocos2D 1.X升級至Cocos2D 2.X,大家的願望就是對我的命令!:]
現在,此套系列教程已經完全升級至Cocos2D 2.X和Xcode 4.5,同時作了大量的改進,例如Retina顯示屏以及對iPhone 5的4英寸屏幕支持。點擊鏈接可以訪問以前Cocos2D 1.X版本的教程,如果您需要的話!
Cocos2D是一套用於iPhone開發的強大的庫,能為您建立iPhone游戲節省大量的時間。Cocos2D包含精靈支持、超酷的圖形效果、動畫、物理引擎庫、聲音引擎及其他更多內容。
回想當初我剛開始學習Cocos2D的時候,只有極少的幾篇關於Cocos2D入門有用的教程,但卻未能找到任何與我期望類似的教程——做一款非常簡單但又包含常見功能的游戲,只需要包含動畫、碰撞檢測以及聲音,而不需要使用太多高級的功能。
在結束了自己的第一款簡單游戲的制作之后,我就決定根據自己的經驗寫一套系列教程,這可能會對其他新人有所幫助。
本系列教程將會從頭至尾,一步一步地教您如何使用Cocos2D制作一款簡單iPhone游戲。您可以按照系列教程一步步來,也可以直接跳到文章的最后下載示例程序。沒錯!游戲里面有忍者。
(跳轉至系列教程的第二部分或第三部分。)
譯者注:第二部分和第三部分的教程,待翻譯完成后再增加鏈接。
下載並安裝Cocos2D
您可以從Cocos2D-iPhone官方網站直接下載Cocos2D。
在官方網站上,您會注意到有幾種版本可供下載: Cocos2D 1.X版本或是Cocos2D 2.x版本,穩定版本或是不穩定版本。下面對這幾種版本做一個簡單的介紹。
Cocos2D 1.X和Cocos2D 2.X
這兩個版本之間主要的區別在於底層使用的引擎不同,Cocos2D 1.X使用的是OpenGL ES 1.X,而Cocos2D 2.X使用的則是OpenGL ES 2.X。除非您此前有過OpenGL相關的開發經驗,否則對您而言這個區別可能沒有多大意義。:]
現在,您只需知道以下幾點即可:
- Cocos2D 1.X已經出現了很長一段時間。因此有大量的代碼僅適用於Cocos2D 1.X。不過這種情況正在改變,越來越多的人正在轉向Cocos2D 2.X!
- Cocos2D 2.X可以使用着色器。着色器在OpenGL ES 2.X中是一個很神奇的東西,允許您創建一些很酷的效果,而這些效果無法用OpenGL ES 1.X實現。
雖然這兩個版本的Cocos2D都能很好地工作,並且基於這兩個版本均開發有非常多優秀的游戲。不過在本教程中,您將使用的是最新、最偉大的Cocos2D 2.X。:]
Cocos2D的穩定版本和不穩定版本
在官方網站上,您還將注意到有“stable(穩定的)”和“unstable(穩定的)”兩個版本。
我注意到一些有用的新功能從不穩定版本過渡至穩定版本通常會需要很長一段時間。因此我更加傾向於使用不穩定版本,以便能夠使用到所有新的功能。雖然它被稱為是“不穩定”的版本,不過不用擔心,它通常還是相當不錯地。:]
因此,在本教程中,您將使用“不穩定”版本。
結論
對於本教程,請下載Cocos2D 2.X的最新不穩定的版本。
下載之后,需要安裝有用的項目模板。打開終端窗口進入Cocos2D的下載目錄,然后輸入如下命令:
./install-templates.sh -f -u
應該能夠看到“Installing cocos2d templates”和一堆提示信息。恭喜!您已經准備好開始使用Cocos2D了!
注釋:如果您曾經安裝過Cocos2D 1.X,在安裝Cocos2D 2.X時可能會擔心覆蓋掉當前Cocos2D 1.X的模板,不要驚慌!:] Cocos2D 2.X會將其模板安裝在一個單獨的文件夾,因此您可以同時安裝Cocos2D兩個版本的模板。
Hello, Cocos2D!
讓我們使用剛剛安裝的Cocos2D模板從最簡單的Hello World項目開始吧!啟動Xcode,選擇iOS\cocos2d v2.X\cocos2d iOS template新建一個Cocos2D項目,並將項目命名為Cocos2DSimpleGame。
來吧!生成並運行這個由模板建立的項目,如果一切正常,將能看到如下圖所示的運行效果:
Cocos2D是按照場景(scene)概念組織的,對游戲而言,場景類似於“關卡”或者“屏幕”。例如,您可能會有一個場景用於游戲的初始菜單,另一個場景用於游戲操作主界面,還有一個場景用於游戲結束。
在一個場景中,可以包含許多圖層(layer),這與Photoshop的圖層概念有些類似。同時圖層可以包含多個節點(node),諸如:精靈、標簽、菜單等。而一個節點也可以包含其他節點,例如一個精靈之中可以包含一個子精靈。
如果看一下示例項目,可以看到IntroLayer和HelloWorldLayer兩個圖層,而且每個圖層均包含在一個場景之中。
在本教程中您將使用HelloWorldLayer圖層。打開HelloWorldLayer.m並查看其init方法。可以看到其中添加了一個“Hello World”標簽和一個菜單。下一步將在圖層中放入一個精靈替換現有內容。
添加精靈
在添加精靈之前,需要有一些可以使用的圖像。您可以點擊下載教程資源,其中有一個是我親愛的妻子為這個教程項目特意制作的。
下載資源之后,解壓縮文件並將其中所有內容拖拽到Xcode中的Resources文件夾之上,在彈出的對話框中確認勾選了“Copy items into destination group’s folder (if needed)”。
現在您已經擁有了自己的圖像,下面需要計算一下具體放置忍者的位置。請注意,在Cocos2D中屏幕的左下角的坐標是(0,0),x值和y值隨着向右上方的移動增加。由於此項目是橫版模式,這意味着如果在3.5英寸的屏幕上運行,右上角的坐標會是(480, 320),而如果在4英寸的屏幕上運行,右上角的坐標則會是(568, 320)。
注釋:如果您已經有過一段時間的iOS開發經驗,此時可能會想“稍等,我認為4英寸屏幕應該是1136×640像素的,而不是568×320像素的!”
就像素而言,您的想法是對的,不過Cocos2D使用的單位是“點”而不是“像素”。在Retina顯示屏的設備上,1點=2像素,因此1136×640像素=568×320點。使用點作為單位是非常方便的,因為如此一來游戲在Retina顯示屏和非Retina顯示屏上可以使用相同的坐標!
另外請注意,默認情況下在設置一個對象的位置時,該位置是與所添加精靈的中心點相對的。因此,如果您希望忍者精靈與屏幕的左側邊緣水平方向對齊,垂直方向居中,則需要:
- 將精靈位置的x坐標設置為[player sprite's width]/2。
- 將精靈位置的y坐標設置為[window height]/2。
如下所示的圖片,可以更好地說明這一點:
現在開始動手吧!打開HelloWorldLayer.m,使用如下代碼替換init方法:
- (id) init { if ((self = [super init])) { CGSize winSize = [CCDirector sharedDirector].winSize; CCSprite *player = [CCSprite spriteWithFile:@"player.png"]; 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並對init方法稍加修改,將其背景顏色設置為白色,如下所示:
if ((self = [super initWithColor:ccc4(255,255,255,255)])) {
再次編譯並運行,您將看到精靈出現在白色背景之上了。哈哈,您的忍者看起來已經准備行動了哦!
注釋:您可能已經注意到,在資源包里實際上包含有兩個版本忍者的圖像,一個是player.png(27×40像素),另一個是player-hd.png(兩倍大小54×80像素)。
這顯示了Cocos2D一個很酷的特性——當游戲在Retina顯示屏上運行時,它能夠智能地選擇使用高分辨率的圖形!而您所需要做的只是為兩倍大小的圖片文件添加一個-hd后綴即可,這與UIKit支持的@2x行為非常類似。
移動妖怪
接下來,需要在場景中添加一些妖怪與忍者進行戰斗。為了讓游戲變得更加有趣,可以讓妖怪移動起來,否則游戲就太沒有挑戰了!因此,讓我們在屏幕右邊的外側創建一些妖怪,然后為它們設置一個動作(action)讓它們向左側移動。
在init方法的前面添加如下方法:
- (void) addMonster { CCSprite * monster = [CCSprite spriteWithFile:@"monster.png"]; // Determine where to spawn the monster along the Y axis CGSize winSize = [CCDirector sharedDirector].winSize; int minY = monster.contentSize.height / 2; int maxY = winSize.height - monster.contentSize.height/2; int rangeY = maxY - minY; int actualY = (arc4random() % rangeY) + minY; // Create the monster slightly off-screen along the right edge, // and along a random position along the Y axis as calculated above monster.position = ccp(winSize.width + monster.contentSize.width/2, actualY); [self addChild:monster]; // Determine speed of the monster int minDuration = 2.0; int maxDuration = 4.0; int rangeDuration = maxDuration - minDuration; int actualDuration = (arc4random() % rangeDuration) + minDuration; // Create the actions CCMoveTo * actionMove = [CCMoveTo actionWithDuration:actualDuration position:ccp(-monster.contentSize.width/2, actualY)]; CCCallBlockN * actionMoveDone = [CCCallBlockN actionWithBlock:^(CCNode *node) { [node removeFromParentAndCleanup:YES]; }]; [monster runAction:[CCSequence actions:actionMove, actionMoveDone, nil]]; }
下面我將詳細解釋這段代碼以便於大家的理解。第一部分代碼的意義此前我們已經討論過:做一些簡單的計算確定創建對象的位置,使用計算結果設置對象的position,然后使用與忍者精靈同樣的方式將其添加至場景。
代碼中新出現的元素是添加動作(action)。Cocos2D提供了很多非常方便的內置動作可以用於實現精靈的動畫,例如移動動作、跳躍動作、淡入淡出動作、序列動畫動作等。代碼中在妖怪上使用了如下三種動作:
- CCMoveTo:使用CCMoveTo動作指示對象從屏幕右邊外側向左移動。請注意,在此可以指定移動過程的持續時間,代碼中使用隨機2~4秒的時間讓妖怪的速度有所區別。
- CCCallBlockN:CCCallBlockN函數允許我們指定動作被執行時要運行的回調代碼塊。在本游戲中,您設置將要運行的操作是——當妖怪從屏幕左側移出后將妖怪從圖層中刪除。這一點非常重要,這樣可以避免一段時間之后由於大量無用的精靈站在屏幕之外造成內存泄漏。請注意,還有其他更好的方式來解決這一問題,例如使用精靈的可重用數組,不過本教程是面向初學者的,還是先使用簡單的方式。
- CCSequence:CCSequence動作允許我們將一系列動作串聯起來,按照順序一次一個地執行。以這種方式,我們可以首先執行CCMoveTo動作,該動作一旦完成就會執行CCCallBlockN動作。
在繼續下一內容之前,還有最后一件事情需要去做。實際調用該方法去創建妖怪!另外,為了增加游戲趣味性,我們讓妖怪持續不斷地出來。在Cocos2D中可以通過schedule定期調用一個回調函數實現這一點。每隔一秒調用一次回調函數。因此在init方法中的返回語句之前添加如下代碼:
[self schedule:@selector(gameLogic:) interval:1.0];
然后簡單地實現回調函數,如下所示:
-(void)gameLogic:(ccTime)dt { [self addMonster]; }
搞定!生成並運行項目,現在您應該能夠看到一些妖怪快樂地移動穿梭於屏幕之上了,如下圖所示:
發射暗器
進行到現在,忍者正在眼巴巴地盼望着能夠有一些動作,因此讓他發射暗器准備戰斗吧!有許多種方式可以實現發射的動作,不過本游戲所采用的方式是當玩家點按屏幕時開始發射,從忍者所在位置向手指點按方向發射一枚暗器。
我決定使用CCMoveTo動作來實現暗器的發射,從而將教程的內容保持在初學者的水平,但是在使用之前需要做一些小小的數學題。這是因為CCMoveTo要求我們給定一個暗器的目標,而在此我們不能簡單地使用觸摸點,因為觸摸點僅能代表相對於忍者暗器的飛行方向。而實際上您需要讓暗器朝目標點方向移動並最終飛出屏幕。
下面這張圖說明了這一問題:
從圖中可以看到,原點和觸摸點之間的x和y偏移量已經構成一個小的三角形。現在只需要做一個等比放大的三角形,同時讓三角形的一個端點剛好位於屏幕的外側。
Ok,現在開始編寫代碼。首先需要讓圖層能夠接受觸摸事件。在init方法中添加如下代碼:
[self setIsTouchEnabled:YES];
譯者注:在翻譯本文時,譯者所使用的cocos2d-iphone v2.1-rc1版本中,應該使用如下代碼:
[self setTouchEnabled:YES];
由於我們已經允許圖層能夠接受觸摸事件,因此已經能夠接收到觸摸事件的回調了。現在讓我們來實現ccTouchesEnded方法,該方法在用戶完成觸摸后被調用,代碼如下:
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { // Choose one of the touches to work with UITouch *touch = [touches anyObject]; CGPoint location = [self convertTouchToNodeSpace:touch]; // Set up initial location of projectile CGSize winSize = [[CCDirector sharedDirector] winSize]; CCSprite *projectile = [CCSprite spriteWithFile:@"projectile.png"]; projectile.position = ccp(20, winSize.height/2); // Determine offset of location to projectile CGPoint offset = ccpSub(location, projectile.position); // Bail out if you are shooting down or backwards if (offset.x <= 0) return; // Ok to add now - we've double checked position [self addChild:projectile]; int realX = winSize.width + (projectile.contentSize.width/2); float ratio = (float) offset.y / (float) offset.x; int realY = (realX * ratio) + projectile.position.y; CGPoint realDest = ccp(realX, realY); // Determine the length of how far you'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], [CCCallBlockN actionWithBlock:^(CCNode *node) { [node removeFromParentAndCleanup:YES]; }], nil]]; }
在代碼的第一部分,從touches集合中選擇一個觸摸來處理,然后使用convertTouchToNodeSpace方法將觸摸的坐標從視圖坐標轉換為當前圖層的坐標。
接下來,加載暗器精靈並設置其初始位置。然后以忍者和觸摸之間的向量為參照,根據之前描述的算法,確定暗器最終應該移動到的目標位置。
請注意,此處所使用的算法並不是非常理想。只是簡單地要求暗器一直移動到屏幕外的X位置,即便是暗器已經從Y軸方向飛出了屏幕!有很多種方式可以解決這一問題,譬如檢測飛出屏幕的最短距離,或者在游戲邏輯回調中檢查飛出屏幕的暗器並負責刪除它們,而不是使用回調方法等。但是就入門教程而言,我們還是盡量保持內容的簡單。
最后一件事情是確定移動的持續時間。您希望無論發射方向如何,暗器都以恆定的速度飛行,因此,您還需要再做一些小小的數學題。使用勾股定理,可以計算得出暗器需要的移動距離。請記住在平面幾何中有這樣一條規則:直角三角形斜邊的長度等於兩條直角邊長度的平方和的平方根。
得到移動距離之后,直接除以速度就可以得出暗器飛行所需的時間。這是因為:速度=距離/時間,換言之:時間=距離/速度。
剩下的事情就是設置動作,這與之前對妖怪所作的類似。生成並運行程序,現在您的忍者應該能夠向來犯的妖怪開火了!
碰撞檢測
現在您已經可以看到暗器到處亂飛了,但是您的忍者真正需要做的是放倒一些妖怪。因此,讓我們添加一些代碼檢測暗器何時擊中目標。
在Cocos2D中有很多種方式解決這一問題,包括使用Cocos2D內置的開源物理引擎:Box2D或Chipmunk。然而,為了保持內容的簡單,您將自己去實現一個簡單的碰撞檢測。
要做到這一點,首先需要能夠很好地跟蹤當前場景中的妖怪和暗器。在HelloWorldLayer類的聲明中添加如下代碼:
NSMutableArray * _monsters;
NSMutableArray * _projectiles;
然后在init方法中初始化這些數組:
_monsters = [[NSMutableArray alloc] init];
_projectiles = [[NSMutableArray alloc] init];
另外,當您在思考這些數組時,請不要忘記清理內存,在寫這篇教程時,Cocos2D 2.X模板默認還不支持ARC。在dealloc中輸入如下代碼:
[_monsters release]; _monsters = nil; [_projectiles release]; _projectiles = nil;
注釋:盡管Cocos2D 2.X模板默認還不支持ARC ,不過這個非常容易做到。要學習如何做到的具體細節,可以查閱此教程。
譯者注:此前譯者也曾整理過一篇通過靜態庫的方式讓Cocos2D 2.X支持ARC,有興趣的朋友可以點擊此處查看。
現在,修改addMonster方法,將新建的monster添加至monsters數組,並設置其tag以供后續使用:
monster.tag = 1; [_monsters addObject:monster];
同時修改ccTouchesEnded方法,將新建的projectile添加至projectiles數組,並設置其tag以供后續使用:
projectile.tag = 2; [_projectiles addObject:projectile];
最后,修改兩個CCCallBlockN代碼塊從相應的數組中刪除精靈:
// CCCallBlockN in addMonster [_monsters removeObject:node]; // CCCallBlockN in ccTouchesEnded [_projectiles removeObject:node];
生成並運行項目,確保一切仍然能夠正常工作。到目前為止,運行程序應該沒有明顯的差別,不過接下來您就需要來實現此前提及的碰撞檢測了。
現在,添加如下新的方法:
- (void)update:(ccTime)dt { NSMutableArray *projectilesToDelete = [[NSMutableArray alloc] init]; for (CCSprite *projectile in _projectiles) { NSMutableArray *monstersToDelete = [[NSMutableArray alloc] init]; for (CCSprite *monster in _monsters) { if (CGRectIntersectsRect(projectile.boundingBox, monster.boundingBox)) { [monstersToDelete addObject:monster]; } } for (CCSprite *monster in monstersToDelete) { [_monsters removeObject:monster]; [self removeChild:monster cleanup:YES]; } if (monstersToDelete.count > 0) { [projectilesToDelete addObject:projectile]; } [monstersToDelete release]; } for (CCSprite *projectile in projectilesToDelete) { [_projectiles removeObject:projectile]; [self removeChild:projectile cleanup:YES]; } [projectilesToDelete release]; }
以上代碼應該非常清楚。只是遍歷projectiles和monsters數組,創建其中對象邊框對應的矩形,然后使用CGRectIntersectsRect檢測矩形是否相交。如果發現碰撞,則從場景及數組中刪除。
請注意,您必須把要刪除的對象先添加至一個“toDelete”數組,因為在遍歷數組的同時不能從中刪除對象。同樣,還有很多更優化的方式來實現上述操作,不過您現在所需要的就是簡單的方式。
現在還差最后一件事情,就一切准備就緒了!在init方法中添加如下代碼,盡可能頻繁地調度剛剛添加的方法運行,以便及時檢測到碰撞的發生:
[self schedule:@selector(update:)];
生成並運行程序,現在當暗器擊中目標時,它們都會消失了!
收尾
現在,距離一個切實可行的游戲已經非常接近了,盡管有些簡單。您只需要再添加一些音效和音樂(哪個游戲能沒有聲音呢!)以及一些簡單的游戲邏輯就大功告成了!
如果以前閱讀過iPhone開發之音頻系列博客,當您知道使用Cocos2D在游戲中播放音效及背景音樂是如何的簡單,您一定會非常高興。
通過前面下載教程資源,您的項目中現在應該已經有了一個酷酷的背景音樂以及另外一個音效文件。您現在要做的就是播放它們!
要做到這一點,首先在HelloWorldLayer.m文件的頂部添加如下引入:
#import "SimpleAudioEngine.h"
在init方法中,使用如下代碼啟動背景音樂:
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"background-music-aac.caf"];
在ccTouchesEnded方法中,使用如下代碼播放音效:
[[SimpleAudioEngine sharedEngine] playEffect:@"pew-pew-lei.caf"];
現在,讓我們新建一個場景和圖層來負責“You Win”或“You Lose”的提示。使用iOS\cocos2d v2.x\CCNode class模板新建一個文件,將Subclass of指定為CCLayerColor,然后單擊Next按鈕。將其命名為GameOverLayer,然后單擊Create按鈕。
然后使用如下代碼替換GameOverLayer.h:
#import "cocos2d.h" @interface GameOverLayer : CCLayerColor +(CCScene *) sceneWithWon:(BOOL)won; - (id)initWithWon:(BOOL)won; @end
使用如下代碼替換GameOverLayer.m:
#import "GameOverLayer.h" #import "HelloWorldLayer.h" @implementation GameOverLayer +(CCScene *) sceneWithWon:(BOOL)won { CCScene *scene = [CCScene node]; GameOverLayer *layer = [[[GameOverLayer alloc] initWithWon:won] autorelease]; [scene addChild: layer]; return scene; } - (id)initWithWon:(BOOL)won { if ((self = [super initWithColor:ccc4(255, 255, 255, 255)])) { NSString * message; if (won) { message = @"You Won!"; } else { message = @"You Lose :["; } CGSize winSize = [[CCDirector sharedDirector] winSize]; CCLabelTTF * label = [CCLabelTTF labelWithString:message 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], [CCCallBlockN actionWithBlock:^(CCNode *node) { [[CCDirector sharedDirector] replaceScene:[HelloWorldLayer scene]]; }], nil]]; } return self; } @end
請注意,此處有兩種不同的對象:場景和圖層。場景可以包含任意多個圖層,不過在此示例中僅包含了一個圖層。該圖層在屏幕的中間放置了一個標簽,並設置了一個動作,等待3秒鍾之后,切換回Hello World場景。
最后,讓我們添加一些非常基礎的游戲邏輯。首先,需要記錄住忍者到底干掉了的多少個妖怪。在HelloWorldLayer類的HelloWorldLayer.h中添加一個成員變量,如下所示:
int _monstersDestroyed;
在HelloWorldLayer.m中添加GameOverLayer類的引入:
#import "GameOverLayer.h"
在update方法的monstersToDelete循環中的removeChild:monster語句之后,添加如下代碼,遞增計數並且檢查勝利條件:
_monstersDestroyed++; if (_monstersDestroyed > 30) { CCScene *gameOverScene = [GameOverLayer sceneWithWon:YES]; [[CCDirector sharedDirector] replaceScene:gameOverScene]; }
最后,讓我們設定只要有一個妖怪穿過屏幕,你就輸了。在addMonster:方法的CCCallBlockN回調中的removeFromParentAndCleanup語句之后,添加如下代碼:
CCScene *gameOverScene = [GameOverLayer sceneWithWon:NO];
[[CCDirector sharedDirector] replaceScene:gameOverScene];
來吧!生成並運行您的游戲,當滿足上述輸贏條件時應該能看到游戲結束的場景了!
下一步做些什么?
點擊鏈接可以下載本教程使用的完成源代碼:簡單的Cocos2D iPhone游戲。
您可以將此項目作為一個良好的基礎,通過在項目中添加一些新的功能,進一步了解Cocos2D的相關知識。可以考慮添加一個條形圖顯示已經干掉了多少個妖怪,查閱drawPrimitivesTest示例項目可以了解相關內容。也可以考慮在干掉怪物時添加更酷的死亡動畫,查閱ActionsTest、EffectsTest和EffectsAdvancedTest項目可以了解相關內容。也可以考慮添加更多的聲音、圖片或游戲邏輯讓游戲更加好玩。創意無極限!
著作權聲明:本文由http://www.cnblogs.com/liufan9翻譯,歡迎轉載分享。請尊重作者勞動,轉載時保留該聲明和作者博客鏈接,謝謝!