10.炮塔攻击。每座塔进行检查是否有敌人出现在攻击范围之内,如果有的话,对敌人进行开火,直到以下两种情况之一发生:敌人移动出范围;敌人被消灭。那么炮塔就会寻找下一个敌人。打开Tower.h文件,添加以下代码:
|
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文件,添加头文件声明:
|
#include"Enemy.h"
|
在initWithTheGame函数do-while开头,添加如下代码:
|
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条件之后,添加如下代码:
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文件,添加头文件声明:
|
#include"SimpleAudioEngine.h"
|
在init函数,if条件之后,添加如下代码:
|
CocosDenshion::SimpleAudioEngine::sharedEngine()->playBackgroundMusic("8bitDungeonLevel.mp3", true);
|
在ccTouchesBegan函数,添加一个新的Tower对象前,添加如下代码:
|
CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect("tower_place.wav");
|
在getHpDamage函数里,添加如下代码:
|
CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect("life_lose.wav");
|
打开Enemy.cpp文件,添加头文件声明:
|
#include"SimpleAudioEngine.h"
|
在getDamaged函数里,添加如下代码:
|
CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect("laser_shoot.wav");
|
编译运行,现在游戏将有配乐,关闭掉调试绘制后,效果图: