天天看點

手把手教你開發一款IOS飛行射擊遊戲(五)

我們接着上一篇教程,繼續開發我們的遊戲。

本篇教程我們在之前的基礎上添加子彈,然後用之前建立的按鈕控制飛船發射子彈。

首先介紹一下CCSpriteBatchNode類,我們知道,在射擊類遊戲中,我們發射的,敵人發射的子彈有很多很多,相同類型的子彈長得都一樣,也就是使用的是相同的圖檔紋理,正常的情況是,我們發射一顆子彈,程式準備渲染,渲染圖形,子彈消失後釋放資源。這樣在遊戲過程中就會不斷的重複這個過程。那麼我們是否能夠為相同的子彈使用相同的圖檔紋理,這樣在程式的執行過程中隻會有一次準備和釋放過程呢?答案是肯定的。我們使用CCSpriteBatchNode為CCSprite提供相同的紋理,使用CCSpriteBatchNode的前提條件是所有CCSprite都必須與CCSpriteBatchNode使用相同的紋理。

首先我們定義Bullet類,Bullet.h檔案:

#import"CCNode.h"

#import"cocos2d.h"

@interface Bullet: CCSprite{

   CGPoint speed;

   float damage;

   float angle;

   CGPoint targetPos;

}

-(id)initWithFrameName:(NSString*) frameName

              andSpeed:(CGPoint) speed

             andDamage:(float) dmg;

-(void)initByPos:(CGPoint) pos;

-(void)initByPos:(CGPoint) pos

       andTarget:(CGPoint) tp;

-(void)initByPos:(CGPoint) pos

        andAngle:(float) angle;

- (void)recollect;

@property(readonly) float damage;

@end

bullet.m檔案:

#import"Bullet.h"

#import"CommonUtility.h"

#define PI3.141592653

@implementationBullet

-(id)initWithFrameName:(NSString*) frameName

              andSpeed:(CGPoint) s

             andDamage:(float)dmg{

   if (self = [super initWithSpriteFrameName:frameName]) {

       self.anchorPoint = CGPointZero;

       speed = s;

       damage = dmg;

       self.visible = NO;

   }

   return self;

}  //initWithFrameName

-(void)initByPos:(CGPoint)pos{

   self.position = pos;

   self.visible = YES;

   targetPos = CGPointZero;

   [self scheduleUpdate];

}  //initByPos

-(void)initByPos:(CGPoint) pos

       andTarget:(CGPoint) tp{

   self.position = pos;

   self.visible = YES;

   if (self.position.x < tp.x + 100) {

       tp.x = self.position.x - 100;

   }

   targetPos = tp;

   [self scheduleUpdate];

}  //initByPos

-(void)initByPos:(CGPoint) pos

        andAngle:(float) a{

   self.position = pos;

   self.visible = YES;

   angle = a;

   [self scheduleUpdate];

}  //initByPos

-(void)update:(ccTime)delta{

   float xMove = delta * speed.x;

   float yMove = delta * speed.y;

   if (targetPos.x != 0 && targetPos.y != 0 && self.position.x !=targetPos.x) {

       float a = atanf((self.position.y - targetPos.y) / (self.position.x -targetPos.x));

       yMove = xMove * sinf(a);

       xMove = xMove * cosf(a);

   }else if (angle != -1000){

       float a = 2 * PI * angle / 360;

       xMove = delta * speed.x * cosf(a);

       yMove = delta * speed.x * sinf(a);

   }

   CGPoint pos = ccp(self.position.x + xMove, self.position.y + yMove);

   [self setPosition:pos];

   if (CGRectIntersectsRect([self boundingBox], [CommonUtilityutility].screenRect) == NO) {

       [self recollect];

   }

}  //update

- (void)recollect{

   self.visible = NO;

   angle = -1000;

   targetPos = CGPointZero;

   [self unscheduleAllSelectors];

}  //recollect

- (float)damage{

   return damage;

}  //damage

- (void)dealloc{

   [super dealloc];

}  //dealloc

@end

首先解釋一下,我們為什麼要繼承CCSprite呢,一個原因是CCSpriteBatchNode的addChild方法隻能添加CCSprite或者其子類,當然這不是主要原因,另一個原因是子彈類本身也不需要包含太複雜的邏輯,我們可以認為它就是界面的一個小元素。當然,一種更合理的模式是我們的子彈類繼承CCNode,CCSprite作為類的一個屬性,而不是類本身,在利用CCSpriteBatchNode的時候,将子彈類的CCSprite屬性添加到CCSpriteBatchNode中,後面我們在添加敵人的時候用的就是這種模式。

書歸正傳,我們定義了子彈的幾個屬性:速度、傷害(不能是糖衣炮彈)、角度(angle)和目标(target)。這裡解釋下後面兩個屬性,還記得我們的Entity的射擊的三個方法麼:水準射擊,按一定角度射擊,向指定目标射擊,其實我們的飛船隻是将子彈以一定的參數初始化好,然後子彈自己按照參數飛出去直到飛出螢幕或者擊中目标。是以子彈的後兩個屬性就對應了Entity的後兩種射擊方式。

接着我們看一下方法,三種初始化方法,分别對應射擊的三個方法:初始化angle,初始化target,隻初始化基本屬性。在update方法中,根據angle和target是否被初始化來判斷子彈如何飛行,如果子彈飛出螢幕,将其回收(也就是隐藏起來以備下一次利用,這裡注意,我們回收并沒有釋放子彈這個類)。

接着我們定義一個子彈的Cache類,這裡我們考慮一下,一個CCSpriteBatchNode對應一張紋理貼圖,是以我們有兩種方式來處理各種各樣的BulletCache:一種是每個CCSpriteBatchNode處理一個子彈紋理,然後再用一個類管理各種CCSpriteBatchNode;另一種方式是使用TexturePacker将各種子彈組裝成一張大紋理,然後用一個CCSpriteBatchNode來管理。這裡第二種方式的好處是可以把子彈都幾種到一張紋理圖中,但是問題是在擷取某種子彈精靈的時候,要進行更多的計算,代碼的可讀性比較差,是以我們選擇第一種方式來做。除了之前我們做過的兩種子彈,我們又為敵人設計了幾種子彈(雖然還沒有敵人,未雨綢缪嘛),各種子彈圖檔如下:

bigenemybullet.png:

手把手教你開發一款IOS飛行射擊遊戲(五)

bossbullet.png:

手把手教你開發一款IOS飛行射擊遊戲(五)

bullet.png:

手把手教你開發一款IOS飛行射擊遊戲(五)

poweredenemybullet.png:

手把手教你開發一款IOS飛行射擊遊戲(五)

roundBullets.png:

手把手教你開發一款IOS飛行射擊遊戲(五)

smallenemybullet.png:

手把手教你開發一款IOS飛行射擊遊戲(五)

我們先不管敵人的這幾種子彈,我們先把Cache類寫好,後面我們添加敵人的飛船的時候,再把這些子彈和敵人飛船貼圖整合到一起。我們首先添加BulletCache類,繼承自CCNode,BulletCache.h定義如下:

#import<Foundation/Foundation.h>

#import"cocos2d.h"

#import"Bullet.h"

#import"TagDefinitions.h"

@interfaceBulletCache : CCNode{

   int nextIdleBulletCachePosData;

   CCSpriteBatchNode* bulletCache;

}

-(id)initWithFrameName:(NSString*) frameName

   andBulletCacheCount:(int) cacheCount

        andBulletSpeed:(CGPoint) speed

             andDamage:(float) dmg;

-(Bullet*)nextIdleBullet;

-(float)getBulletHitDamageWithinArea:(CGRect) area;

@property(readonly) CCSpriteBatchNode* bullets;

@property intnextIdleBulletCachePos;

@end

BulletCache.m代碼如下:

#import"BulletCache.h"

@implementationBulletCache

-(id)initWithFrameName:(NSString*) frameName

   andBulletCacheCount:(int) cacheCount

        andBulletSpeed:(CGPoint)speed

             andDamage:(float)dmg{

   if (self = [super init]) {

       nextIdleBulletCachePosData = 0;

       CCSpriteFrameCache* frameCache = [CCSpriteFrameCache sharedSpriteFrameCache];

       CCSpriteFrame* frame = [frameCache spriteFrameByName:frameName];

       bulletCache = [CCSpriteBatchNode batchNodeWithTexture:frame.texture];

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

           Bullet* bCache = [[[Bullet alloc]initWithFrameName:frameName andSpeed:speedandDamage:dmg] autorelease];

           [bulletCache addChild:bCache];

       }

   }

   return self;

}  //initWithFrameName

-(Bullet*)nextIdleBullet{

   return [[bulletCache children] objectAtIndex:self.nextIdleBulletCachePos];

}  //nextIdleBullet

-(int)nextIdleBulletCachePos{

   int tempId = nextIdleBulletCachePosData;

   nextIdleBulletCachePosData++;

   if (nextIdleBulletCachePosData >= [[bulletCache children] count]) {

       nextIdleBulletCachePosData = 0;

   }

   return tempId;

}  //nextIdleBulletCachePos

-(void)setNextIdleBulletCachePos:(int)nextIdleBulletCachePos{

   nextIdleBulletCachePosData = nextIdleBulletCachePos;

}  //setNextIdleBulletCachePos

-(CCSpriteBatchNode*)bullets{

   return bulletCache;

}  //bullets

-(float)getBulletHitDamageWithinArea:(CGRect) area{

   float damage = 0;

   for (Bullet* bullet in [bulletCache children]) {

       if (bullet.visible && CGRectIntersectsRect([bullet boundingBox], area)!= NO) {

           damage += bullet.damage;

           [bullet recollect];

       }

   }

   return damage;

}  //getBulletHitDamageWithinArea

- (void)dealloc{

   [super dealloc];

}  //dealloc

@end

屬性nextIdleBulletCachePosData用來記錄下一個未被使用的Cache,這個值随着Cache被使用而增加,當達到Cache的總個數的時候,歸零。初始化方法中,根據子彈屬性和Cache總數,利用一個循環初始化CCSpriteBatchNode。nextIdleBullet傳回下一個未使用的Bullet(由于Bullet是依次取出的,是以實際上就是下一個Bullet作為未使用的Bullet),通過nextIdleBulletCachePos屬性控制這個計數增長。CCSpriteBatchNode需要添加到層中才能被正确顯示(之前在調試程式的時候,沒有把這個節點添加到Layer中,隻是添加到父節點中,結果子彈怎麼也顯示不出來,查資料才知道,坑啊T_T),是以我們添加了一個屬性:bullets,用來傳回CCSpriteBatchNode。getBulletHitDamageWithinArea這個方法簡單說明一下,這個方法用來判斷一共有多少子彈(目前這種Cache類型)擊中指定區域(參數),計算并傳回傷害,然後回收這些擊中這個區域的子彈。這個方法後面我們用來計算傷害。

這樣我們就完成了一種子彈Cache的封裝,接着我們添加一個類管理多種Cache:

BulletCacheManager.h代碼如下

#import"CCNode.h"

#import"TagDefinitions.h"

#import"cocos2d.h"

#import"Bullet.h"

#import"BulletCache.h"

@interfaceBulletCacheManager : CCNode{

   NSMutableDictionary* bulletCacheMapData;

}

+(BulletCacheManager*) sharedBulletCacheManager;

-(Bullet*)getBullet:(BulletTypes) bulletType;

-(void)addBulletCacheWithType:(BulletTypes) bulletType

             bulletCacheCount:(int) bulletCacheCount

              bulletFrameName:(NSString*) bulletFrameName

                  bulletSpeed:(CGPoint) speed

                 bulletDamage:(float) dmg;

-(float)getBulletHitDamageWithinArea:(CGRect) area isPlayerBullet:(BOOL)isPlayerBullet;

@property(readonly) NSMutableDictionary* bulletCacheMap;

@end

BulletCacheManager.m代碼:

#import"BulletCacheManager.h"

@implementationBulletCacheManager

staticBulletCacheManager* sharedBulletCache;

+(BulletCacheManager*) sharedBulletCacheManager{

   if (sharedBulletCache == nil) {

       sharedBulletCache = [[[BulletCacheManager alloc] init] autorelease];

   }

   return sharedBulletCache;

}  //sharedBulletCacheManager

- (id)init{

   if (self = [super init]) {

       bulletCacheMapData = [[NSMutableDictionary dictionaryWithCapacity:5] retain];

       [self addBulletCacheWithType:PoweredBullet bulletCacheCount:100bulletFrameName:@"bullet.png" bulletSpeed:CGPointMake(500, 0)bulletDamage:80];

       [self addBulletCacheWithType:SmallRoundBullet bulletCacheCount:300bulletFrameName:@"roundBullets.png" bulletSpeed:CGPointMake(800, 0)bulletDamage:40];

   }

   return self;

}  //init

-(void)addBulletCacheWithType:(BulletTypes) bulletType

             bulletCacheCount:(int) bulletCacheCount

              bulletFrameName:(NSString*) bulletFrameName

                  bulletSpeed:(CGPoint) speed

                 bulletDamage:(float) dmg{

   BulletCache* bulletCache = [[[BulletCache alloc]initWithFrameName:bulletFrameName andBulletCacheCount:bulletCacheCountandBulletSpeed:speed andDamage:dmg] autorelease];

   [self.bulletCacheMap setObject:bulletCache forKey:[NSNumbernumberWithInt:bulletType]];

   [self addChild:bulletCache];

}  //addBulletCacheWithType

-(Bullet*)getBullet:(BulletTypes) bulletType{

   BulletCache* bulletCache = [self getBulletCache:bulletType];

   return [bulletCache nextIdleBullet];

}  //getBullet

-(BulletCache*)getBulletCache:(BulletTypes) bulletType{

   return [self.bulletCacheMap objectForKey:[NSNumber numberWithInt:bulletType]];

}

-(NSMutableDictionary*)bulletCacheMap{

   return bulletCacheMapData;

}  //bulletCacheMap

-(float)getBulletHitDamageWithinArea:(CGRect) area isPlayerBullet:(BOOL)isPlayerBullet{

   float damage = 0;

   if (isPlayerBullet) {

       damage += [[self getBulletCache:PoweredBullet]getBulletHitDamageWithinArea:area];

       damage += [[self getBulletCache:SmallRoundBullet]getBulletHitDamageWithinArea:area];

   }else{

       damage += [[self getBulletCache:SmallEnemyBullet]getBulletHitDamageWithinArea:area];

       damage += [[self getBulletCache:BigEnemyBullet]getBulletHitDamageWithinArea:area];

       damage += [[self getBulletCache:PoweredEnemyBullet]getBulletHitDamageWithinArea:area];

       damage += [[self getBulletCache:BOSSBullet] getBulletHitDamageWithinArea:area];

   }

   return damage;

}  //isBulletHitArea

- (void)dealloc{

   [bulletCacheMapData removeAllObjects];

   [bulletCacheMapData release];

   [sharedBulletCache release];

   sharedBulletCache = nil;

   [super dealloc];

}  //dealloc

@end

我們定義了一個字典,用來存放不同的子彈Cache。addBulletCacheWithType方法用來添加某種子彈Cache到字典中,getBullet方法用來傳回某種類型的bullet,getBulletCache方法用來擷取CCSpriteBatchNode對象,這個方法用來将CCSpriteBatchNode傳回到CCLayer中,作為CCLayer的child添加,不然bullet就不能正常渲染了。初始化方法初始化了兩種BulletCache,後面添加完敵人的子彈之後,我們會修改這個初始化方法,将敵人的子彈也添加到Cache中。getBulletHitDamageWithinArea利用BulletCache提供的方法,計算某個區域内各種子彈的總傷害,這裡加入了一個BOOL參數,用來判斷是玩家的子彈還是敵人的子彈,我們不希望敵人的子彈打到自己,是以進行一下判斷是有必要的。

好了,準備工作已經就緒了,下面我們就修改Entity的三個射擊方法,代碼如下:

-(void)shootBulletAtPosition:(CGPoint) position

                  bulletType:(BulletTypes) bT{

   BulletCacheManager* bullets = [BulletCacheManager sharedBulletCacheManager];

   Bullet* bullet = [bullets getBullet:bT];

   [bullet initByPos:position];

}  //shootBulletAtPosition

-(void)shootBulletAtPosition:(CGPoint) position

                    atTarget:(CGPoint) target

                  bulletType:(BulletTypes) bT{

   BulletCacheManager* bullets = [BulletCacheManager sharedBulletCacheManager];

   Bullet* bullet = [bullets getBullet:bT];

   [bullet initByPos:position andTarget:target];

}  //shootBulletAtPosition

-(void)shootBulletAtPosition:(CGPoint) position

                     atAngle:(float) angle

                  bulletType:(BulletTypes) bT{

   BulletCacheManager* bullets = [BulletCacheManager sharedBulletCacheManager];

   Bullet* bullet = [bullets getBullet:bT];

   [bullet initByPos:position andAngle:angle];

}  //shootBulletAtPosition

不多做解釋,就是利用Bullet自己的三種構造方法建立子彈就好了。

好了,貌似大功告成了,運作一下,點選發射子彈按鈕,什麼也沒有對麼?這就對了,我們之前說過,必須要把CCSpriteBatchNode添加到目前層,裡面的紋理才能被正确渲染,是以我們在GameLayer的initCaches方法最後加入下面的代碼:

BulletCacheManager*bulletCacheManager = [BulletCacheManager sharedBulletCacheManager];

   [self addChild:bulletCacheManager z:-1 tag:BulletCacheManagerTag];

   for (BulletCache* bulletCache in [[bulletCacheManager bulletCacheMap]objectEnumerator]) {

       [self addChild:bulletCache.bullets];

   }

再運作一下,這回子彈能夠正常發射了,而且比較流暢,效果如下:

手把手教你開發一款IOS飛行射擊遊戲(五)

這一篇教程就到這兒了,下一篇我們繼續添加各種“可怕”的敵人~~