iOS cocos2d遊戲引擎的瞭解之一

ios遊戲引擎之Cocos2d(一)

cocos2d是一個免費開源的ios遊戲開發引擎,而且徹底採用object-c進行編寫,這對於已經用慣object-c進行ios應用開發的童鞋來講很是容易上手。這些也是我推薦使用cocos2d進行ios遊戲開發的緣由,固然從字面上已經能夠開出來,這是一款專一於「2d」遊戲的開發引擎,您也能夠本身編寫3d渲染代碼或者使用第三方的解決方案,在cocos2d里加載顯示3d模型。此外對於3d,也能夠選用cocos3d來進行遊戲開發。好了,廢話很少說,仍是先專一在cocos2d。

下面我會主要經過最近正在着手開發的一款小遊戲來講一下cocos2d:(關於如何安裝cocos2d,網上有大把的資料可查,這裏就不詳細說了,安裝推薦下這篇http://hi.baidu.com/784500515/item/402e6d100d7b8df6dceecaf1,我親身實踐過是靠譜的。此外網上還有不少關於cocos2d的教程也不錯的,能夠去看看,像Learn iPhone and iPad cocos2d game development和知易cocos2d-iphone開發教程。)

大概介紹下這款遊戲:經過iphone的三軸陀螺儀來操縱遊戲主角,避開沿路的炸彈,並獲取隨機散落的金幣來得分,我只說整個遊戲的骨架。固然你也能夠有更多的拓展,好比讓沿路的炸彈有多種隨機類型,有的炸彈吃到會變讓遊戲主角變大,有的炸彈會把主角沿前進的方向炸飛使其加速等等。最尋常的炸彈就是僅僅炸到主角使其生命值減小。此外也能夠對主角進行選擇,好比讓不一樣的主角類型擁有不一樣的橫向移動速度和不一樣的滿格生命值。。。

遊戲主界面的截圖主要包括:得分(左上角),主角生命值(右上角),遊戲速度控制按鈕(右上角),炸彈和金幣,遊戲主角(屏幕底部)以及一直滾動的背景圖

—————————————下面是遊戲主界面截圖—————————————



首先,咱們須要先建立一個cocos2d的工程,在xcode的新建工程界面選擇cocos2d分類下的cocos2d Application,鍵入工程名稱(我把它命名爲DropBomb)後,點擊「save」。若是你已經安裝成功cocos2d,xcode會幫助建立一個基本的cocos2d Application工程,裏面會包含一些基本的文件和代碼,包括「Helloworld」。咱們須要對這個基本的工程進行一些修改,以使其符合這個遊戲的須要。

咱們先分別添加兩個繼承CCSprite的類myBomb和myCoin,這兩個類用於控制炸彈和金幣,注意添加的時候同時勾選「Also create XXX.h」,這樣添加完成後,xcode會自動將該類的.m文件和.h頭文件都添加到當前工程下。而後刪除xcode幫咱們自動生成的Helloworld.m和相應的.h頭文件,添加遊戲的主場景,新建一個繼承CCLayer的類GameScene。此外還須要添加一個結束呈現得分以及從新開始的界面EndScene(繼承自CCLayer)以及一個控制跳轉和載入過程的界面LoadingScene(繼承自CCScene)。另外我對系統提供的CCAnimation類進行了擴展,使其能夠更方便地用於CCSprite播放幀動畫,這個類我命名爲Helper。

好了,這樣工程的基本文件結構就完成了,整個工程文件的結構以下(其餘一些不是很重要的圖片資源文件不是很影響整個工程,因此就沒有截全)

—————————————下面是整個工程文件截圖—————————————



因爲咱們刪除了系統給添加的「Helloworld」,那麼就須要來修改啓動頁面事後進入到的遊戲主場景界面。在「Groups & Files」中進入DropBombAppDelegate.m,對applicationDidFinishLaunching函數中的runWithScene進行修改,將遊戲主場景設爲GameScene:

[[CCDirector sharedDirector] runWithScene: [GameScene scene]];

另外,cocos2d默認的屏幕顯示方向是橫向的,這裏須要縱向的屏幕來進行遊戲,找到shouldAutorotateToInterfaceOrientation函數,對GAME_AUTOROTATION == kGameAutorotationUIViewController條件下的return值進行修改,將UIInterfaceOrientationIsLandscape改成UIInterfaceOrientationIsPortrait

#elif GAME_AUTOROTATION == kGameAutorotationUIViewController

return ( UIInterfaceOrientationIsPortrait( interfaceOrientation ) );

好了,暫時沒必要理會DropBombAppDelegate.m中的其餘代碼,咱們還不用動到他們。此時運行項目,在啓動頁面事後會出現空白頁面,那是由於咱們尚未給GameScene主場景中添加內容。沒關係,如今立刻切換到GameScene中。

 

1. GameScene類

咱們須要在GameScene中以靜態方法定義場景節點和層節點,在GameScene.h中添加一個靜態方法:

+(id)scene;

而後在GameScene.m中實現這個靜態方法:

+(id)scene {

CCScene *scene = [CCScene node];

CCLayer *layer = [GameScene node];

[scene addChild:layer];

return scene;

}

此時,咱們的GameScene中就包含了一個CCScene的場景節點和GameScene的層節點(不要忘了GameScene是繼承自CCLayer的)。下面就經過實現GameScene的init方法來爲主場景添加點內容吧:咱們在GameScene的init方法中對遊戲的時間、得分和生命值進行了初始化以保證每次切換到主場景時,這些數值都是新的;而後咱們對遊戲主角、炸彈、金幣、遊戲背景、加速按鈕、得分顯示、生命值顯示等進行了初始化(注意此處對遊戲主角的初始化用到了咱們對系統的CCAnimation擴展的方法,須要實現Helper類後纔可使用,稍後再來看Helper的實現方法);此外咱們還初始化了炸彈的初始下落速度,並設定了一個循環播放的背景音樂。因爲須要對主場景中的變量傳遞到其餘的層中,咱們還在init方法中將self賦值給了GameScene的一個靜態對象(cocos2d中將這種方法稱爲「僞單例」)。詳細的GameScene的init方法以下,對代碼我進行詳細的標註,能夠進行對照:

-(id)init {

if (self == [super init]) {

NSLog(@"%@,%@",NSStringFromSelector(_cmd),self);

totalTime = 0.0f;

score = 0;

life = 3;

self.isAccelerometerEnabled = YES;  //啓用加速計

player = [CCSprite spriteWithFile:@"bomber0.png"];  //用bomber圖片初始化player精靈

[self addChild:player z:2 tag:1];  //將player精靈添加到場景中

CGSize screenSize = [[CCDirector sharedDirector] winSize];  //獲取屏幕大小

float imageHeight = [player texture].contentSize.height;  //獲取精靈中圖片內容的大小

player.position = CGPointMake(screenSize.width*0.5f, imageHeight*0.5f);  //初始化player的位置

CCAnimation *anim = [CCAnimation animationWithFile:@"bomber" frameCount:2 delay:0.5f];  //運用給CCAnimation擴展的方法來生成一個動畫對象anim

CCAnimate *animate = [CCAnimate actionWithAnimation:anim];

CCRepeatForever *repeate = [CCRepeatForever actionWithAction:animate];  //生成一個重複動做的repeate對象

[player runAction:repeate];  //讓player運行這個重複動做

//初始化bomb

[self initBombs];

//初始化coin

[self initCoins];

[self scheduleUpdate];  //用於檢測加速度,以隨時改變player的位置

//設置一個不斷向前滾動的背景

CCSprite *background = [CCSprite spriteWithFile:@"background.png"];

background.position = ccp(0,screenSize.height*3);

background.anchorPoint = ccp(0,1);

[self addChild:background z:-1 tag:2];

CCMoveTo *moveTo = [CCMoveTo actionWithDuration:8 position:CGPointMake(0, screenSize.height)];

CCCallFunc *func = [CCCallFunc actionWithTarget:self selector:@selector(onCallFunc)];

CCSequence *bgSequence = [CCSequence actions:moveTo,func,nil];

CCRepeatForever *bgRepeat = [CCRepeatForever actionWithAction:bgSequence];

[background runAction:bgRepeat];

//設置加速按鈕

CCSprite *normal1 = [CCSprite spriteWithFile:@"speed1.png"];

CCSprite *normalSelected = [CCSprite spriteWithFile:@"speed1.png"];

normalSelected.color = ccGRAY;

CCMenuItemSprite *itemNormal = [CCMenuItemSprite itemFromNormalSprite:normal1 selectedSprite:normalSelected];

CCSprite *speed1 = [CCSprite spriteWithFile:@"speed2.png"];

CCSprite *speedSelected = [CCSprite spriteWithFile:@"speed2.png"];

speedSelected.color = ccGRAY;

CCMenuItemSprite *itemSpeed = [CCMenuItemSprite itemFromNormalSprite:speed1 selectedSprite:speedSelected];

CCMenuItemToggle *speedToggle = [CCMenuItemToggle itemWithTarget:self selector:@selector(speedTouched) items:itemNormal,itemSpeed,nil];

CCMenu *menu = [CCMenu menuWithItems:speedToggle,nil];

menu.position = CGPointMake(screenSize.width - [speed1 texture].contentSize.width * 0.5f - 5, screenSize.height - [speed1 texture].contentSize.height * 0.5f - 5);

[self addChild:menu z:102];

//初始化scoreLabel

scoreLabel = [CCLabelTTF labelWithString:@"SCORE:0" fontName:@"Marker Felt" fontSize:23];

scoreLabel.color = ccGREEN;

//scoreLabel.opacity = 160;

scoreLabel.position = CGPointMake(10, screenSize.height - 10);

scoreLabel.anchorPoint = CGPointMake(0, 1.0f);

[self addChild:scoreLabel z:100];

//初始化lifeLabel

lifeLabel = [CCLabelTTF labelWithString:@"LIVES:3" fontName:@"Marker Felt" fontSize:23];

//lifeLabel.color = ccGREEN;

lifeLabel.position = CGPointMake(screenSize.width * 0.6f, screenSize.height - 10);

lifeLabel.anchorPoint = CGPointMake(0, 1.0f);

[self addChild:lifeLabel z:101];

life = 3; 

//myBomb *test = [[myBomb alloc] init];

//[self addChild:test z:112 tag:111];

//test.position = ccp(screenSize.width/2,screenSize.height/2);

[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"star.mp3" loop:YES];  //播放背景音樂

dropSpeed = 1.0f;  //設置默認下落速度爲1倍速

sharedAllInMe = self;  //將self賦值給共享本層的對象

}

return self;

}

這裏的sharedAllInMe是一個指向當前GameScene的靜態對象,對於其的定義在GameScene.m中,以下:

static GameScene *sharedAllInMe;  //生成一個當前層的靜態對象

固然爲了在其餘的類中能夠訪問這個靜態對象,咱們還須要設置一個返回它的靜態方法,在GameScene.h中定義:

+(GameScene *)getShared;  //生成一個當前層的靜態訪問方法

並在GameScene.m中實現這個方法:

+(GameScene *)getShared {  //返回當前層

return sharedAllInMe;

}

爲了防止在其餘的類中score被修改,咱們須要在GameScene.h中將其定義爲只讀:

@property (assign,readonly) int score;

咱們在設置背景不斷向前滾動時採用的方法是建立一個動做序列bgSequence,在這個動做序列的末尾調用函數onCallFunc,將背景的位置從新挪到第一屏的位置(須要保證這個背景的第一屏圖像與最後一屏圖像一致)。onCallFunc函數的詳細以下:

//當背景圖片滾動到頭時,將其從新放回初始位置

-(void)onCallFunc {  

CCNode *node = [self getChildByTag:2];  //獲取tag爲2的對象做爲一個CCNode對象,就是背景圖

if ([node isKindOfClass:[CCSprite class]]) {

CCSprite *back1 = (CCSprite *)node; 

CGSize size = [[CCDirector sharedDirector] winSize];

back1.position = ccp(0,size.height*3);  //將該圖片的位置重置爲初始位置

}

}

在init中,咱們設定了點擊加速按鈕將會調用的方法speedTouched,對於這個方法咱們做一個簡單的實現,假設咱們有一個二級的速度控制(三級或者更多級別僅須要增長判斷):

//點擊加速按鈕改變下落速度

-(void)speedTouched {

if (dropSpeed == 1.0f) {

dropSpeed = 0.5f;

}

else {

dropSpeed = 1.0f;

}

}

init中還用[self scheduleUpdate]預約了一個每秒調用的函數,以隨時經過加速計修正的速度來調整主角的位置,並進行碰撞測試。這個函數的實現以下:

-(void)update:(ccTime)delta {

//獲取player的當前位置

CGPoint pos = player.position;

//將player的當前位置根據目前方向的速度增長

pos.x += playerVelocity.x;

//獲取player可移動的邊界

CGSize screenSize = [[CCDirector sharedDirector] winSize];

float imageWidthHelv = [player texture].contentSize.width * 0.5f;

float leftBorderLimit = imageWidthHelv;

float rightBorderLimit = screenSize.width - imageWidthHelv;

//判斷player的當前位置是否超過了邊界,並進行相應處理

if (pos.x < leftBorderLimit) {

pos.x = leftBorderLimit;

playerVelocity = CGPointZero;

}

else if (pos.x > rightBorderLimit) {

pos.x = rightBorderLimit;

playerVelocity = CGPointZero;

}

//將處理後的位置信息賦值到player上以改變其位置

player.position = pos;

[self checkForCollision];  //進行bomb和bomber或者coin和bomber的碰撞測試

}

對於碰撞測試的實現函數checkForCollision稍後再做說明,此外這裏須要用到三軸陀螺儀來返回x軸方向上的速度,相應的實現以下:

//利用加速計改變player的位置

-(void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {

float deceleration = 0.3f;  //控制減速的速率,值越小能夠越快地改變方向

float sensitivity = 8.0f;  //控制對加速計輸入的敏感度,值越大對加速計輸入越敏感

float maxVelocity = 100;  //控制最大的速度

playerVelocity.x = playerVelocity.x * deceleration + acceleration.x * sensitivity;  //基於當前加速計的加速度調整速度

if (playerVelocity.x > maxVelocity) {  //控制player的速度在最大速度以內

playerVelocity.x = maxVelocity;

}

else if (playerVelocity.x < -maxVelocity) {

playerVelocity.x = -maxVelocity;

}

}

其中playerVelocity是在GameScene.h中定義的一個CGPoint變量,咱們在此僅對其進行改變以影響主角的位置。

下面來看在GameScene中對炸彈的控制,首先是對炸彈進行初始化,咱們要設定炸彈的初始移動速度,將生成的炸彈放入一個炸彈數組以便於後續的控制,而後將炸彈放置到場景中。initBombs的詳細實現以下:

//初始化bombs

-(void)initBombs {

bombMoveDuration = 1.8f;

CGSize screenSize = [[CCDirector sharedDirector] winSize];

CCSprite *tempBomb = [CCSprite spriteWithFile:@"bomb0.png"];

//NSLog(@"bombs------%@",tempBomb);

float imageWidth = [tempBomb texture].contentSize.width;  //獲取bomb圖片的寬度

int numBombs = screenSize.width / imageWidth;  //計算在當前屏幕寬度下,可容納多少個bomb

bombs = [[CCArray alloc] initWithCapacity:numBombs];  //用可容納的bomb數量來初始化bombs數組

for (int i = 0; i < numBombs; i++) {  //在當前場景中添加這些bomb,並將其放入bombs數組

myBomb *bomb1 = [[[myBomb alloc] init] autorelease];

//NSLog(@"bombs------%@",bomb1);

[self addChild:bomb1 z:1 tag:3];

//bomb1.position = CGPointMake([tempBomb texture].contentSize.width*i + [tempBomb texture].contentSize.width*0.5f, screenSize.height + [tempBomb texture].contentSize.height);

[bombs addObject:bomb1];

}

[self resetBombs];

}

resetBombs用於將炸彈移到屏幕外的初始位置,並中止其動做:

-(void)resetBombs {  //重設bomb的位置,將bomb的位置所有設置在屏幕頂部之外

CGSize screenSize = [[CCDirector sharedDirector] winSize];

myBomb *tmpBomb = [bombs lastObject];  //生成臨時bomb對象

CGSize size = tmpBomb.bombSize;

int numBombs = [bombs count];

for (int i = 0; i < numBombs; i++) {

myBomb *bomb1 = [bombs objectAtIndex:i];  //獲取bombs數組中的每一個bomb對象

bomb1.position = CGPointMake(size.width*i + size.width*0.5f, screenSize.height + size.height);  //將各個bomb對象的位置設置到屏幕頂部之外

//NSLog(@"bombs------%f,%f",bomb1.position.x,bomb1.position.y);

[bomb1 stopAllActions];

}

//將預定的方法取消掉

[self unschedule:@selector(bombUpdates:)];

//用指定的時間間隔調用bomb的更新方法

[self schedule:@selector(bombUpdates:) interval:0.7f];

}

每次resetBombs以後,都會預定一個每隔0.7s執行一次的函數bombUpdates:,並將以前的預定取消。bombUpdates:的做用是在炸彈數組中隨機尋找一個沒有開始移動的炸彈,並讓它開始向屏幕底部移動:

-(void)bombUpdates:(ccTime)delta {

//尋找一隻不動的bomb,並讓其執行動做序列

for (int i = 0; i < 10; i++) {

//獲取bombs數組中的一個bomb對象

int randomBombIndex = CCRANDOM_0_1()*[bombs count];

myBomb *bomb1 = [bombs objectAtIndex:randomBombIndex];

//檢測該bomb對象是否沒有執行任何動做,若是是則讓其執行動做序列

if ([bomb1 numberOfRunningActions] == 0) {

[self runBombMoveSequence:bomb1];

//NSLog(@"update-----%@",bomb1);

break;  //保證一次僅有一個bomb執行動做

}

}

}

控制炸彈的移動動做則經過runBombMoveSequence:函數來實現,該函數會在判斷炸彈沒有與主角碰撞到以後(判斷經過isCollision標誌位來進行),將炸彈向屏幕底部以設定的bombMoveDuration進行移動:

-(void)runBombMoveSequence:(myBomb *)bomb {  //控制bomb的執行動做

if (bomb.isCollision == NO) {

bombMoveDuration = 1.8f * dropSpeed;

//設置bomb的移動終點

CGPoint belowScreenPosition = CGPointMake(bomb.position.x, -bomb.bombSize.height);

//bomb移動動做

CCMoveTo *move = [CCMoveTo actionWithDuration:bombMoveDuration position:belowScreenPosition];

//bomb移動到終點,即屏幕底部之外時,將其從新放回屏幕頂部之外的初始位置

CCCallFuncN *call = [CCCallFuncN actionWithTarget:self selector:@selector(onCallFuncN:)];

//生成動做序列並執行動做

CCSequence *seq = [CCSequence actions:move,call,nil];

[bomb runAction:seq];

//NSLog(@"update-----%f,%f",bomb.position.x,bomb.position.y);

}

}

爲了防止炸彈移動到屏幕底部之外仍然向下移動,在炸彈移動的動做序列最後調用函數

onCallFuncN:將炸彈從新放回到屏幕頂部的初始位置:

-(void)onCallFuncN:(id)sender {

if ([sender isKindOfClass:[myBomb class]]) {  //檢測sender是不是myBomb類,若是是則將其位置重置到屏幕頂部之外的初始位置

myBomb *bomb1 = (myBomb *)sender;

//NSLog(@"update-----%f,%f",bomb1.position.x,bomb1.position.y);

CGPoint pos = bomb1.position;

CGSize screenSize = [[CCDirector sharedDirector] winSize];

pos.y = screenSize.height + bomb1.bombSize.height;

bomb1.position = pos;

}

else {

NSLog(@"sender is not a CCSprite");

}

}

到這裏,GameScene中對於炸彈的控制就差很少了。而對於金幣的控制,原理與炸彈的控制基本一致,就不詳細說了,具體的代碼以下:

//初始化coins

-(void)initCoins {

coinMoveDuration = 1.6f;  //設置coin的移動速度

CGSize screenSize = [[CCDirector sharedDirector] winSize];

CCSprite *tempCoin = [CCSprite spriteWithFile:@"coin0.png"];

//NSLog(@"coins------%@",tempCoin);

float imageWidth = [tempCoin texture].contentSize.width;  //獲取coin圖片的寬度

int numCoins = screenSize.width / imageWidth;  //計算在當前屏幕寬度下,可容納多少個coin

coins = [[CCArray alloc] initWithCapacity:numCoins];  //用可容納的coin數量來初始化coins數組

for (int i = 0; i < numCoins; i++) {  //在當前場景中添加這些coin,並將其放入coins數組

myCoin *coin1 = [[[myCoin alloc] init] autorelease];

//NSLog(@"coins------%@",coin1);

[self addChild:coin1 z:0 tag:4];

//coin1.position = CGPointMake([tempCoin texture].contentSize.width*i + [tempCoin texture].contentSize.width*0.5f, screenSize.height + [tempCoin texture].contentSize.height);

[coins addObject:coin1];

}

[self resetCoins];

}

-(void)resetCoins {  //重設coin的位置,將coin的位置所有設置在屏幕頂部之外

CGSize screenSize = [[CCDirector sharedDirector] winSize];

myCoin *tmpCoin = [coins lastObject];  //生成臨時coin對象

CGSize size = tmpCoin.coinSize;

int numCoins = [coins count];

for (int i = 0; i < numCoins; i++) {

myCoin *coin1 = [coins objectAtIndex:i];  //獲取coins數組中的每一個coin對象

coin1.position = CGPointMake(size.width*i + size.width*0.5f, screenSize.height + size.height);  //將各個coin對象的位置設置到屏幕頂部之外

//NSLog(@"coins------%f,%f",coin1.position.x,coin1.position.y);

[coin1 stopAllActions];



//將預定的方法取消掉

[self unschedule:@selector(coinUpdates:)];

//用指定的時間間隔調用coin的更新方法

[self schedule:@selector(coinUpdates:) interval:0.7f];

}

-(void)coinUpdates:(ccTime)delta {

//尋找一隻不動的coin,並讓其執行動做序列

for (int i = 0; i < 10; i++) {

//獲取coins數組中的一個coin對象

int randomCoinIndex = CCRANDOM_0_1()*[coins count];

myCoin *coin1 = [coins objectAtIndex:randomCoinIndex];

//檢測該coin對象是否沒有執行任何動做,若是是則讓其執行動做序列

if ([coin1 numberOfRunningActions] == 0) {

[self runCoinMoveSequence:coin1];

//NSLog(@"update-----%@",coin1);

break;  //保證一次僅有一個coin執行動做

}

}

}

-(void)runCoinMoveSequence:(myCoin *)coin {  //控制coin的執行動做

if (coin.isGet == NO) {

coinMoveDuration = 1.6f * dropSpeed;

//設置coin的移動終點

CGPoint belowScreenPosition = CGPointMake(coin.position.x, -coin.coinSize.height);

//coin移動動做

CCMoveTo *move = [CCMoveTo actionWithDuration:coinMoveDuration position:belowScreenPosition];

//coin移動到終點,即屏幕底部之外時,將其從新放回屏幕頂部之外的初始位置

CCCallFuncN *call = [CCCallFuncN actionWithTarget:self selector:@selector(coinCallFuncN:)];

//生成動做序列並執行動做

CCSequence *seq = [CCSequence actions:move,call,nil];

[coin runAction:seq];

//NSLog(@"update-----%f,%f",coin.position.x,coin.position.y);

}

}

-(void)coinCallFuncN:(id)sender {

if ([sender isKindOfClass:[myCoin class]]) {  //檢測sender是不是myCoin類,若是是則將其位置重置到屏幕頂部之外的初始位置

myCoin *coin1 = (myCoin *)sender;

//NSLog(@"update-----%f,%f",coin1.position.x,coin1.position.y);

CGPoint pos = coin1.position;

CGSize screenSize = [[CCDirector sharedDirector] winSize];

pos.y = screenSize.height + coin1.coinSize.height;

coin1.position = pos;

}

else {

NSLog(@"sender is not a CCSprite");

}

}

接下來,主要來看一下GameScene中對主角和炸彈以及金幣的碰撞測試部分。首先要獲取主角、炸彈以及金幣的尺寸,這裏採用簡單的徑向測試,所以僅取圖片寬度。而後經過圖片寬度,計算出主角和炸彈、主角和金幣的最大碰撞距離。若是檢測二者間的距離小於最大碰撞距離,即認爲發生了碰撞。與炸彈發生碰撞後,生命值減小1,播放炸彈爆炸的音效並呈現爆炸效果;與金幣發生碰撞後,則播放吃金幣的音效並呈現得分+1的效果。在碰撞後均須要將碰撞標誌位(isCollision和isGet)設爲已碰撞。具體以下:

//碰撞測試

-(void)checkForCollision {

//獲取player和bomb、coin的半徑

float playerImageSize = [player texture].contentSize.width;

myBomb *tmpBomb = [bombs lastObject];

float bombImageSize = tmpBomb.bombSize.width;

myCoin *tmpCoin = [coins lastObject];

float coinImageSize = tmpCoin.coinSize.width;

float playerImageRadius = playerImageSize * 0.4f;

float bombImageRadius = bombImageSize * 0.4f;

float coinImageRadius = coinImageSize * 0.4f;

float maxCollisionDistance = playerImageRadius + bombImageRadius;  //這裏的最大碰撞距離和圖片形狀基本一致

float maxCoinGetDistance = playerImageRadius + coinImageRadius;  //這裏獲取coin和bomber的最大碰撞距離

int numBombs = [bombs count];

int numCoins = [coins count];

for (int i = 0; i < numBombs; i++) {

myBomb *bomb1 = [bombs objectAtIndex:i];

if ([bomb1 numberOfRunningActions] == 0) {  //若是此bomb未移動,咱們就跳過對其的碰撞測試

continue;

}

float actualDistance = ccpDistance(player.position, bomb1.position);  //獲得bomb和bomber間的距離

if (actualDistance < maxCollisionDistance && bomb1.isCollision == NO) {  //檢查是否已碰撞,由於重置這個bomb的位置在延遲0.1s後的函數裏進行,而這段時間裏碰撞測試函數是一直在執行的,爲防止對同一個bomb重複檢測碰撞,增長了isCollision的標誌位

[bomb1 stopAllActions];  //先中止碰撞的bomb的向下掉落動做

[bomb1 explosionEffect];  //讓該bomb呈現爆炸的效果

[[SimpleAudioEngine sharedEngine] playEffect:@"bomb3.wav"];

if (life > 0) {

life--;

[lifeLabel setString:[NSString stringWithFormat:@"LIVES:%i",life]];

}

//NSLog(@"%i",life);

[self schedule:@selector(collisionReset) interval:0.1f];  //延遲執行碰撞後的重置操做

}

//NSLog(@"%@",bomb1.isCollision?@"yes":@"no");

}

for (int i = 0; i < numCoins; i++) {

myCoin *coin1 = [coins objectAtIndex:i];

if ([coin1 numberOfRunningActions] == 0) {  //若是此coin未移動,咱們就跳過對其的碰撞測試

continue;

}

float actualCoinBomberDistance = ccpDistance(player.position, coin1.position);  //獲得coin和bomber間的距離

if (actualCoinBomberDistance < maxCoinGetDistance && coin1.isGet == NO) {  //檢查是否已碰撞,由於重置這個coin的位置在延遲0.5s後的函數裏進行,而這段時間裏碰撞測試函數是一直在執行的,爲防止對同一個coin重複檢測碰撞,增長了isGet的標誌位

[coin1 stopAllActions];  //先中止碰撞的coin的向下掉落動做

[coin1 getEffect];  //讓該coin呈現獲取的效果

[[SimpleAudioEngine sharedEngine] playEffect:@"coinsound.mp3"];

score++;

[scoreLabel setString:[NSString stringWithFormat:@"SCORE:%i",score]];

//NSLog(@"%i",life);

[self schedule:@selector(getCoinReset) interval:0.5f];  //延遲執行碰撞後的重置操做

}

//NSLog(@"%@",coin1.isGet?@"yes":@"no");

}

}

發生碰撞後,須要對炸彈和金幣進行設置,包括位置重置、動做效果重置以及碰撞標誌位的設置,都在相應的設置函數裏進行(collisionReset和getCoinReset)。collisionReset主要用於將已碰撞的炸彈放回屏幕頂部,移去爆炸效果,重置已碰撞標誌位,並在生命值爲0時結束遊戲跳轉到得分呈現和從新開始界面;getCoinReset主要用於將已吃到的金幣+1效果移去,增長得分,重置金幣位置以及已碰撞標誌。具體以下:

-(void)collisionReset {

//重置bomb位置

for (int i = 0; i < [bombs count]; i++) {

myBomb *bomb1 = [bombs objectAtIndex:i];

//檢測該bomb對象是否沒有執行任何動做,若是是則讓其回到屏幕頂部之外

if ([bomb1 numberOfRunningActions] == 0) {

CGPoint pos = bomb1.position;

CGSize screenSize = [[CCDirector sharedDirector] winSize];

pos.y = screenSize.height + bomb1.bombSize.height;

bomb1.position = pos;

[bomb1 removeExplosion];

}

}

if (life <= 0) {

[[SimpleAudioEngine sharedEngine] stopBackgroundMusic];

[[CCDirector sharedDirector] replaceScene:[CCTransitionFadeTR transitionWithDuration:1.0f scene:[LoadingScene sceneWithTargetScene:TargetSceneSecondScene]]];  //調用載入頁面

}

[self unschedule:_cmd];

}

-(void)getCoinReset {  //獲取金幣後重置coin

for (int i = 0; i < [coins count]; i++) {

myCoin *coin1 = [coins objectAtIndex:i];

//檢測該coin對象是否沒有執行任何動做,若是是則讓其回到屏幕頂部之外

if ([coin1 numberOfRunningActions] == 0) {

CGPoint pos = coin1.position;

CGSize screenSize = [[CCDirector sharedDirector] winSize];

pos.y = screenSize.height + coin1.coinSize.height;

coin1.position = pos;

[coin1 removeEffect];

}

}

[self unschedule:_cmd];

}

在GameScene的最後,實現dealloc,將非自動釋放的對象釋放:

-(void)dealloc {

NSLog(@"%@,%@",NSStringFromSelector(_cmd),self);

[bombs release];

bombs = nil;

[coins release];

coins = nil;

sharedAllInMe = nil;

[super dealloc];

}

在GameScene.m中用到的一些函數和變量須要在GameScene.h中聲明,GameScene.h具體以下:

#import <Foundation/Foundation.h>

#import "cocos2d.h"

#import "SimpleAudioEngine.h"

#import "LoadingScene.h"

#import "Helper.h"

#import "myBomb.h"

#import "myCoin.h"

@interface GameScene : CCLayer {

CCSprite *player;

CGPoint playerVelocity;

CCArray *bombs;

float bombMoveDuration;

CCArray *coins;

float coinMoveDuration;

CCLabelTTF *scoreLabel;

float totalTime;

int score;

CCLabelTTF *lifeLabel;

int life;

float dropSpeed;

}

@property (assign,readonly) int score;

+(id)scene;

+(GameScene *)getShared;  //生成一個當前層的靜態訪問方法

-(void)onCallFunc;  //背景圖移動到頭後將其重置

-(void)initBombs;

-(void)resetBombs;

-(void)bombUpdates:(ccTime)delta;

-(void)runBombMoveSequence:(myBomb *)bomb;

-(void)onCallFuncN:(id)sender;

-(void)initCoins;

-(void)resetCoins;

-(void)coinUpdates:(ccTime)delta;

-(void)runCoinMoveSequence:(myCoin *)coin;

-(void)coinCallFuncN:(id)sender;

-(void)checkForCollision;

-(void)collisionReset;

-(void)getCoinReset;

-(void)speedTouched;

@end

到這裏遊戲的主場景GameScene就基本上完成了。接下來,須要實如今GameScene中用到的幾個類:myBomb、myCoin、Helper以及兩個場景LoadingScene和EndScene。

 

2. myBomb類

myBomb類中主要實現了炸彈的初始化、爆炸動做以及移除爆炸動做。爆炸動做採用了cocos2d的粒子效果來實現,關於粒子效果網上有不少資料說明,另外也有第三方提供的粒子效果生成器可直接用,這裏就不展開了。炸彈的初始化也用到了咱們對CCAnimation的擴展方法來播放炸彈在飛行下落過程當中的動做。具體實現以下:

-(id)init {

if (self == [super init]) {

bomb = [CCSprite spriteWithFile:@"bomb0.png"];  //用bomber圖片初始化player精靈

bombSize = [bomb texture].contentSize;

isCollision = NO;

[self addChild:bomb z:0 tag:1];  //將player精靈添加到場景中

bomb.position = CGPointMake(0,0);  //初始化player的位置

CCAnimation *anim = [CCAnimation animationWithFile:@"bomb" frameCount:2 delay:0.5f];  //運用給CCAnimation擴展的方法來生成一個動畫對象anim

CCAnimate *animate = [CCAnimate actionWithAnimation:anim];

CCRepeatForever *repeate = [CCRepeatForever actionWithAction:animate];  //生成一個重複動做的repeate對象

[bomb runAction:repeate];  //讓player運行這個重複動做

}

return self;

}

-(void)explosionEffect {

CCParticleSystem *system;

system = [CCParticleSun node];

[bomb addChild:system z:0 tag:1];

system.position = ccp(bombSize.width/2,bombSize.height/2);

system.startSize = 38.0f;

system.startSizeVar = 80.0f;

system.endSize = 88.0f;

system.endSizeVar = 80.0f;

isCollision = YES;

}

-(void)removeExplosion {

isCollision = NO;

[bomb removeAllChildrenWithCleanup:YES];

}

-(void)dealloc {

[super dealloc];

}

 

3. myCoin類

myCoin類與myBomb類類似,不一樣的是沒有采用粒子效果,而是在碰撞時播放一個+1的漸隱動畫:

-(id)init {

if (self == [super init]) {

coin = [CCSprite spriteWithFile:@"coin0.png"];  //用coin0圖片初始化coin精靈

coinSize = [coin texture].contentSize;

isGet = NO;

[self addChild:coin z:0 tag:1];  //將coin精靈添加到myCoin中

coin.position = CGPointMake(0, 0);  //初始化coin的位置

CCAnimation *anim = [CCAnimation animationWithFile:@"coin" frameCount:2 delay:0.5f];  //運用給CCAnimation擴展的方法來生成一個動畫對象anim

CCAnimate *animate = [CCAnimate actionWithAnimation:anim];

CCRepeatForever *repeate = [CCRepeatForever actionWithAction:animate];  //生成一個重複動做的repeate對象

[coin runAction:repeate];  //讓coin運行這個重複動做

}

return self;

}

-(void)getEffect {  //碰撞金幣後的效果

[coin stopAllActions];  //先暫停coin自己的全部動做

CCTexture2D * texture =[[CCTextureCache sharedTextureCache] addImage:@"plusone.png"];  //新建一個貼圖對象

[coin setTexture:texture];  //將coin的貼圖更換爲texture貼圖對象

CCFadeOut *getAnim = [CCFadeOut actionWithDuration:0.5f];  //+1圖片漸隱

[coin runAction:getAnim];  //coin運行+1漸隱的動做

//NSLog(@"%@",coin);

isGet = YES;  //將該coin的已獲取標誌位置爲yes

}

-(void)removeEffect {  //+1動做完成後對該coin的重置操做

[coin stopAllActions];  //先暫停當前coin執行的全部動做

coin.opacity = 255;  //將透明度重置爲徹底不透明

CCTexture2D * texture =[[CCTextureCache sharedTextureCache] addImage:@"coin0.png"];  //新建一個貼圖對象

[coin setTexture:texture];  

CCAnimation *initAnim = [CCAnimation animationWithFile:@"coin" frameCount:2 delay:0.5f];  //運用給CCAnimation擴展的方法來生成一個動畫對象anim

CCAnimate *initAnimate = [CCAnimate actionWithAnimation:initAnim];  

CCRepeatForever *initRepeate = [CCRepeatForever actionWithAction:initAnimate];  //生成一個重複動做的repeate對象

[coin runAction:initRepeate];  //coin運行這個重複動做

isGet = NO;

}

-(void)dealloc {

[super dealloc];

}

 

4. Helper類

對系統CCAnimation擴展的方法在Helper裏實現,cocos2d容許採用類別功能(category)對其系統提供的類進行方法擴展,但僅能添加方法,不能添加類的成員變量。首先在Helper.h裏進行類別擴展的聲明:

@interface CCAnimation(Helper)

+(CCAnimation *)animationWithFile:(NSString *)name frameCount:(int)frameCount delay:(float)delay;  //給CCAnimation類擴展的一個方法,經過動畫名稱,幀數和每幀延遲生成一個動畫對象 

@end

在Helper.m裏對擴展的類別方法的實現:

@implementation CCAnimation(Helper)

//使用單個文件生成動畫

+(CCAnimation *)animationWithFile:(NSString *)name frameCount:(int)frameCount delay:(float)delay {

//把動畫幀做爲貼圖進行加載,而後生成精靈動畫幀

NSMutableArray *frames = [NSMutableArray arrayWithCapacity:frameCount];

for (int i = 0; i < frameCount; i++) {

//假設全部動畫幀的名字均爲「名字+數字.png」的格式

NSString *file = [NSString stringWithFormat:@"%@%i.png",name,i];

CCTexture2D *texture = [[CCTextureCache sharedTextureCache] addImage:file];

//假設老是使用整個動畫幀文件

CGSize textureSize = texture.contentSize;

CGRect textureRect = CGRectMake(0, 0, textureSize.width, textureSize.height);

CCSpriteFrame *frame = [CCSpriteFrame frameWithTexture:texture rect:textureRect];

[frames addObject:frame];

}

//使用全部的精靈動畫幀,返回一個動畫對象

return [CCAnimation animationWithFrames:frames delay:delay];

}

@end

這樣咱們就能夠經過單張的圖片來直接造成myBomb和myCoin的精靈動畫了。

 

5. LoadingScene類

因爲在場景之間切換會須要加載資源,消耗時間,所以設置一個載入界面無疑能夠減弱加載過程對用戶體驗的影響。這裏的載入界面,咱們經過LoadingScene類來實現,經過維護這一個界面,便可實現向各個界面的跳轉。首先咱們在LoadingScene.h中聲明一個枚舉類型TargetScenes來列舉咱們須要涉及載入的界面,同時咱們在此保存須要在各個界面間傳遞的變量,如score。另外,聲明一個靜態方法sceneWithTargetScene:以供其餘需載入的場景調用。具體聲明以下:

#import <Foundation/Foundation.h>

#import "cocos2d.h"

#import "GameScene.h"

#import "EndScene.h"

typedef enum {

TargetSceneINVALID = 0,

TargetSceneFirstScene,  //有任何新的須要載入的場景只須要在此增長新的場景枚舉類型就可,如TargetSceneSecondScene。。。

TargetSceneSecondScene,

TargetSceneMAX,

} TargetScenes;

@interface LoadingScene : CCScene {

TargetScenes targetScene_;

int endScore;

}

+(id)sceneWithTargetScene:(TargetScenes)targetScene;

-(id)initWithTargetScene:(TargetScenes)targetScene;

-(void)loadingUpdate:(ccTime)delta;

@end

在LoadingScene.m中,實現sceneWithTargetScene:方法以及LoadingScene的初始化方法。initWithTargetScene:方法中經過在GameScene中設定的靜態方法獲取到當前的GameScene對象。以後經過targetScene_進行條件判斷需載入的目標場景,詳細以下:

+(id)sceneWithTargetScene:(TargetScenes)targetScene {  //此處的targetScene參數即爲想要加載進來的下一個場景

return [[[self alloc] initWithTargetScene:targetScene] autorelease];  //生成一個當前類的自動釋放對象,self便是LoadingScene

}

-(id)initWithTargetScene:(TargetScenes)targetScene {

if (self == [super init]) {

targetScene_ = targetScene;  //將要加載的目標場景給到全局的枚舉變量中,以在update函數中判斷並加載對應的場景

GameScene *tmpScene = [GameScene getShared];

endScore = tmpScene.score;

//生成並添加一個載入的文本標籤

CCLabelTTF *label = [CCLabelTTF labelWithString:@"Loading..." fontName:@"Marker Felt" fontSize:38];

CGSize size = [[CCDirector sharedDirector] winSize];

label.position = CGPointMake(size.width * 0.5f, size.height * 0.5f);

[self addChild:label];

[self schedule:@selector(loadingUpdate:) interval:2.3f];  //必須在下一幀加載目標場景

}

return self;



-(void)loadingUpdate:(ccTime)delta {

[self unscheduleAllSelectors];

switch (targetScene_) {  //經過targetScene_這個枚舉類型決定加載哪一個scene

case TargetSceneFirstScene:

[[CCDirector sharedDirector] replaceScene:[GameScene scene]];

break;

case TargetSceneSecondScene:

[[CCDirector sharedDirector] replaceScene:[EndScene endScene:endScore]];

break;

default:

NSAssert2(nil,@"%@:unsupported TargetScene %i",NSStringFromSelector(_cmd),targetScene_);  //使用未指定的枚舉類型時發出的警告信息

break;

}

}

這樣,咱們就能夠在任意場景中經過CCDirector的replaceScene方法先切換到LoadingScene,再由LoadingScene過渡到目標場景了。

 

6. EndScene類

這個層須要呈現的內容並很少,咱們只是簡單在這裏呈現用戶的得分,並提供用戶一個從新開始的按鈕。這裏的初始化方法須要帶入一個用戶得分的參數,由LoadingScene類傳遞過來:

+(id)endScene:(int)myScore {

return [[[self alloc] initScene:myScore] autorelease];

}

-(id)initScene:(int)myScore {

if (self == [super init]) {

//生成一個分數標籤

CCLabelTTF *label = [CCLabelTTF labelWithString:[NSString stringWithFormat:@"Your Score is: %i",myScore] fontName:@"Marker Felt" fontSize:38];

CGSize size = [[CCDirector sharedDirector] winSize];

label.position = CGPointMake(size.width * 0.5f, size.height * 0.5f);

label.anchorPoint = ccp(0.5f,0);

[self addChild:label];

//生成一個從新開始的按鈕

[CCMenuItemFont setFontName:@"STHeitiJ-Light"];

[CCMenuItemFont setFontSize:26];

CCMenuItemFont *item1 = [CCMenuItemFont itemFromString:@"Retry!" target:self selector:@selector(redirectToGameScene)];

item1.color = ccGREEN;

CCMenu *menu = [CCMenu menuWithItems:item1,nil];

menu.position = CGPointMake(size.width * 0.5f, size.height * 0.5f - 88.0f);

menu.anchorPoint = ccp(0.5f,1.0f);

[self addChild:menu];

}

return self;

}

-(void)redirectToGameScene {

[[CCDirector sharedDirector] replaceScene:[CCTransitionFade transitionWithDuration:1.0f scene:[LoadingScene sceneWithTargetScene:TargetSceneFirstScene]]];  //調用載入頁面

}

到這裏,整個工程就基本上完成了,剩下的就是添加須要的資源文件了,好比:主角、炸彈、金幣等遊戲元素在各類狀態下的圖片,遊戲中用到的音效等等。這部分就再也不羅嗦了,固然是否能設計出引人入勝的圖片和音效是遊戲成敗的關鍵,我一直這麼以爲~真正的獨立遊戲開發者應該是一個全才(像韓國開發亡靈殺手的那位大神),繼續努力~~~加油!node

相關文章
相關標籤/搜索