正好不知道接下來要怎么寫的時候,發現了一本好書:《Learn IPhone and iPad Cocos2d Game Delevopment》。于是直接翻譯了第4章的例子。如果你看過這部分內容,可以直接跳過不看了。本章講如何響應加速器事件。
?
## 一、游戲介紹
這個例子是一個叫做DoodleDrop的游戲,是一個重力感應類游戲。玩家操縱角色來躲避從空中墜落的障礙物。游戲界面如下:

?
## 二、設置主場景
1、新建Cocos2dApplication,工程名DoodleDrop。
2、游戲主場景。選File-> new file,選擇User Templates -> Cocos2d.0.99.x -> CCNode.class。SubclassOf選擇CCLayer。文件名選GameScene。
3、在頭文件中聲明靜態方法+(id) scene;
4、.m文件
~~~
#import"GameScene.h"
@implementationGameScene
+(id) scene {
CCScene *scene = [CCScene node];
CCLayer* layer = [GameScene node];
[scene addChild:layer];
return scene;
}
-(id) init {
if ((self = [super init])) {
CCLOG(@"%@: %@",NSStringFromSelector(_cmd), self); returnself;
}
}
-(void)dealloc {
// never forget to call [super dealloc] [superdealloc];
CCLOG(@"%@: %@",NSStringFromSelector(_cmd), self);
[super dealloc];
}
@end
~~~
5、刪除HelloWorldScene.h和HelloWorldScene.m文件。
6、修改DoodleDropAppDelegate.m,將其中的主場景啟動代碼修改為GameScene:
~~~
[[CCDirectorsharedDirector] runWithScene: [GameScene scene]];
~~~
## 三、游戲角色
1、把玩家角色圖片alien.png添加到工程。添加時,選中“Copy items”,同時勾選“add to targets”中的“DoodleDrop”選項。
2、在游戲主場景(GameScene.h)增加變量聲明:
CCSprite*player;
3、在游戲主場景(GameScene.m)的init方法中加入下列代碼:
~~~
self.isAccelerometerEnabled= YES;
player =[CCSprite spriteWithFile:@"alien.png"];
[selfaddChild:player z:0 tag:1];
CGSizescreenSize = [[CCDirector sharedDirector] winSize];
?float imageHeight = [playertexture].contentSize.height;
player.position= CGPointMake(screenSize.width / 2, imageHeight / 2);
~~~
這樣,玩家角色就被放到屏幕底部正中的位置上。注意,player變量未retain。因為addChild會自動retain。
[playertexture].contentSize.height返回的是渲染圖的content size。渲染對象(玩家角色圖片alient.png)有兩個尺寸:contentsize和texture size。前者是圖片的實際尺寸,后者是渲染尺寸——iPhone規定渲染尺寸只能是2的n次方。比如圖片實際尺寸100*100,那么渲染尺寸則是128*128,因為最接近100的2的n次方為128。
## 四、使用加速器
1、為了響應加速器事件,你必須在init方法中加上:
~~~
self.isAccelerometerEnabled= YES;
~~~
同時實現accelerometer方法:
~~~
-(void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration*)acceleration{
CGPoint pos = player.position;
?pos.x += acceleration.x * 10;
player.position = pos;
}
~~~
跟java和c不同。你不能對player.position.x進行賦值。這種賦值在
c語言中是可以的,但oc中不行。因為player.position實際上是調用[playerposition],這個方法返回一個臨時的CGPoint變量。當你想對這個臨時的CGPoint的x進行賦值后,這個變量會被被拋棄,所以你的賦值沒有任何作用。所以你需要用一個新的CGPoint變量,修改其x值,然后再把這個CGPoint賦值給player.position(即調用[playersetPosition:])。如果你是來自java和c++的程序員,在oc中需要留心這個“不幸的”問題并盡可能的修改編程習慣。
2、運行測試
模擬器不支持重力感應,請在物理設備上運行代碼。
## 五、玩家控制
現住發現用加速器控制有些不靈?反應遲鈍,移動也不流暢?為此,我們需要增加一些代碼。
首先需要增加變量聲明:
~~~
CGPointplayerVelocity;
~~~
為了便于今后的擴展(假設有一天我們會想上下移動角色),這是一個CGPoint類型,而不是一個float。
然后修改加速器方法:
~~~
-(void)accelerometer:(UIAccelerometer *)accelerometer
didAccelerate:(UIAcceleration*)acceleration
{
// 減速度系數(值越小=轉向越快)
float deceleration = 0.4f;
// 加速度系數 (值越大 = 越敏感)
float sensitivity = 6.0f;
// 最大速度
float maxVelocity = 100;
// 根據加速度計算當前速度
?playerVelocity.x = playerVelocity.x * deceleration + acceleration.x* sensitivity;
// 限制最大速度為 ±maxVelocity之間
?directions if (playerVelocity.x > maxVelocity) {
playerVelocity.x= maxVelocity;
} else if (playerVelocity.x < - maxVelocity){
playerVelocity.x= - maxVelocity;
}
}
~~~
現在,玩家速度由一個一次線性方程決定:
V= V? * β + V? * ε
其中,
V為終速
V?為初速
β ???為減速系數
V?為加速度
ε為加速系數
其中,β 和 ε兩個系數(即減速度系數和加速度系數:deceleration和sensitivity變量)是兩個經驗值,你可以自己調整它以達到理想效果。
然后,需要通過以下方法來改變游戲角色的位置:
~~~
-(void) update:(ccTime)delta{
// 不斷改變角色x坐標
?CGPointpos = player.position;
pos.x += playerVelocity.x;
// 防止角色移到屏幕以外
?CGSizescreenSize = [[CCDirector sharedDirector] winSize];
float imageWidthHalved = [playertexture].contentSize.width * 0.5f; float leftBorderLimit = imageWidthHalved;
float rightBorderLimit = screenSize.width -imageWidthHalved;
if (pos.x < leftBorderLimit) {
pos.x =leftBorderLimit;
playerVelocity= CGPointZero;
} else if(pos.x > rightBorderLimit) {
pos.x =rightBorderLimit;
playerVelocity= CGPointZero;
}
player.position = pos;
?}
~~~
然后,在init方法中加入:
~~~
[selfscheduleUpdate];
~~~
這樣,每隔一段時間cocos2d會自動調用update方法。
## 六、添加障礙物
導入spider.png圖片到工程。這是一張蜘蛛的圖片,在游戲中我們需要躲避的東西。
首先,增加如下變量聲明:
~~~
CCArray*spiders;
floatspiderMoveDuration;
?int numSpidersMoved;
~~~
在init方法中,加上一句方法調用語句:
~~~
[selfinitSpiders];
~~~
下面是initSpiders方法:
~~~
-(void) {
CGSize screenSize = [[CCDirectorsharedDirector] winSize];
// 用一個臨時的CCSprider取得圖片寬度
CCSprite* tempSpider = [CCSpritespriteWithFile:@"spider.png"]; floatimageWidth = [tempSpider texture].contentSize.width;
// 計算出要多少蜘蛛圖片可以布滿屏幕的寬度
int numSpiders = screenSize.width / imageWidth;
// 初始化數組并指定數組大小
spiders = [[CCArray alloc]initWithCapacity:numSpiders];
for (int i = 0; i < numSpiders; i++) {
CCSprite*spider = [CCSprite spriteWithFile:@"spider.png"]; [self addChild:spider z:0 tag:2];
[spidersaddObject:spider];
}
[self resetSpiders];
}
~~~
tempSpider是一個臨時變量,我們僅用于取得圖片寬度。我們沒有retain他,也不需要release他——他會自動被release。
與此相反,spiders是由我們init的,我們也沒有retain(實際上init會自動retain),但我們必須自己release(OC規定,init/copy/new出來的對象,必須手動release,OC的內存管理不會自動release)。因此在dealloc方法中有這么一句:
~~~
[spidersrelease],spiders=nil;
~~~
同時,我們使用了coco2d提供的一個類似NSMutableArray的CCArray類,該類對數組的操作更快。以下是CCArray提供的一些方法:
~~~
+ (id) array;
+ (id)arrayWithCapacity:(NSUInteger)capacity;
+ (id)arrayWithArray:(CCArray*)otherArray;
+ (id)arrayWithNSArray:(NSArray*)otherArray;
- (id)initWithCapacity:(NSUInteger)capacity;
?- (id)initWithArray:(CCArray*)otherArray;
- (id)initWithNSArray:(NSArray*)otherArray;
-(NSUInteger) count;
-(NSUInteger) capacity;
-(NSUInteger) indexOfObject:(id)object;
?- (id) objectAtIndex:(NSUInteger)index;
- (id)lastObject;
- (BOOL)containsObject:(id)object;
#pragma markAdding Objects
- (void)addObject:(id)object;
- (void)addObjectsFromArray:(CCArray*)otherArray;
?- (void)addObjectsFromNSArray:(NSArray*)otherArray;
- (void) insertObject:(id)objectatIndex:(NSUInteger)index;
#pragma markRemoving Objects
- (void)removeLastObject;
- (void)removeObject:(id)object;
?- (void)removeObjectAtIndex:(NSUInteger)index;
- (void)removeObjectsInArray:(CCArray*)otherArray;
- (void) removeAllObjects;
- (void)fastRemoveObject:(id)object;
- (void)fastRemoveObjectAtIndex:(NSUInteger)index;
- (void)makeObjectsPerformSelector:(SEL)aSelector;
- (void)makeObjectsPerformSelector:(SEL)aSelector withObject:(id)object;
- (NSArray*)getNSArray;
~~~
resetSpiders 方法如下所示:
~~~
-(void)resetSpiders {
CGSize screenSize = [[CCDirectorsharedDirector] winSize];
// 用一個臨時的CCSprider取得圖片寬度
CCSprite* tempSpider = [spiders lastObject];
CGSize size = [tempSpider texture].contentSize;
int numSpiders = [spiders count];
for (int i = 0; i < numSpiders; i++) {
// 放置每個蜘蛛的位置
CCSprite*spider = [spiders objectAtIndex:i];
spider.position=
CGPointMake(size.width * i + size.width * 0.5f,
screenSize.height + size.height);
[spiderstopAllActions];
}
// 為保險起見,在注冊之前先從schedule中反注冊(未注冊則不動作)
[self unschedule:@selector(spidersUpdate:)];
// 注冊schedule,每0.7秒執行
[self schedule:@selector(spidersUpdate:)interval:0.7f];
}
?
-(void) {
? // 找出空閑的蜘蛛(未在移動的).
? for (int i = 0; i < 10; i++) {
??? // 從數組中隨機抽取一只蜘蛛
??? int randomSpiderIndex =CCRANDOM_0_1() * [spiders count];
??? CCSprite* spider = [spidersobjectAtIndex:randomSpiderIndex];
??? // 若蜘蛛未在移動,讓蜘蛛往下掉
??? if ([spidernumberOfRunningActions] == 0) {
????? // 控制蜘蛛往下掉
????? [selfrunSpiderMoveSequence:spider];
????? // 每次循環僅移動一只蜘蛛
????? break;
??? }
? }
}
-(void)runSpiderMoveSequence:(CCSprite*)spider
{
? // 隨時間逐漸加快蜘蛛的速度
? numSpidersMoved++;
? if (numSpidersMoved % 8 == 0 &&spiderMoveDuration > 2.0f) {
??? spiderMoveDuration -= 0.1f;
? }
// 移動的終點
CGPoint belowScreenPosition= CGPointMake(spider.position.x,
-[spidertexture].contentSize.height);
// 動作:移動
?CCMoveTo* move = [CCMoveToactionWithDuration:spiderMoveDuration
position:belowScreenPosition];
// 瞬時動作:方法調用
CCCallFuncN*call = [CCCallFuncN actionWithTarget:self
selector:@selector(spiderBelowScreen:)];
// 組合動作:移動+方法調用
CCSequence*sequence = [CCSequence actions:move, call, nil];
// 運行組合動作
[spiderrunAction:sequence];
}
spiderBelowScreen方法重置蜘蛛的狀態,讓其回到屏幕上端等待下次墜落。
-(void)spiderBelowScreen:(id)sender {
// 斷言:sender是否為CCSprite.
?NSAssert([sender isKindOfClass:[CCSpriteclass]], @"sender is not a CCSprite!");
CCSprite*spider = (CCSprite*)sender;
// 把蜘蛛重新放回屏幕上端
CGPoint pos =spider.position;
CGSizescreenSize = [[CCDirector sharedDirector] winSize];
pos.y =screenSize.height + [spider texture].contentSize.height; spider.position = pos;
}
~~~
書中作者提到,出于一個“保守”程序員的習慣,作者使用了 NSAssert語句來測試sender是否是一個CCSprite類。雖然理論上,Sender應當是一個CCSprite,實際上它卻有可能根本不是。因為作者曾犯過一個錯誤:把CCCallFuncN寫成了CCCallFunc(二者的區別在于,后者不能傳遞參數而前者帶一個sender參數),導致sender未被作為參數傳遞到調用方法,即sender=nil。這樣的錯誤也被NSAssert捕獲到了,于是作者發現并修改了這個錯誤。
## 七、碰撞檢測
很簡單。在update方法中添加語句:
~~~
[selfcheckForCollision];
~~~
checkForCollision中包含了碰撞檢測的所有邏輯:
~~~
-(void )checkForCollision {
// 玩家和蜘蛛的尺寸
floatplayerImageSize = [player texture].contentSize.width;
floatspiderImageSize = [[spiders lastObject] texture].contentSize.width;
//玩家和蜘蛛的碰撞半徑
floatplayerCollisionRadius = playerImageSize * 0.4f;
floatspiderCollisionRadius = spiderImageSize * 0.4f;
// 發生碰撞的最大距離,如果兩個對象間的距離<=此距離可判定為有效碰撞
floatmaxCollisionDistance=playerCollisionRadius +spiderCollisionRadius;
intnumSpiders = [spiders count];
//循環檢測玩家和每一只蜘蛛間的碰撞距離
for (int i =0; i < numSpiders; i++) {
? CCSprite* spider = [spidersobjectAtIndex:i];
? // 計算每只蜘蛛和玩家間的距離. ccpDistance及其他非常有用的函數都列在CGPointExtension中
? float actualDistance = ?ccpDistance(player.position,spider.position);
? // 如二者距離小于碰撞最大距離,認為發生碰撞?
? if (actualDistance <maxCollisionDistance) {
??? // 結束游戲.
??? [self showGameOver];
? }
}
}
-(void)showGameOver
{
// 屏保開啟
[self setScreenSaverEnabled:YES];
// 凍結所有對象的動作
CCNode* node;
CCARRAY_FOREACH([self children], node){
[nodestopAllActions];
}
// 使蜘蛛保持扭動
CCSprite* spider;
CCARRAY_FOREACH(spiders, spider){
[selfrunSpiderWiggleSequence:spider];
}
// 游戲開始前,關閉加速器的輸入
self.isAccelerometerEnabled = NO;
// 允許觸摸
self.isTouchEnabled = YES;
// 取消所有schedule
[self unscheduleAllSelectors];
?
// 顯示GameOver文本標簽
CGSize screenSize = [[CCDirectorsharedDirector] winSize];
CCLabel* gameOver = [CCLabellabelWithString:@"GAME OVER!" fontName:@"Marker Felt"fontSize:60];
gameOver.position =CGPointMake(screenSize.width / 2, screenSize.height / 3);
[self addChild:gameOver z:100 tag:100];
// 動作:色彩漸變
CCTintTo* tint1 = [CCTintToactionWithDuration:2 red:255 green:0 blue:0];
CCTintTo* tint2 = [CCTintToactionWithDuration:2 red:255 green:255 blue:0];
CCTintTo* tint3 = [CCTintToactionWithDuration:2 red:0 green:255 blue:0];
CCTintTo* tint4 = [CCTintToactionWithDuration:2 red:0 green:255 blue:255];
CCTintTo* tint5 = [CCTintToactionWithDuration:2 red:0 green:0 blue:255];
CCTintTo* tint6 = [CCTintToactionWithDuration:2 red:255 green:0 blue:255];
CCSequence* tintSequence = [CCSequenceactions:tint1, tint2, tint3, tint4, tint5, tint6, nil];
CCRepeatForever* repeatTint = [CCRepeatForeveractionWithAction:tintSequence];
[gameOver runAction:repeatTint];
// 動作:轉動、顫動
CCRotateTo* rotate1 = [CCRotateToactionWithDuration:2 angle:3];
CCEaseBounceInOut* bounce1 = [CCEaseBounceInOutactionWithAction:rotate1];
CCRotateTo* rotate2 = [CCRotateTo actionWithDuration:2angle:-3];
CCEaseBounceInOut* bounce2 = [CCEaseBounceInOutactionWithAction:rotate2];
CCSequence* rotateSequence = [CCSequenceactions:bounce1, bounce2, nil];
CCRepeatForever* repeatBounce =[CCRepeatForever actionWithAction:rotateSequence];
[gameOver runAction:repeatBounce];
// 動作:跳動
CCJumpBy* jump = [CCJumpBy actionWithDuration:3position:CGPointZero height:screenSize.height / 3 jumps:1];
CCRepeatForever* repeatJump = [CCRepeatForeveractionWithAction:jump];
[gameOver runAction:repeatJump];
// 標簽:點擊游戲開始
CCLabel* touch = [CCLabellabelWithString:@"tap screen to play again"fontName:@"Arial" fontSize:20];
touch.position = CGPointMake(screenSize.width /2, screenSize.height / 4);
[self addChild:touch z:100 tag:101];
// 動作:閃爍
CCBlink* blink = [CCBlink actionWithDuration:10blinks:20];
CCRepeatForever* repeatBlink = [CCRepeatForeveractionWithAction:blink];
[touch runAction:repeatBlink];
}
~~~
當然,為了使游戲一開始就停頓在GameOver畫面,需要在init方法中調用:
~~~
[selfshowGameOver];
~~~
只有當用戶觸摸屏幕后,游戲才會開始。這需要實現方法:
~~~
-(void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self resetGame];
}
resetGame方法負責重置游戲變量并啟動游戲。
-(void)resetGame
{
// 關閉屏保
[self setScreenSaverEnabled:NO];
// 移除GameOver標簽和啟動游戲標簽
[self removeChildByTag:100 cleanup:YES];
[self removeChildByTag:101 cleanup:YES];
// 啟動加速器輸入,關閉觸摸輸入
self.isAccelerometerEnabled = YES;
self.isTouchEnabled = NO;
// 重設蜘蛛數組
[self resetSpiders];
// 注冊schedule
[self scheduleUpdate];
// 積分
score = 0;
totalTime = 0;
[scoreLabel setString:@"0"];
}
開啟/關閉屏保的方法:
-(void)setScreenSaverEnabled:(bool)enabled
{
UIApplication *thisApp = [UIApplicationsharedApplication];
thisApp.idleTimerDisabled = !enabled;
}
使蜘蛛不停扭動的方法如下(實際上是把圖像不斷的放大縮小):
-(void)runSpiderWiggleSequence:(CCSprite*)spider
{
//動作:放大
CCScaleTo* scaleUp = [CCScaleToactionWithDuration:CCRANDOM_0_1() * 2 + 1 scale:1.05f];
//速度漸變動作:速度由慢至快,再由快至慢
CCEaseBackInOut* easeUp = [CCEaseBackInOutactionWithAction:scaleUp];
//動作:縮小
CCScaleTo* scaleDown = [CCScaleToactionWithDuration:CCRANDOM_0_1() * 2 + 1 scale:0.95f];
//速度漸變動作:速度由慢至快,再由快至慢
CCEaseBackInOut*easeDown = [CCEaseBackInOut actionWithAction:scaleDown];
CCSequence* scaleSequence = [CCSequenceactions:easeUp, easeDown, nil];
CCRepeatForever* repeatScale = [CCRepeatForeveractionWithAction:scaleSequence];
[spider runAction:repeatScale];
}
?
~~~
## 八、CCLabel、CCBitmapFontAtlas 和 Hiero
我們的計分標準很簡單,以游戲時間作為游戲分數。
在init方法中加入:
~~~
scoreLabel =[CCLabel labelWithString:@"0" fontName:@"Arial"fontSize:48];
scoreLabel.position= CGPointMake(screenSize.width / 2, screenSize.height);
// 調整錨點。
scoreLabel.anchorPoint= CGPointMake(0.5f, 1.0f);
// 把label添加到scene,z坐標為-1,則位于所有layer的下方
[selfaddChild:scoreLabel z:-1];
~~~
為了將計分牌對其到屏幕上端中心位置,這里使用了“錨點”的概念。 錨點即參考點,和position屬性配合使用,用于將物體向其他物體對齊。比如當把一個物體移動到一個位置點時,實際上是把這個物體的“錨點”移動/對齊到另外一個點。錨點由兩個float表示,表示的是錨點相對于物體寬/高的比率。比如錨點(0.5f,1.0f)表示該錨點位于該物體寬1/2,高1/1的地方。
修改update方法,在其中加入:
~~~
// 每秒更新一次計分牌
totalTime +=delta;
?int currentTime = (int)totalTime;
if (score< currentTime)
{
? score = currentTime;
? [scoreLabel setString:[NSStringstringWithFormat:@"%i", score]];
}
~~~
這里需要說明的是,[CCLabelsetString]方法的效率很低:它需要釋放老的texture,分配一個新的texture,并用iOS font的rendering方法重新構造texture。你只需要注釋[CCLabel? setString]方法就可以知道,那有多么的糟糕。不使用setString方法時幀率為60幀/秒,而使用該方法的幀率竟然才30幀/秒。
象CCSprite等刷新效率高(只是更費一點內存)的Label類,都是屬于CCBitmapFontAtlas類的特例。我們可以通過簡單地把CCLabel變量聲明從CCLabel更改為CCBitmapFontAtlas,并修改它的構造語句:
~~~
scoreLabel =[CCBitmapFontAtlas bitmapFontAtlasWithString:@"0"fntFile:@"bitmapfont.fnt"];
~~~
在游戲中使用bitmapfont是很好的選擇,因為操作更快速,同時會有一個缺點:bitmap字體都是大小固定的。如果同樣的字體,大小不同,你需要對CCBitmapFontAtlas對象進行縮放。或者為不同尺寸的字體創建單獨的bitmap文件,并因此占用更多的內存。
當然需要把bitmapfont.fnt文件和對應的.png文件一起加入到工程的資源目錄下。
如果你需要創建自己的bitmap字體,可以用Hiero這個小工具(javaweb application):
[http://slick.cokeandcode.com/demos/hiero.jnlp](http://slick.cokeandcode.com/demos/hiero.jnlp)
也可以使用BMFont (windows應用):
?[www.angelcode.com/products/bmfont/](http://www.angelcode.com/products/bmfont/)
Hiero允許你從TrueTypeFont創建一個.fnt文件,該文件可以直接用于cocos2d的CCBitmapFontAtlas類。
安裝Hiero時需要同意一個數字簽名。請放心,迄今為止沒有跡象表明該簽名有任何問題。
?Hiero的使用很簡單,首先挑選一種TrueType字體,在SampleText 文本框中輸入你要用的字符,然后點擊File->Save BMFont Files…即可保存為.fnt文件。
?

?
其他的選項是可選的。比如你可以加上漸變和陰影效果,使字體顯得更3D。
選擇Glyph cache后,你還可以調整生成的.png文件的大小。當然,如果你象我一樣只用到了極少的幾個字符,只要把頁寬/高設為最小值(比如在這里我們設成了256),然后點擊ResetCache應用。這樣可以創建比較小.png文件同時減少內存占用。對于更復雜的字體,Hiero會創建多個.png文件——記住,每一個.png文件都應當加到工程中。
在這個例子里,我們的字體文件里只放了幾個數字。因為png文件被創建為256*256大小,不管你是輸入1個字還是再加幾個其他的字,都會占用這么多的空間。
注意,如果你使用了在.fnt文件中不存在的字符,那么該字符會被忽略掉,且不會顯示在CCBitmapFontAtlas中。
## 九、加入音頻
在工程目錄中有一對音頻文件: blues.mp3 和 alien-sfx.caf 。
在cocos2d中播放音頻的最好也是最初的方法是用 SimpleAudioEngine。然而音頻支持并不是cocos2d內置的一部分。它屬于CocosDenshion,就像物理引擎一樣。因此,你需要import額外的頭文件:
~~~
#import "SimpleAudioEngine.h"
?
~~~
然后可以在init方法中象這樣來播放音樂/音頻:
~~~
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"blues.mp3"loop:YES];
[[SimpleAudioEngine sharedEngine] preloadEffect:@"alien-sfx.caf"];
~~~
對于背景音樂,我們設置loop參數為YES,這樣就會循環播放。
對于音頻聲效,我們并沒有立即播放,而僅僅是加載到內存。然后在條件合適時播放(比如碰撞發生時):
~~~
[[SimpleAudioEngine sharedEngine]playEffect:@"alien-sfx.caf"];
~~~
對于音樂,最好使用mp3格式。注意,同一時間內,只能播放1首背景音樂。雖然同時播放多首mp3從技術上是可行的,但物理硬件在同一時間內只能對一首mp3進行解碼。在游戲中拒絕任何額外的CPU開銷,因此對大部分游戲而言,都不會同時播放多首mp3.
至于聲效,我喜歡用CAF格式。如果要進行音頻格式的轉換,可以使用 SoundConverter:
http://dekorte.com/projects/shareware/SoundConverter/
如果文件大小在500k以內,該軟件是免費的,無限制的許可僅僅需要$15。
如果你發現無法播放音頻文件或者出現雜音,不要擔心。有無數音頻軟件和音頻編碼擁有它們特有的文件格式。有些格式無法在iOS設備上播放,然而在其他設備上播放正常。解決辦法是打開它們,然后重新保存。或者使用音頻轉換程序或音頻軟件。
## 十、遷移至iPad
如果所有的坐標都采用屏幕坐標,在iPad的大屏上運行游戲將會進行簡單縮放而沒有任何問題。相反,如果采用了固定坐標,你不得不重新編寫游戲代碼。
?
遷移至iPad工程很簡單。在Groups&Files面板中選擇Target,選擇Project->Upgrade CurrentTarget for iPad…,將打開對話框:

?
對于這個游戲,選“One Universal application”(即iPhone/iPad通用)。
這樣的缺點是兩個設備的特性都會被加到target,增加了程序大小。但程序既可在iPhone上運行,也可在iPad上運行。
另一個選擇是“Two device-specific application”,你會得到兩個獨立于設備的app,你需要提交兩次。如果用戶有兩個設備——iPhone和iPad的,那么需要分別購買。
?
編譯運行。程序會自動偵測當前所連接的設備類型并運行對應的版本。如圖,選擇iPad Simulator 3.2 ,可以查看在iPad模擬器運行游戲的效果:

?