现在的位置: 首页 > 综合 > 正文

cocos2d-x塔防游戏教程(四)

2013年10月16日 ⁄ 综合 ⁄ 共 6999字 ⁄ 字号 评论关闭

10.炮塔攻击。每座塔进行检查是否有敌人出现在攻击范围之内,如果有的话,对敌人进行开火,直到以下两种情况之一发生:敌人移动出范围;敌人被消灭。那么炮塔就会寻找下一个敌人。打开Tower.h文件,添加以下代码:

1
 
class Enemy;

添加以下变量和函数:

 //控制炮塔是否攻击的开关
    bool attacking;
    //选择攻击敌人的对象
    Enemy *chosenEnemy;
    
    //攻击敌人的方法
    void attackEnemy();
    //选择攻击哪个敌人的方法
    void chosenEnemyForAttack(Enemy *enemy);
    //炮塔射击的方法
    void shootWeapon(float dt);
    //移除子弹的方法
    void removeBullet(cocos2d::CCSprite *bullet);
    //敌人受伤的方法
    void damageEnemy();
    //目标敌人死亡的方法
    void targetKilled();
    //敌人走出攻击范围的方法
    void lostSightOfEnemy();

打开Enemy.h文件,添加以下代码:

 //在攻击范围内的炮塔数组
    cocos2d::CCArray *attackedBy;
    //将炮塔添加的攻击的数组中
    void getAttacked(Tower* attacker);
    //当敌人走出炮塔的攻击范围外中,将炮塔从数组中移除
    void gotLostSight(Tower* attacker);
    //敌人收到打击
    void getDamaged(int damage);

打开Tower.cpp文件,添加头文件声明:

1
 
#include"Enemy.h"

initWithTheGame函数do-while开头,添加如下代码:

1
 
chosenEnemy = NULL;

添加以下方法:

void Tower::attackEnemy()
{
    //炮塔 每隔fireRate代表的时间,发起一次攻击
    this->schedule(schedule_selector(Tower::shootWeapon), fireRate);
}

void Tower::chosenEnemyForAttack(Enemy *enemy)
{
    //首先,将之前的敌人接收容器置空
    chosenEnemy = NULL;
    //接收新的敌人
    chosenEnemy = enemy;
    //让炮塔攻击敌人
    this->attackEnemy();
    //调用敌人对象的方法,将当前炮塔传入
    enemy->getAttacked(this);
}

void Tower::shootWeapon(float dt)
{
    //创建子弹对象
    CCSprite *bullet = CCSprite::create("bullet.png");
    //将子弹显示在层上
    _theGame->addChild(bullet);
    //设置子弹显示的位置
    bullet->setPosition(_mySprite->getPosition());
    //子弹调用moveto的动作方法
    CCMoveTo* moveTo = CCMoveTo::create(0.1, chosenEnemy->getMySprite()->getPosition());
    //调用敌人受到伤害的方法
    CCCallFunc* callFunc = CCCallFunc::create(this, callfunc_selector(Tower::damageEnemy));
    //将子弹从视图中移除
    CCCallFuncN* callFuncN = CCCallFuncN::create(this, callfuncN_selector(Tower::removeBullet));
    //让子弹对象执行一个顺序的动作序列,1、移动2、敌人受伤3、子弹移除
    bullet->runAction(CCSequence::create(moveTo,callFunc,callFuncN,NULL));
}

void Tower::removeBullet(CCSprite *bullet)
{
    //将子弹从父视图中移除,getParent()代表父视图
    bullet->getParent()->removeChild(bullet, true);
}

void Tower::damageEnemy()
{
    if (chosenEnemy)
    {
        //调用敌人受到攻击的方法
        chosenEnemy->getDamaged(damage);
    }
}

void Tower::targetKilled()
{
    if (chosenEnemy)
    {
        chosenEnemy = NULL;
    }
    //让炮塔攻击的定时器停止。
    this->unschedule(schedule_selector(Tower::shootWeapon));
}

void Tower::lostSightOfEnemy()
{
    //调用敌人类中,将该炮塔从数组中移除的方法
    chosenEnemy->gotLostSight(this);
    if (chosenEnemy)
    {
        chosenEnemy = NULL;
    }
    //取消执行之前攻击敌人的方法
    this->unschedule(schedule_selector(Tower::shootWeapon));
}

最后,更新update方法为如下:

void Tower::update(float dt)
{
    //只有当敌人对象存在时才执行下面的方法
    if (chosenEnemy)
    {
        //以下内容为计算角度的公式,以后遇到相似问题直接套用公式就OK
        //We make it turn to target the enemy chosen
        //1、得到敌人与炮塔的向量  
        CCPoint normalized = ccpNormalize(ccp(chosenEnemy->getMySprite()->getPosition().x - _mySprite->getPosition().x,chosenEnemy->getMySprite()->getPosition().y - _mySprite->getPosition().y));
        //2、得到tan值
        float angleRadians = atan2(normalized.y, -normalized.x);
  
        //3、得到角度    很多同学可能对下面的加90不理解,这个是根据图片的形状决定,在以后的项目中如何得到类似90这种数值呢?最简单的方法就是试,转角的方向一般会以90为单位,也就是说下面的数字只有4种情况0、90、180、270。试试就OK。
        float angleDegrees = CC_RADIANS_TO_DEGREES(angleRadians)  + 90;
   
        
        _mySprite->setRotation(angleDegrees);
        
        //如果敌人走出了攻击范围
        if (!_theGame->collisionWithCircle(_mySprite->getPosition(), attackRange, chosenEnemy->getMySprite()->getPosition(), 1))
        {
            //让炮塔不再发动攻击
            this->lostSightOfEnemy();
        }
    }
    //对象不存在时执行下面的方法
    else
    {
        
        CCObject *pObject = NULL;
        //遍历敌人数组
        CCARRAY_FOREACH(_theGame->getEnemies(), pObject)
        {
            Enemy *enemy = (Enemy*)pObject;
            //将敌人对象是否在炮塔的攻击范围之内
            if (_theGame->collisionWithCircle(_mySprite->getPosition(), attackRange, enemy->getMySprite()->getPosition(), 1))
            {
                //如果在攻击范围之内将让敌人对象成为受攻击对象
                this->chosenEnemyForAttack(enemy);
                break;
            }           
        }
    }
}

打开Enemy.cpp文件,在initWithTheGame函数开头if条件之后,添加如下代码:

1
2
 
attackedBy = CCArray::createWithCapacity(5);
attackedBy->retain();

getRemoved函数开头,添加如下代码:

 CCObject *pObject = NULL;
    //遍历炮塔对象
    CCARRAY_FOREACH(attackedBy, pObject)
    {
        //取出炮塔对象
        Tower *attacker = (Tower*)pObject;
        //让炮塔停止攻击
        attacker->targetKilled();
    }

添加如下方法:

void Enemy::getAttacked(Tower* attacker)
{
    //添加到正在攻击的炮塔数组中
    attackedBy->addObject(attacker);
}

void Enemy::gotLostSight(Tower* attacker)
{
    //从正在攻击的炮塔数组中移除
    attackedBy->removeObject(attacker);
}

void Enemy::getDamaged(int damage)
{
    //被攻击后减血
    currentHp -= damage;
    if (currentHp <= 0)
    {
        //如果没有血就消灭敌人
        this->getRemoved();
    }
}

代码中最重要的部分是在Tower类的update方法。炮塔不断检查敌人是否在攻击范围内,如果是的话,炮塔将旋转朝向敌人,开火攻击。一个敌人一旦被标记为被攻击,将会调用方法让炮塔以攻击间隔发射子弹。反过来,每个敌人都存储有向其攻击的炮塔列表,所以如果敌人被杀死了,那么炮塔就会被通知停止攻击。编译运行,放置几个炮塔在地图上,将会看到一旦敌人进入炮塔的攻击范围,炮塔就会向它们开火攻击,敌人的血量条就会减少,直到被消灭。如下图所示:


11.显示玩家血量。打开HelloWorldScene.h文件,添加以下代码:

 //玩家的血值
    int playerHp;
    //显示玩家血值的标签
    cocos2d::CCLabelBMFont *ui_hp_lbl;
    //判断游戏是否结束
    bool gameEnded;
    //游戏结束的方法
    void doGameOver();

变量playerHp表示玩家的生命值,CCLabelBMFont对象是一个标签,用来显示生命数值。gameEnded用来表示游戏是否结束。打开HelloWorldScene.cpp文件,在init函数里面,添加如下代码:

 //游戏结束的开关  开始时是关闭的
        gameEnded = false;
        //设置玩家的血值
        playerHp = 5;
        //显示血值的标签
        ui_hp_lbl = CCLabelBMFont::create(CCString::createWithFormat("HP: %d", playerHp)->getCString(),"font_red_14.fnt");
        //将标签显示的游戏层上
        this->addChild(ui_hp_lbl, 10);
        //设置标签的位置
        ui_hp_lbl->setPosition(ccp(35, wins.height - 12));

添加如下方法:

void HelloWorld::getHpDamage()
{
    //玩家受到攻击 血值减一
    playerHp--;
    //标签显示的值改变
    ui_hp_lbl->setString(CCString::createWithFormat("HP: %d", playerHp)->getCString());
    //判断玩家的血值
    if (playerHp <= 0)
    {
        //开启游戏结束的开关
        gameEnded = true;
        //执行游戏结束的方法
        this->doGameOver();
    }
}

void HelloWorld::doGameOver()
{
    
    if (gameEnded)
    {
        //关上游戏结束的开关
        gameEnded = false;
        //场景切换
        CCDirector::sharedDirector()->replaceScene(CCTransitionRotoZoom::create(1, HelloWorld::scene()));
    }
}

添加的方法为减少玩家生命值,更新标签,并检查玩家生命是否耗尽,如果是的话,游戏就结束了。当敌人到达基地的时候,getHpDamage方法被调用。编译运行,让敌人到达基地,你将会看到玩家的生命在减少,直到游戏失败。如下图所示:


12.限制金币供应量。大多数游戏都实现了“零和”功能,建造每座炮塔需要一定的资源,并给玩家有限的资源进行分配。打开HelloWorldScene.h文件,添加如下代码:

    //金币的数量
    int playerGold;
    //显示金币数量的标签
    cocos2d::CCLabelBMFont *ui_gold_lbl;
    //调整金币的方法
    void awardGold(int gold);

就像显示生命数值一样,一个变量表示玩家的金币数,一个标签对象显示金币数值。打开HelloWorldScene.cpp文件,在init函数里面,添加如下代码:

//设置初始金币数量
        playerGold = 1000;
        //显示金币数量
        ui_gold_lbl = CCLabelBMFont::create(CCString::createWithFormat("GOLD: %d", playerGold)->getCString(),"font_red_14.fnt");
        //显示标签
        this->addChild(ui_gold_lbl, 10);
        //设置标签的坐标点
        ui_gold_lbl->setPosition(ccp(175, wins.height - 12));

添加如下方法:

void HelloWorld::awardGold(int gold)
{
    //改变金币数量
    playerGold += gold;
    //改变后重新显示金币的方法
    ui_gold_lbl->setString(CCString::createWithFormat("GOLD: %d", playerGold)->getCString());
}

替换canBuyTower方法,代码如下:

bool HelloWorld::canBuyTower()
{
    //判断金币是否购买炮塔
    if (playerGold - kTOWER_COST >= 0)
    {
        return true;
    }
    //如果不够返回假
    return false;
}

ccTouchesBegan函数里面,语句//We
will spend our gold later.
的后面,添加如下代码:

//We will spend our gold later.
                //减少金币
                playerGold -= kTOWER_COST;
                //重新显示金币的数量
                ui_gold_lbl->setString(CCString::createWithFormat("GOLD: %d", playerGold)->getCString());

上述的代码在玩家尝试放置炮塔时,检查是否有足够的金币。如果足够的话,炮塔就会放置上去,并从玩家的金币数中减去炮塔的费用。每次杀死敌人的时候也应该奖励玩家一些金币。打开Enemy.cpp文件,在getDamaged函数里面,if条件后面,添加如下语句:

  //每消灭一个敌人增加200个金币
        _theGame->awardGold(200);

编译运行,会看到不能随意的放置炮塔了,因为每个炮塔都要花费金币。当然,杀死敌人就可以获得金币奖励,这样就可以继续购买炮塔。这是一个很好的系统。如下图所示:


13.加入背景音乐和音效。打开HelloWorldScene.cpp文件,添加头文件声明:

1
 
#include"SimpleAudioEngine.h"

init函数,if条件之后,添加如下代码:

1
 
CocosDenshion::SimpleAudioEngine::sharedEngine()->playBackgroundMusic("8bitDungeonLevel.mp3"true);

ccTouchesBegan函数,添加一个新的Tower对象前,添加如下代码:

1
 
CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect("tower_place.wav");

getHpDamage函数里,添加如下代码:

1
 
CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect("life_lose.wav");

打开Enemy.cpp文件,添加头文件声明:

1
 
#include"SimpleAudioEngine.h"

getDamaged函数里,添加如下代码:

1
 
CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect("laser_shoot.wav");

编译运行,现在游戏将有配乐,关闭掉调试绘制后,效果图:

代码下载: http://vdisk.weibo.com/s/BDn59yfnBVwYa  

抱歉!评论已关闭.