Cocos2D 2.1: 塔防游戏

来源:互联网 发布:谷嫂淘宝同款排除王 编辑:程序博客网 时间:2024/03/28 21:49

一 、原文

        http://www.raywenderlich.com/37701/how-to-make-a-tower-defense-game-tutorial

二、

    塔防类游戏是iOS中最流行的游戏之一。建造顶级的防御物和看着它消灭一群群的侵略者,让人有难于置信的乐趣。如植物大战僵尸,保卫萝卜等游戏。

    从本文你可以学到:

    *如何通过配置好的时间间隔创建一拨拨的敌人

    *如何使这些敌人根据定义好的路线前进

    *如何在地图上特定的位置创建炮塔

    *如何使炮塔攻击敌人

    *如何可视化地调试路线和炮塔的攻击范围

    最后,你将有一个此类游戏的完整的框架,使你可以通过增加新的类型的炮塔,新的敌人和新的地图,来扩展游戏。

1.概述

   塔防游戏是策略游戏,你可以购买炮塔,将它们放在战略位置来阻止一拨拨试图到达你的基地并破坏它的敌人。

   每拨敌人通常都比上一拨敌人更厉害,移动速度更快,更强的防御能力。

   当你干掉敌人通过了所有的关卡(胜利),或者当有足够多的敌人到达你的基地并摧毁了基地(失败),游戏结束。

   下面是完成的游戏截图:


  在路的两边,有许多可以放置炮塔的平台。玩家只可以购买和放置一定数量的炮塔,由金币数量的多少决定。  敌人从屏幕的左上角出现,沿着绿色的路线到达玩家的基地。

白色圆圈就是炮塔的攻击范围,如果有一个敌人出现在攻击范围内,炮塔就会向敌人开火,直到敌人被消灭或敌人走出攻击范围。


2.资源

  我创建了个初始的项目工程,让你能很快地开始学习。download the starter project

  这个工程是基于Cocos2D的模版创建的,这个工程有一个带文本的层,你不需要这个文本,你将创建自己的界面。

  编译运行这个工程,你可以看到一个黑屏,因为“Hello World”已经被去掉了。只要运行成功,一切已经就绪。

  看下工程的目录结构,你会发现:

   *libs文件夹包含了所有的Cocos2D的文件

   *Resources包含了所有的图片和声音文件

  现在,你可以布置好地图,开始创建炮塔。


3.放置炮塔

a.设置背景图片

   打开HelloWorldLayer.m,修改init函数,在 if 语句里增加代码

  self.touchEnabled =TRUE;//接受触屏事件

  CGSize size = [[CCDirectorsharedDirector]winSize];

 CCSprite *backGround = [CCSpritespriteWithFile:@"bg.png"];

  [self addChild:backGround];

  [backGround setPosition:ccp(size.width/2,size.height/2)];


b.设置炮塔放置点

   炮塔位置由文件TowersPosition.plist定义。

   打开这个文件,你会发现一组包含字典的数组,每个字典代表一个炮塔位置的x,y坐标。

   打开HelloWorldLayer.h,增加保存炮塔位置的数组变量。NSMutableArray * towerBases;

   打开HelloWorldLayer.m,增加新的函数loadTowerPositions,从TowersPostion.plist读取位置信息,并增加位置图片到地图上。

- (void)loadTowerPositions

{

    NSString *plistPath = [[NSBundlemainBundle]pathForResource:@"TowersPosition"ofType:@"plist"];

   NSArray *towerPostions = [NSArrayarrayWithContentsOfFile:plistPath];

    towerBases = [[NSMutableArrayalloc]initWithCapacity:10]; 

   for(NSDictionary *towerPosin towerPostions)

    {

       CCSprite *towerBase = [CCSpritespriteWithFile:@"open_spot.png"];

        [selfaddChild:towerBase];

        [towerBasesetPosition:ccp([[towerPosobjectForKey:@"x"]intValue],

                                   [[towerPosobjectForKey:@"y"]intValue])

         ];

        [towerBasesaddObject:towerBase];

    }

}

  在init函数中增加代码[self loadTowerPositions];


c.增加炮塔类

  打开HelloWorldLayer.h增加属性@property (nonatomic,strong)NSMutableArray* towers;

  打开HelloWorldLayer.m实现属性读写函数@synthesize towers;

  

  实现炮塔类Tower,父类为CCNode。

  Tower.h   

@interface Tower :CCNode {

   int attackRange; //攻击范围

   int damage;      //攻击力

   float fireRate;  //开火频率

}


@property (nonatomic,assign)HelloWorldLayer *theGame;

@property (nonatomic,assign)CCSprite *mySprite;


+(id)nodeWithTheGame:(HelloWorldLayer*)_game location:(CGPoint)location;

-(id)initWithTheGame:(HelloWorldLayer*)_game location:(CGPoint)location;

@end


  Tower.m

@implementation Tower


@synthesize theGame,mySprite;


+(id) nodeWithTheGame:(HelloWorldLayer*)_game location:(CGPoint)location

{

   return [[selfalloc]initWithTheGame:_gamelocation:location];

}

-(id) initWithTheGame:(HelloWorldLayer *)_game location:(CGPoint)location

{

if( (self=[superinit])) {

       theGame = _game;

       attackRange =70;

       damage =10;

       fireRate =1;

        

       mySprite = [CCSpritespriteWithFile:@"tower.png"];

[selfaddChild:mySprite];

        

        [mySpritesetPosition:location];

        

        [theGameaddChild:self];

        

        [self scheduleUpdate];  

return self;

}

-(void)update:(ccTime)dt

{  

}

-(void)draw

{

 //   ccDrawColor4B(255, 255, 255, 255);

//    ccDrawCircle(mySprite.position, attackRange, 360, 30, false);

    [superdraw];

}

@end


d.添加炮塔

  打开HelloWorldLayer.m添加函数

-(BOOL) canBuyTower

{

    return YES;

}

-(void) ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event

{

   for(UITouch *touchin touches)

    {

       CGPoint location = [touchlocationInView:[touchview]];

        location = [[CCDirectorsharedDirector]convertToGL:location];

        for(CCSprite *tbintowerBases)

        {

           if(CGRectContainsPoint([tbboundingBox],location) &&

               [self canBuyTower] && !tb.userData)

            {

               Tower * tower = [TowernodeWithTheGame:selflocation:tb.position];

                [towersaddObject:tower];

                tb.userData = (__bridgevoid *)(tower); //关联炮塔 

            }

        }

    }

}

编译运行程序,可以放置炮塔了。但是若没有敌人,这些炮塔又有什么用呢?


4.路线,敌人,周期

a.路线,增加Waypoint类,路线上的点对象,父类是CCNode。

  Waypoint类主要属性:

 @property (nonatomic,readwrite)CGPoint myPosition;//位置

 @property (nonatomic,assign)Waypoint *nextWaypoint;//下个点

b.给HelloWorldLayer增加属性waypoints,保存路线的关键点

  @property (nonatomic,strong)NSMutableArray* waypoints;

   增加函数addWaypoints,产生敌人行走路线的6个关键点,在init函数调用。

-(void)addWaypoints

{

   waypoints = [[NSMutableArrayalloc]init];

    

   Waypoint * waypoint1 = [WaypointnodeWithTheGame:selflocation:ccp(420,35)];//右下,终点

    [waypointsaddObject:waypoint1];

    

   Waypoint * waypoint2 = [WaypointnodeWithTheGame:selflocation:ccp(35,35)];

    [waypointsaddObject:waypoint2];

    waypoint2.nextWaypoint =waypoint1;

  

   Waypoint * waypoint3 = [WaypointnodeWithTheGame:selflocation:ccp(35,130)];

    [waypointsaddObject:waypoint3];

    waypoint3.nextWaypoint =waypoint2;

   

   Waypoint * waypoint4 = [WaypointnodeWithTheGame:selflocation:ccp(445,130)];

    [waypointsaddObject:waypoint4];

    waypoint4.nextWaypoint =waypoint3;

    

   Waypoint * waypoint5 = [WaypointnodeWithTheGame:selflocation:ccp(445,220)];

    [waypointsaddObject:waypoint5];

    waypoint5.nextWaypoint =waypoint4;

    

   Waypoint * waypoint6 = [WaypointnodeWithTheGame:selflocation:ccp(-40,220)];//左上,起点

    [waypointsaddObject:waypoint6];

    waypoint6.nextWaypoint =waypoint5;

}


c.敌人,增加敌人类Enemy,父类是CCNode。

@interface Enemy :CCNode {

   int maxHp;//最大生命值

   int currentHp;//当前生命值

   BOOL active;//是否活动状态

   float walkingSpeed;//行走速度

   Waypoint *destinationWaypoint;//目标点

   CGPoint myPosition;//当前位置

}

@property (nonatomic,assign)CCSprite *mySprite;//敌人精灵

d.给HelloWorldLayer增加属性enemies,保存敌人

@property (nonatomic,strong)NSMutableArray *enemies;


e.waves.plist定义了三拨敌人,第一拨有6个敌人,第二拨3个,第三拨5个。

  spawnTime定义敌人被激活的时间。

  增加函数loadWave,加载各拨敌人,在init函数调用。

-(BOOL)loadWave {

    NSString* plistPath = [[NSBundlemainBundle]pathForResource:@"Waves"ofType:@"plist"];

   NSArray * waveData = [NSArrayarrayWithContentsOfFile:plistPath];

   if(wave >= [waveDatacount])

    {

       returnNO;

    }

   NSArray * currentWaveData =[NSArrayarrayWithArray:[waveDataobjectAtIndex:wave]];

   for(NSDictionary * enemyDatain currentWaveData)

    {

       Enemy * enemy = [EnemynodeWithTheGame:self];

        [enemiesaddObject:enemy];

        [enemyschedule:@selector(doActivate)

              interval:[[enemyDataobjectForKey:@"spawnTime"]floatValue]];

     }

   wave++;

    return YES;

}


5.炮塔的攻击

  每个炮塔检查看攻击范围有无敌人,如果有,炮塔会开始炮击敌人,直到敌人走出攻击范围或敌人被消灭,然后开始找其他敌人。

a.打开Tower.h

  增加成员变量

 BOOL attaching;

 Enemy *chosenEnemy;

    增加方法声明
    -(void) targetKilled;

b.打开Enemy.h
  增加成员变量

  NSMutableArray *attackedBy;

  增加方法声明  

-(void)getAttacked:(Tower*)attacker;

-(void)getLostSight:(Tower*)attacker;

-(void)getDamaged:(int)damage;


c.打开Tower.m
  增加方法:

-(void)attackEnemy

{

    [selfschedule:@selector(shootWeapon)interval:fireRate];

}

-(void)chosenEnemyForAttack:(Enemy *)enemy

{

    chosenEnemy = nil;

   chosenEnemy = enemy;

    [selfattackEnemy];

    [enemygetAttacked:self];

}

-(void)shootWeapon

{

   CCSprite * bullet = [CCSpritespriteWithFile:@"bullet.png"];

    [theGameaddChild:bullet];

    [bulletsetPosition:mySprite.position];

    [bulletrunAction:[CCSequenceactions:

                       [CCMoveToactionWithDuration:0.1position:chosenEnemy.mySprite.position],

                       [CCCallFuncactionWithTarget:selfselector:@selector(damageEnemy)],

                       [CCCallFuncNactionWithTarget:selfselector:@selector(removeBullet:)],nil]];    

}

-(void)removeBullet:(CCSprite *)bullet

{

    [bullet.parentremoveChild:bulletcleanup:YES];

}

-(void)damageEnemy

{

    [chosenEnemy getDamaged:damage];

}

-(void)targetKilled

{

    if(chosenEnemy)

       chosenEnemy =nil;  

    [selfunschedule:@selector(shootWeapon)];

}

-(void)lostSightOfEnemy

{

    [chosenEnemygetLostSight:self];

    if(chosenEnemy)

       chosenEnemy =nil

    [selfunschedule:@selector(shootWeapon)];

}

  
更新update函数(最重要)

-(void)update:(ccTime)dt

{

    if (chosenEnemy){

        //We make it turn to target the enemy chosen

        CGPoint normalized =ccpNormalize(ccp(chosenEnemy.mySprite.position.x-mySprite.position.x,

                                             chosenEnemy.mySprite.position.y-mySprite.position.y));

       mySprite.rotation =CC_RADIANS_TO_DEGREES(atan2(normalized.y,-normalized.x))+90;

        

        if(![theGamecircle:mySprite.positionwithRadius:attackRange

        collisionWithCircle:chosenEnemy.mySprite.positioncollisionCircleRadius:1])

        {

            [self lostSightOfEnemy];//走出攻击范围

        }

    }else {

       for(Enemy * enemyintheGame.enemies)

        {

            if([theGamecircle:mySprite.positionwithRadius:attackRange

           collisionWithCircle:enemy.mySprite.positioncollisionCircleRadius:1])

            {

                [selfchosenEnemyForAttack:enemy];//锁定目标

               break;

            }

        }

    }

}


d.打开Enemy.m
   在init函数初始化attackedBy(保存攻击它的炮塔们),attackedBy = [[NSMutableArrayalloc]initWithCapacity:5];
   修改getRemoved函数

-(void)getRemoved

{

   for(Tower * attackerinattackedBy)

    {

        [attackertargetKilled];//通知炮塔,已被消灭

    }

    [self.parentremoveChild:selfcleanup:YES];

    [theGame.enemiesremoveObject:self];

    //Notify the game that we killed an enemy so we can check if we can send another wave

    [theGameenemyGotKilled];

}

   增加函数

-(void)getAttacked:(Tower *)attacker

{

    [attackedByaddObject:attacker];

}

-(void)getLostSight:(Tower *)attacker

{

    [attackedByremoveObject:attacker];

}

-(void)getDamaged:(int)damage

{

   currentHp -=damage;

   if(currentHp <=0)

    {

        [selfgetRemoved];

    }

}

e.编译运行程序,你已经可以放置炮塔来消灭敌人了。

6.完善游戏
a.基地(玩家)的命值或游戏的结束条件
   HelloWorldLayer.h增加:

 int playerHp;//命值

 BOOL gameEnded;//标记游戏是否结束

 CCLabelBMFont *ui_hp_lbl;//显示命值的文本

  -(void) doGameOver;//处理游戏结束的函数
 
  init函数中增加:

 playerHp =5;

 ui_hp_lbl = [CCLabelBMFontlabelWithString:[NSStringstringWithFormat:@"HP: %d",playerHp]fntFile:@"font_red.fnt"];

 [selfaddChild:ui_hp_lblz:10];

 [ui_hp_lblsetPosition:ccp(85,size.height-12)];

 注意增加字体资源时,除了font_red.fnt,还有font_red_14.png也要加进去,否则文字无法显示。


  增加函数

-(void)getHpDamage {

    playerHp--;

    [ui_hp_lbl setString:[NSStringstringWithFormat:@"HP: %d",playerHp]];

   if (playerHp <=0) {

        [selfdoGameOver];

    }

}

-(void)doGameOver {

   if (!gameEnded) {

       gameEnded =YES;

        [[CCDirectorsharedDirector]replaceScene:[CCTransitionRotoZoomtransitionWithDuration:1scene:[HelloWorldLayerscene]]];

    }

}


b.显示第几拨敌人
   给HelloWorldLayer增加标签文本
   CCLabelBMFont *ui_wave_lbl;
   在init 函数中初始化
    ui_wave_lbl = [CCLabelBMFontlabelWithString:[NSStringstringWithFormat:@"WAVE: %d",wave]fntFile:@"font_red.fnt"];

  [selfaddChild:ui_wave_lblz:10];

  [ui_wave_lblsetPosition:ccp(400,size.height-12)];

  [ui_wave_lblsetAnchorPoint:ccp(0,0.5)];

     在loadWave的最后增加,这样就可以看到当前是第几拨敌人了。
   [ui_wave_lbl setString:[NSString stringWithFormat:@"WAVE: %d",wave]];

c.金币,购买炮塔需要花费金币
  打开HelloWorldLayer.h,增加
  int playGold;//金币数量

 CCLabelBMFont *ui_gold_lbl;//显示金币数量的文本

   -(void)awardGold:(int)gold;//消灭敌人后奖励金币的函数

 打开HelloWorldLayer.m,增加函数

-(void)awardGold:(int)gold{

   playGold += gold;  

    [ui_gold_lblsetString:[NSStringstringWithFormat:@"Gold: %d",playGold]];

}

 在init函数中增加

    playGold = 1000;

    ui_gold_lbl = [CCLabelBMFontlabelWithString:[NSStringstringWithFormat:@"Gold: %d",playGold]fntFile:@"font_red.fnt"];

    [selfaddChild:ui_gold_lblz:10];

    [ui_gold_lblsetPosition:ccp(135,size.height-12)];

    [ui_gold_lblsetAnchorPoint:ccp(0,0.5)];

 修改canBuyTower函数,kTOWER_COST 在Tower.h中定义#define kTOWER_COST300

-(BOOL) canBuyTower

{

   if(playGold -kTOWER_COST>=0)

       returnYES;

   else

       returnNO;

}

  修改ccTouchesBegan函数,生成新的炮塔后增加代码
  playGold -=kTOWER_COST;

  [ui_gold_lblsetString:[NSStringstringWithFormat:@"GOLD: %d",playGold]];


  打开Enemy.m,修改函数getDamaged,在if语句里面增加代码
  [theGameawardGold:200];


d.音效
   打开HelloWorldLayer.m,添加 #import"SimpleAudioEngine.h"
   在init函数添加背景音乐
   [[SimpleAudioEnginesharedEngine]playBackgroundMusic:@"8bitDungeonLevel.mp3"loop:YES];
  在函数ccTouchesBegan中,生成炮塔对象前添加音效

  [[SimpleAudioEnginesharedEngine]playEffect:@"tower_place.wave"];

  在函数getHpDamage的开始添加音效 

  [[SimpleAudioEnginesharedEngine]playEffect:@"life_lose.wav"];

  

  打开Enemy.m,添加 #import"SimpleAudioEngine.h"

  在函数getDamaged的开始添加音效

  [[SimpleAudioEnginesharedEngine]playEffect:@"laser_shoot.wave"];


7.结束

你可以做的事情还有:

*增加新的敌人类型

*增加新的炮塔类型

*增加多条敌人的行进路线

*拥有不同炮塔位置的不同关卡

 


0 0