如何设计开发iPhone塔防游戏11-火力升级

本文由eseedo(泰然骷髅会成员)翻译,泰然授权转载,转载请通知eseedo(http://blog.sina.com.cn/eseedo)。(-by Iven)

欢迎再次回到iPhone塔防游戏系列教程。在这一部分的教程中,我们添加范围攻击炮塔,炮塔的升级机制,以及敌人的升级。

 

在学习之前,可以从本部分完成的源代码中找到baseAttributes.h和.m文件,并添加到你自己的Xcode项目中。



为了方便学习,这里先提供该部分项目完成后的源代码:

http://www.iphonegametutorials.com/usr/uploads/2012/02/TowerDefensePart8.zip

 

如何设计开发iPhone塔防游戏11-火力升级

1.首先对GameHUD类进行一些调整:

在Xcode中切换到GameHUD.h,并添加以下代码:

 

#import “baseAttributes.h”
BaseAttributes *baseAttributes;

切换到GameHUD.m,在init方法的顶部添加以下代码:

 

baseAttributes = [BaseAttributes sharedAttributes];



然后使用以下代码替换“baseHpPercentage = 100;”

 

baseHp = baseAttributes.baseHealth;
baseHpPercentage = (baseHp/baseAttributes.baseHealth) *100;



删除resources =100;这行语句,在[self addChild:resourceLabel z:1];语句后添加以下代码:

resources = baseAttributes.baseStartingMoney;
[self->resourceLabel setString:[NSString stringWithFormat: @"Money $%i",resources]];

在init方法的最后添加以下代码:

[self schedule:@selector(updateResourcesNom) interval: baseAttributes.baseMoneyRegenRate];

 

同时更改updateResourcesNom方法的代码如下:

-(void) updateResourcesNom{
     [self updateResources:baseAttributes.baseMoneyRegen];
}

注意:

在TutorialScene.m的init方法中删除对updateResourcesNom方法的调用。

 

接下来我们来看看GameHUD.m中的炮塔成本。首先,这款示例游戏中我们只会用到3种炮塔,因此可以从NSArray *images中删除第4种炮塔,然后把第三种炮塔的图片改为”CannonTurret.png”,别忘了把这个图片拖到项目里面去。

使用以下代码替代setCost switch语句:

 

//Set cost values
switch (i) {
     case 0:
          [towerCost setString:[NSString stringWithFormat:@"$ %i", (int) (baseAttributes.baseMGCost*baseAttributes.baseTowerCostPercentage)]];
     break;

     case 1:
          [towerCost setString:[NSString stringWithFormat:@"$ %i",(int) (baseAttributes.baseFCost*baseAttributes.baseTowerCostPercentage)]];
     break;

     case 2:
          [towerCost setString:[NSString stringWithFormat:@"$ %i",(int) (baseAttributes.baseCCost*baseAttributes.baseTowerCostPercentage)]];
     break;

     default:
     break;
}

然后在TutorialScene.m的addTower方法中使用以下语句替代switch语句:

switch (towerTag) {
     case 1:
          if (gameHUD.resources >= (int) (baseAttributes.baseMGCost*baseAttributes.baseTowerCostPercentage)) {
               target = [MachineGunTower tower];
               [gameHUD updateResources:-(int) (baseAttributes.baseMGCost*baseAttributes.baseTowerCostPercentage)];
          }
          else
               return;
          break;

     case 2:
          if (gameHUD.resources >= (int) (baseAttributes.baseFCost*baseAttributes.baseTowerCostPercentage)) {
               target = [FreezeTower tower];
               [gameHUD updateResources:-(int) (baseAttributes.baseFCost*baseAttributes.baseTowerCostPercentage)];
          }
          else
               return;
          break;

     case 3:
          if (gameHUD.resources >= (int) (baseAttributes.baseCCost*baseAttributes.baseTowerCostPercentage)) {
               target = [MachineGunTower tower];//We will change this when we create the CannonTower class.
               [gameHUD updateResources:-(int) (baseAttributes.baseCCost*baseAttributes.baseTowerCostPercentage)];
          }
          else
               return;
          break;

     default:
     break;
}

接下来需要在init方法中添加以下语句:

 

baseAttributes = [BaseAttributes sharedAttributes];

然后切换到TutorialScene.h,添加下面的语句。

 

#import “baseAttributes.h”

BaseAttributes * baseAttributes;

 

编译运行游戏,一切应该都还在正常运转。

 

更多对GameHUD的修改:

 

在GameHUD.m中添加以下方法:

 

-(void) update:(ccTime) dt {
     for (CCSprite *sprite in movableSprites){

          switch (sprite.tag) {
               case 1:
                    if (baseAttributes.baseMGCost*baseAttributes.baseTowerCostPercentage > resources)
                    {
                         sprite.opacity = 50;
                         break;
                    }
                    else
                         sprite.opacity = 255;
                    break;

               case 2:
                    if (baseAttributes.baseFCost*baseAttributes.baseTowerCostPercentage > resources)
                    {
                         sprite.opacity = 50;
                         break;
                    }
                    else
                         sprite.opacity = 255;
                    break;

               case 3:
                    if (baseAttributes.baseCCost*baseAttributes.baseTowerCostPercentage > resources)
                    {
                         sprite.opacity = 50;
                         break;
                    }
                    else
                         sprite.opacity = 255;
                    break;

                    default:
                    break;
         }
     }
}

然后在init方法中添加一行语句调用上面的方法:

 

[self schedule:@selector(update:)];

 

接下来使用下面的 语句来替换ccTouchBegan:

 

- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
     CGPoint touchLocation = [self convertTouchToNodeSpace:touch];

     CCSprite * newSprite = nil;

     for (CCSprite *sprite in movableSprites) {
          if (CGRectContainsPoint(sprite.boundingBox, touchLocation)) {
               if (sprite.opacity == 255) {
                    DataModel *m = [DataModel getModel];
                    m._gestureRecognizer.enabled = NO;
                    selSpriteRange = [CCSprite spriteWithFile:@"Range.png"];

                    switch (sprite.tag) {
                    case 1:
                         selSpriteRange.scale = (baseAttributes.baseMGRange/50);
                    break;

                    case 2:
                         selSpriteRange.scale = (baseAttributes.baseFRange/50);
                    break;

                    case 3:
                         selSpriteRange.scale = (baseAttributes.baseCRange/50);
                    break;

                    default:
                    break;
                    }

                    [self addChild:selSpriteRange z:-1];
                    selSpriteRange.position = sprite.position;

                    newSprite = [CCSprite spriteWithTexture:[sprite texture]]; //sprite;

                    newSprite.position = sprite.position;
                    selSprite = newSprite;
                    selSprite.tag = sprite.tag;

                    [self addChild:newSprite];
               }
               break;
          }
     }
     return YES;
}

以上代码的变化究竟完成了怎样的工作呢?通过上面的代码修改,现在我们可以从baseAttributes.m中设置新的炮塔成本,而不是在TutorialScene.m和GameHUD.m中同时修改。此外,我们还加入了一个缩放因子(baseTowerCostPercentage),当前设置为1,而后续可以通过更改这个参数来让所有的炮塔变得更便宜或者更贵。

 

接下来,当玩家有足够的银子来购买炮塔时,我们会将图片的不透明度设置为完全不透明。然后在ccTouchesBegan方法中我们又完成了两件事,首先在炮塔为完全不透明时允许玩家选择一个炮塔(比如当银子充足时),然后将towerRange这个图片根据每个炮塔类分别进行缩放。(原始文件的直径大小是100,而范围是50)。

 

现在我们已经做好了准备工作,接下来就是对原来的一些类进行修改,让这个游戏真正变得有趣。

首先要修改的是Creep类。

 

在Creep.m中删除以下变量:

float setRedHp = 9;
float setRedSpeed = 6;
float setGreenHp = 18;
float setGreenSpeed = 12;

然后在FastRedCreep中使用以下代码替代刚才的语句:

 

 

BaseAttributes* baseAttributes = [BaseAttributes sharedAttributes];
creep.hp = creep.totalHp = baseAttributes.baseRedCreepHealth;
creep.moveDuration = baseAttributes.baseRedCreepMoveDur;



在StrongGreenCreep中使用以下代码替代刚才的语句:

 

 

BaseAttributes* baseAttributes = [BaseAttributes sharedAttributes];
creep.hp = creep.totalHp = baseAttributes.baseGreenCreepHealth;
creep.moveDuration = baseAttributes.baseGreenCreepMoveDur;



然后在BossBrownCreep中使用以下代码替代刚才的语句:

 

 

BaseAttributes* baseAttributes = [BaseAttributes sharedAttributes];
creep.hp = creep.totalHp = baseAttributes.baseBrownCreepHealth;
creep.moveDuration = baseAttributes.baseBrownCreepMoveDur;



 

好了,现在我们的怪物敌人可以使用BaseAttributes类中所设置的变量了。记住一点,之所以要这样修改代码,是为了游戏的可扩展性。从现在起,所有的游戏变量都可以在BaseAttributes类中修改。这样也方便在游戏过程中或关卡升级时对怪物升级。

 

新一代炮塔!

 

好吧,这样缝缝补补了半天,终于该进入正题了!接下来我们将要实现一个Cannon Tower,一种可以实现溅射伤害的超级大炮!

首先打开源代码,把Tower.h和Tower.m中的内容拷贝到项目中,然后把CannonTurretUpgrade.png, FreezeTurretUpgrade.png 

 和 MachineGunTurretUpgrade.png这几个图片添加到项目中。

 

现在可以花个几分钟时间来仔细看看Tower.m和Tower.h中的代码,这里对其中的几个重点说明一下:

 

1.新增了一个CannonTower类,该类和之前的炮塔类很相似。在父类Tower中我们创建了几个新的变量,然后在每个子类(MachineGunTower, FreezeTower, CannonTower)中我们会使用BaseAttributes类中的数据来设置这些变量。

 

2.升级炮塔需要赚取经验值(在炮塔成功命中怪物后经验值会随之增长)。

3.如果某种炮塔已经达到升级要求,一个用于表示升级就绪的图片会替代原始炮塔图片。

4.在BaseAttributes中可以设置炮塔伤害值,攻击范围,发射频率等参数,并保存在Tower类中。

5.现在可以向Projectile传递发射它的炮塔id信息。

 

 

以上的代码变化非常大,如果一时半会儿你没看懂,别着急,慢慢研究下,知道你确保自己已经看懂。

 

接下来我们需要对Projectile类做一些调整,添加cannonProjectile类,同时允许炮弹保留发射它的炮塔id信息。

 

 

//Projectile.h
     @interface Projectile : CCSprite {
          CCSprite *parentTower;
     }

     @property (nonatomic, assign) CCSprite *parentTower;
     + (id)projectile: (id) sender;
     @end

     @interface IceProjectile : Projectile {
     }
     + (id)projectile: (id) sender;
     @end

     @interface CannonProjectile : Projectile {
     }
     + (id)projectile: (id) sender;
     @end



接下来是更改后的Projectile.m代码:

 

//Projectile.m
#import "Projectile.h"
@implementation Projectile
@synthesize parentTower = parentTower;

+ (id)projectile: (id) sender {
     Projectile *projectile = nil;
     if ((projectile = [[[super alloc]
          initWithFile:@"Projectile.png"] autorelease])) {
               projectile.parentTower = sender;
          }
     return projectile;
}

- (void) dealloc
{
     [super dealloc];
}
@end

@implementation IceProjectile
+ (id)projectile : (id) sender{

     IceProjectile *projectile = nil;
     if ((projectile = [[[super alloc] initWithFile:@"IceProjectile.png"] autorelease])) {
          projectile.parentTower = sender;
     }
     return projectile;
}

- (void) dealloc
{
     [super dealloc];
}
@end

@implementation CannonProjectile
+ (id)projectile : (id) sender{

     CannonProjectile *projectile = nil;
     if ((projectile = [[[super alloc]
          initWithFile:@"CannonProjectile.png"] autorelease])) {
               projectile.parentTower = sender;
     }
     return projectile;
}

- (void) dealloc
{
     [super dealloc];
}
@end

千万别忘了把CannonProjectile.png这个资源图片添加到项目中。

在Projectile类中保存炮塔id的信息非常有用,特别是在获取炮塔经验方面更是不可或缺。

最后别忘了在TutorialScene.m中添加AddTower方法:

 

在switch(towerTag) case3: 中更改方法调用如下:

 

target = [CannonTower tower];

 

编译允许游戏,确保超级大炮在发射和它相匹配的炮弹。

现在还缺点东西,让炮弹爆炸以及让炮塔获取经验值。这两个功能都是在TutorialScene.m的upgrade方法中实现的。

 

找到以下代码:

 

if (CGRectIntersectsRect(projectileRect, targetRect))
{
     //Code
}



然后替代为以下代码:

 

if (CGRectIntersectsRect(projectileRect, targetRect)) {
     [projectilesToDelete addObject:projectile];

     Creep *creep = (Creep *)target;
     Creep *thisCreep;

     Tower *parentTower = (Tower *) projectile.parentTower;
     int thisHitDamage;

     CGRect splashRect;
     switch (projectile.tag) {
     case 1:
          thisHitDamage = (rand()%baseAttributes.baseMGDamageRandom)+parentTower.damageMin;
          creep.hp -= thisHitDamage;
          parentTower.experience += thisHitDamage;
     break;

     case 2:
          thisHitDamage = (rand()%baseAttributes.baseFDamageRandom)+parentTower.damageMin;
          creep.hp -= thisHitDamage;
          parentTower.experience += thisHitDamage;

          id actionFreeze = [CCMoveTo actionWithDuration:parentTower.freezeDur position:creep.position];
          id actionMoveResume = [CCCallFuncN actionWithTarget:self selector:@selector(ResumePath:)];
          [creep stopAllActions];
          [creep runAction:[CCSequence actions:actionFreeze, actionMoveResume, nil]];
     break;

     case 3:
          thisHitDamage = (rand()%baseAttributes.baseFDamageRandom)+parentTower.damageMin;
          parentTower.experience += thisHitDamage;
          splashRect = CGRectMake(projectile.position.x - (parentTower.splashDist), projectile.position.y - (parentTower.splashDist), (parentTower.splashDist*2), (parentTower.splashDist*2));

          for (CCSprite *target in m._targets) {
               CGRect thistargetRect = CGRectMake(target.position.x - (target.contentSize.width/2), target.position.y - (target.contentSize.height/2), target.contentSize.width, target.contentSize.height);
               if (CGRectIntersectsRect(splashRect, thistargetRect)) {
                    thisCreep = (Creep *) target;
                    thisCreep.hp -= thisHitDamage;

                    if (thisCreep.hp <= 0) {
                         [targetsToDelete addObject:target];
                         [gameHUD updateResources: rand()% (baseAttributes.baseMoneyDropped)];

                         [self removeChild:thisCreep.healthBar cleanup:YES];
                    }
               }
          }
     break;

     default:
     break;
     }

     if (creep.hp <= 0) {
          [targetsToDelete addObject:target];
          [gameHUD updateResources: rand()%(baseAttributes.baseMoneyDropped)];
          [self removeChild:creep.healthBar cleanup:YES];
     }
     break;
}

好了,一切都搞定了!那么我们这里做了些什么呢?

 

1.获取发射炮弹的炮塔id,以便访问相关变量。

2.把projectile.tag的if语句替换成了更高效的switch语句。

3.炮击伤害值受一个随机数影响(在BaseAttributes类中为每种炮塔分别设置)。

4.炮塔的经验值随着伤害完成的数量而增加。

5.当怪物被秒的时候,会掉落一个随机的金币值,而金币会被添加到资源中。

6.超级大炮的炮弹会产生溅射伤害。

 

如何设计开发iPhone塔防游戏11-火力升级

 

 

这里需要对溅射伤害多说两句。当超级大炮的炮弹击中怪物时,首先会计算出所产生的伤害值。然后会在炮弹的周围创建一个方框,其边长是splashDistance的两倍。然后会再次遍历所有的怪物,看是否有怪物进入SplashRect的伤害范围内。进入伤害范围的怪物会受到一定的伤害,而当hp<=0的时候则会被删除。

 

编译运行游戏,现在我们的塔防游戏已经有了3个炮塔类,2个怪物类和1个保存游戏变量的类。

 

在下一篇教程中,我们的游戏就会正式终结了,千万不要错过!

 

原文在此:http://www.iphonegametutorials.com/2012/02/16/how-to-build-a-tower-defense-game-for-the-iphone-part-8b-tower-powers-2/

标签: cocos2d教程, 塔防游戏教程

?>