关闭

手把手教你开发一款IOS飞行射击游戏(五)

标签: IOScocos2d游戏
196人阅读 评论(0) 收藏 举报
分类:

我们接着上一篇教程,继续开发我们的游戏。

本篇教程我们在之前的基础上添加子弹,然后用之前创建的按钮控制飞船发射子弹。

首先介绍一下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飞行射击游戏(五) - Daniel - KHome

bossbullet.png:

手把手教你开发一款IOS飞行射击游戏(五) - Daniel - KHome

bullet.png:

手把手教你开发一款IOS飞行射击游戏(五) - Daniel - KHome

poweredenemybullet.png:

手把手教你开发一款IOS飞行射击游戏(五) - Daniel - KHome

roundBullets.png:

手把手教你开发一款IOS飞行射击游戏(五) - Daniel - KHome

smallenemybullet.png:

手把手教你开发一款IOS飞行射击游戏(五) - Daniel - KHome

我们先不管敌人的这几种子弹,我们先把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飞行射击游戏(五) - Daniel - KHome

这一篇教程就到这儿了,下一篇我们继续添加各种“可怕”的敌人~~

85
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场