本文实践自 Pablo Ruiz 的文章《How To Make a Tower Defense Game》,文中使用Cocos2D,我在这里使用Cocos2d-x 3.0alpha0进行学习和移植。在这篇文章,将会学习到如何制作一个塔防游戏。在这当中,学习如何在设定的时间内出现一波波的敌人,使这些敌人沿着指定的路点前进,如何在地图上指定的位置创建炮塔,如何使炮塔射击敌人,如何可视化调试路点和炮塔的攻击范围。

步骤如下:
1.新建Cocos2d-win32工程,工程名为"TowerDefense",去除"Box2D"选项,勾选"Simple Audio Engine in Cocos Denshion"选项;
2.下载本游戏所需的资源,将资源放置"Resources"目录下;

3.为场景添加背景图片。打开HelloWorldScene.cpp文件,修改init函数,如下:

bool HelloWorld::init()
{bool bRet = false;do {CC_BREAK_IF(!Layer::init());this->setTouchEnabled(true);Size winSize = Director::getInstance()->getWinSize();Sprite* background = Sprite::create("Bg.png");this->addChild(background);background->setPosition(Point(winSize.width / 2, winSize.height / 2));bRet = true;} while (0);return bRet;
}

通过放置的背景图片,可以直观的看出哪些地方允许玩家放置炮塔。编译运行,如下图所示:

4.接着,需要沿路设置一些点,在这些点上能够让玩家触摸和建立炮塔。为了方便管理,使用.plist文件来存储炮塔的放置点,这样就可以很容易的改变它们。TowersPosition.plist已经在资源文件夹中,其中已经有了一些炮塔的位置。查看这个文件,可以看到一个字典数组,字典只包含两个键"x"和"y"。每个字典条目代表一个炮塔位置的x和y坐标。现在需要读取这个文件,并且放置塔基到地图上。打开HelloWorldScene.h文件,添加以下变量:

    Array* towerBases;void loadTowerPositions();

打开 HelloWorldScene.cpp 文件,添加如下方法:

void HelloWorld::loadTowerPositions()
{Array* towerPositions = Array::createWithContentsOfFile("TowersPosition.plist");towerBases = Array::create();towerBases->retain();Object* pObject = NULL;CCARRAY_FOREACH(towerPositions, pObject){Dictionary* towerPos = (Dictionary*)pObject;Sprite* towerBase = Sprite::create("open_spot.png");towerBase->setScale(2);this->cocos2d::Node::addChild(towerBase);towerBase->setPosition(Point(((String*)towerPos->objectForKey("x"))->intValue(), ((String*)towerPos->objectForKey("y"))->intValue()) * 2);towerBases->addObject(towerBase);}
}

在 init 函数里面,添加背景图片代码之后,添加如下代码:

        this->loadTowerPositions();

在析构函数里面,添加如下代码:

HelloWorld::~HelloWorld()
{towerBases->release();
}

编译运行,就可以看到道路两侧的方块,这些是做为玩家炮塔的基座。如下图所示:


5.开始建立炮塔。打开HelloWorldScene.h文件,添加如下代码:

    CC_SYNTHESIZE_RETAIN(Array*, _towers, Towers);

添加 Tower 类,派生自 CCNode 类, Tower.h 文件代码如下:

#include "cocos2d.h"
#include "HelloWorldScene.h"USING_NS_CC;#define kTOWER_COST 300class Tower:public Node {public:Tower();~Tower();static Tower* nodeWithTheGame(HelloWorld* game,Point location);bool initWithTheGame(HelloWorld* game,Point location);void update(float delta);void draw();CC_SYNTHESIZE(HelloWorld*, _theGame, TheGame);CC_SYNTHESIZE(Sprite*, _mySprite, MySprite);private:int attackRange;int damage;float fireRate;};

打开Tower.cpp文件,代码如下:

//
//  Tower.cpp
//  HelloCpp
//
//  Created by 杜甲 on 13-11-30.
//
//#include "Tower.h"Tower::Tower()
{}Tower::~Tower()
{}Tower* Tower::nodeWithTheGame(HelloWorld *game, cocos2d::Point location)
{Tower* pRet = new Tower();if (pRet && pRet->initWithTheGame(game, location)) {return pRet;}else{CC_SAFE_RELEASE_NULL(pRet);return pRet;}
}bool Tower::initWithTheGame(HelloWorld *game, cocos2d::Point location)
{bool bRet = false;do {attackRange = 70;damage = 10;fireRate = 1;_mySprite = Sprite::create("tower.png");_mySprite->setScale(2);this->addChild(_mySprite);_mySprite->setPosition(location);_theGame = game;_theGame->addChild(this);this->scheduleUpdate();bRet = true;} while (0);return bRet;
}void Tower::update(float delta)
{}void Tower::draw()
{Node::draw();
#ifdef COCOS2D_DEBUGDrawPrimitives::setDrawColor4F(255.0f, 255.0f, 255.0f, 255.0f);DrawPrimitives::drawCircle(_mySprite->getPosition(), attackRange, 360, 30, false);#endif}

这个 Tower 类包含几个属性:一个精灵对象,这是炮塔的可视化表现;一个父层的引用,方便访问父层;还有三个变量:

  • attackRange: 炮塔可以攻击敌人的距离。

  • damage: 炮塔对敌人造成的伤害值。

  • fireRate: 炮塔再次攻击敌人的时间间隔。

有了这三个变量,就可以创建各种不同攻击属性的炮塔,比如需要很长时间来重新加载的远程重击,或者范围有限的快速攻击。最后,代码中的draw方法,用于在炮塔周围绘制一个圆,以显示出它的攻击范围,这将方便调试。
6.让玩家添加炮塔。打开HelloWorldScene.cpp文件,加入以下头文件声明:

#include "Tower.h"

在析构函数中添加如下代码:

    _towers->release();

在 init 函数,添加如下代码:

        _towers = Array::create();_towers->retain();this->setTouchEnabled(true);

添加如下两个方法,代码如下:

bool HelloWorld::canBuyTower()
{return true;
}void HelloWorld::onTouchesBegan(const std::vector<Touch *> &touches, cocos2d::Event *event)
{Touch* pTouch = touches.front();Point location = pTouch->getLocation();Object* pObject = NULL;CCARRAY_FOREACH(towerBases, pObject){Sprite* tb = (Sprite*)pObject;if (this->canBuyTower() && tb->getBoundingBox().containsPoint(location) && !tb->getUserData()) {Tower* tower = Tower::nodeWithTheGame(this, tb->getPosition());_towers->addObject(tower);tb->setUserData(tower);}}
}

方法 ccTouchesBegan 检测当用户触摸屏幕上任何点时,遍历 towerBases 数组,检查触摸点是否包含在任何一个塔基上。不过在创建炮塔前,还有两件事需要检查:
①玩家是否买得起炮塔?canBuyTower方法用来检查玩家是否有足够的金币来购买炮塔。在这里先假设玩家有很多金币,方法返回true。
②玩家是否违法了建筑规则?如果tb的UserData已经设置了,那么这个塔基已经有了炮塔,不能再添加一个新的了。
如果一切检查都通过,那么就创建一个新的炮塔,放置在塔基上,并将它添加到炮塔数组中。编译运行,触摸塔基,就可以看到炮塔放置上去了,并且它的周围还有白色的圆圈显示攻击范围,如下图所示:

7.添加路点。敌人将会沿着一系列的路点前进,这些简单相互连接的点构成了一条路径,敌人在这条路径上进行行走。敌人会出现在第一个路点,搜寻列表中的下一个路点,移动到那个位置,重复这个过程,直到他们到达列表中的最后一个路点——玩家基地。如果被敌人到达基地,那么玩家就会受到损害。添加Waypoint类,派生自CCNode类,Waypoint.h文件代码如下:

//
//  Waypoint.h
//  HelloCpp
//
//  Created by 杜甲 on 13-11-30.
//
//#ifndef __HelloCpp__Waypoint__
#define __HelloCpp__Waypoint__#include "cocos2d.h"
#include "HelloWorldScene.h"
USING_NS_CC;class Waypoint:public Node {public:Waypoint();~Waypoint();static Waypoint* nodeWithTheGame(HelloWorld* game,Point locaiton);bool initWithTheGame(HelloWorld* game,Point location);void draw();CC_SYNTHESIZE(Point, _myPosition, MyPosition);CC_SYNTHESIZE(Waypoint*, _nextWaypoint, NextWaypoint);
private:HelloWorld* theGame;};#endif /* defined(__HelloCpp__Waypoint__) */

打开 Waypoint.cpp 文件,代码如下:

//
//  Waypoint.cpp
//  HelloCpp
//
//  Created by 杜甲 on 13-11-30.
//
//#include "Waypoint.h"
USING_NS_CC;Waypoint::Waypoint()
{_nextWaypoint = NULL;
}Waypoint::~Waypoint()
{}Waypoint* Waypoint::nodeWithTheGame(HelloWorld *game, cocos2d::Point locaiton)
{Waypoint* pRet = new Waypoint();if (pRet && pRet->initWithTheGame(game, locaiton)) {}else{CC_SAFE_RELEASE_NULL(pRet);}return pRet;
}bool Waypoint::initWithTheGame(HelloWorld *game, cocos2d::Point location)
{bool bRet = false;do {theGame = game;_myPosition = location;this->setPosition(Point::ZERO);theGame->addChild(this);bRet = true;} while (0);return bRet;
}void Waypoint::draw()
{Node::draw();#ifdef COCOS2D_DEBUGDrawPrimitives::setDrawColor4F(0.0f, 255.0f, 0, 255.0f);DrawPrimitives::drawCircle(_myPosition, 6, 360, 30, false);DrawPrimitives::drawCircle(_myPosition, 2, 360, 30, false);if (_nextWaypoint) {DrawPrimitives::drawLine(_myPosition, _nextWaypoint->_myPosition);}
#endif
}

首先,通过传入的HelloWorld对象引用和路点位置坐标,进行初始化一个waypoint对象。每个路点都包含下一个路点的引用,这将会创建一个路点链接列表。每个路点知道列表中的下一个路点。通过这种方式,可以引导敌人沿着链表上的路点到达他们的最终目的地。最后,draw方法绘制显示路点的位置,并且绘制一条直线将其与下一个路点进行连接,这仅仅用于调试目的。

8.创建路点列表。打开HelloWorldScene.h文件,添加以下代码:

  CC_SYNTHESIZE_RETAIN(Array* , _waypoints, Waypoints);void addWaypoints();

打开 HelloWorldScene.cpp 文件,加入以下头文件声明:

#include "Waypoint.h"

在析构函数中添加如下代码:

    _waypoints->release();

添加以下方法:

void HelloWorld::addWaypoints()
{_waypoints = Array::create();_waypoints->retain();Waypoint* waypoint1 = Waypoint::nodeWithTheGame(this, Point(840, 70));_waypoints->addObject(waypoint1);Waypoint* waypoint2 = Waypoint::nodeWithTheGame(this, Point(70, 70));_waypoints->addObject(waypoint2);waypoint2->setNextWaypoint(waypoint1);Waypoint* waypoint3 = Waypoint::nodeWithTheGame(this, Point(70, 260));_waypoints->addObject(waypoint3);waypoint3->setNextWaypoint(waypoint2);Waypoint* waypoint4 = Waypoint::nodeWithTheGame(this, Point(890, 260));_waypoints->addObject(waypoint4);waypoint4->setNextWaypoint(waypoint3);Waypoint* waypoint5 = Waypoint::nodeWithTheGame(this, Point(890, 440));_waypoints->addObject(waypoint5);waypoint5->setNextWaypoint(waypoint4);Waypoint* waypoint6 = Waypoint::nodeWithTheGame(this, Point(-80, 440));_waypoints->addObject(waypoint6);waypoint6->setNextWaypoint(waypoint5);}

在 init 函数,添加如下代码:

        this->addWaypoints();

编译运行,效果如下图所示:


在地图上有6个路点,这是敌人的行走路线。在让敌人出现在游戏中前,还需要添加一个辅助方法。打开HelloWorldScene.cpp文件,添加方法如下:

bool HelloWorld::collisionWithCircle(Point circlePoint, float radius, Point circlePointTwo, float radiusTwo)
{float xdif = circlePoint.x - circlePointTwo.x;float ydif = circlePoint.y - circlePointTwo.y;float distance = sqrt(xdif * xdif + ydif * ydif);if (distance <= radius + radiusTwo) {return true;}else{return false;}
}

方法 collisionWithCircle 用于判断两个圆是否碰撞或者相交。这将用于判断敌人是否到达一个路点,同时也可以检测敌人是否在炮塔的攻击范围之内。
9.添加敌人。打开 HelloWorldScene.h 文件,添加以下代码:

    CC_SYNTHESIZE_RETAIN(Array*, _enemies, Enemies);int wave;LabelBMFont* ui_wave_lbl;

打开 HelloWorldScene.cpp 文件,在析构函数里,添加如下代码:

    _enemies->release();

添加 Enemy 类,派生自 CCNode 类, Enemy.h 文件代码如下:

#include "cocos2d.h"
#include "HelloWorldScene.h"
#include "Waypoint.h"class Enemy:public Node {public:Enemy();~Enemy();static Enemy* nodeWithTheGame(HelloWorld* game);bool initWithTheGame(HelloWorld* game);void doActivate(float dt);void getRemoved();void update(float delta);void draw();CC_SYNTHESIZE(HelloWorld*, _theGame, TheGame);CC_SYNTHESIZE(Sprite*, _mySprite, MySprite);private:Point myPosition;int maxHp;int currentHp;float walkingSpeed;Waypoint* destinationWaypoint;bool active;
};

打开 Enemy.cpp 文件,代码如下:

//
//  Enemy.cpp
//  HelloCpp
//
//  Created by 杜甲 on 13-11-30.
//
//#include "Enemy.h"#define HEALTH_BAR_WIDTH 40
#define HEALTH_BAR_ORIGIN -20Enemy::Enemy()
{}
Enemy::~Enemy()
{}Enemy* Enemy::nodeWithTheGame(HelloWorld *game)
{Enemy* pRet = new Enemy();if (pRet && pRet->initWithTheGame(game)) {}else{CC_SAFE_RELEASE_NULL(pRet);}return pRet;
}bool Enemy::initWithTheGame(HelloWorld *game)
{bool bRet = false;do {maxHp = 40;currentHp = maxHp;active = false;walkingSpeed = 1.5f;_theGame = game;_mySprite = Sprite::create("enemy.png");_mySprite->setScale(2);this->addChild(_mySprite);Waypoint* waypoint = (Waypoint*)_theGame->getWaypoints()->getObjectAtIndex(_theGame->getWaypoints()->count() - 1);destinationWaypoint = waypoint->getNextWaypoint();Point pos = waypoint->getMyPosition();myPosition = pos;_mySprite->setPosition(pos);_theGame->addChild(this);this->scheduleUpdate();bRet = true;} while (0);return bRet;
}void Enemy::doActivate(float dt)
{active = true;
}void Enemy::getRemoved()
{this->getParent()->removeChild(this);_theGame->getEnemies()->removeObject(this);// _theGame->enemyGotKilled();
}void Enemy::update(float delta)
{if (!active) {return;}if (_theGame->collisionWithCircle(myPosition, 1, destinationWaypoint->getMyPosition(), 1)) {if (destinationWaypoint->getNextWaypoint()) {destinationWaypoint = destinationWaypoint->getNextWaypoint();}else{_theGame->getHpDamage();this->getRemoved();}}Point targetPoint = destinationWaypoint->getMyPosition();float movementSpeed = walkingSpeed;Point normalized = Point(targetPoint.x - myPosition.x, targetPoint.y - myPosition.y).normalize();_mySprite->setRotation(CC_RADIANS_TO_DEGREES(atan2(normalized.y, -normalized.x)));myPosition = Point(myPosition.x + normalized.x * movementSpeed, myPosition.y + normalized.y * movementSpeed );_mySprite->setPosition(myPosition);}void Enemy::draw()
{Node::draw();Point  healthBarBack[] = {Point(_mySprite->getPosition().x - 20, _mySprite->getPosition().y + 32),Point(_mySprite->getPosition().x + 20, _mySprite->getPosition().y + 32),Point(_mySprite->getPosition().x + 20, _mySprite->getPosition().y + 28),Point(_mySprite->getPosition().x - 20, _mySprite->getPosition().y + 28)};DrawPrimitives::drawSolidPoly(healthBarBack, 4, Color4F(255, 0, 0, 255));Point healthBar[] = {Point(_mySprite->getPosition().x + HEALTH_BAR_ORIGIN, _mySprite->getPosition().y + 32),Point(_mySprite->getPosition().x + HEALTH_BAR_ORIGIN + (float)(currentHp * HEALTH_BAR_WIDTH) / maxHp, _mySprite->getPosition().y + 32),Point(_mySprite->getPosition().x + HEALTH_BAR_ORIGIN + (float)(currentHp * HEALTH_BAR_WIDTH) / maxHp, _mySprite->getPosition().y + 28),Point(_mySprite->getPosition().x + HEALTH_BAR_ORIGIN , _mySprite->getPosition().y + 28)};DrawPrimitives::drawSolidPoly(healthBar, 4, Color4F(0, 255, 0, 255));
}

首先,通过传递一个HelloWorld对象引用进行初始化。在初始化函数里面,对一些重要的变量进行设置:

  • maxHP: 敌人的生命值。

  • walkingSpeed: 敌人的移动速度。

  • mySprite: 存储敌人的可视化表现。

  • destinationWaypoint: 存储下一个路点的引用。

update方法每帧都会被调用,它首先通过collisionWithCircle方法检查是否到达了目的路点。如果到达了,则前进到下一个路点,直到敌人到达终点,玩家也就受到伤害。接着,它根据敌人的行走速度,沿着一条直线移动精灵到达下一个路点。它通过以下算法:
①计算出从当前位置到目标位置的向量,然后将其长度设置为1(向量标准化)
②将移动速度乘以标准化向量,得到移动的距离,将它与当前坐标进行相加,得到新的坐标位置。
最后,draw方法在精灵上面简单的实现了一条血量条。它首先绘制一个红色背景,然后根据敌人的当前生命值用绿色进行覆盖血量条。
10.显示敌人。打开HelloWorldScene.cpp文件,添加头文件声明:

#include "Enemy.h"

添加如下方法:

bool HelloWorld::loadWave()
{Array* waveData = Array::createWithContentsOfFile("Waves.plist");if (wave >= waveData->count()) {return false;}Array* currentWaveData = (Array*)waveData->getObjectAtIndex(wave);Object* pObject = NULL;CCARRAY_FOREACH(currentWaveData, pObject){Dictionary* enemyData = (Dictionary*)pObject;Enemy* enemy = Enemy::nodeWithTheGame(this);_enemies->addObject(enemy);enemy->schedule(schedule_selector(Enemy::doActivate), ((String*)enemyData->objectForKey("spawnTime"))->floatValue());}wave++;ui_wave_lbl->setString(String::createWithFormat("WAVE:%d",wave)->getCString());return true;}void HelloWorld::enemyGotKilled()
{if (_enemies->count() <= 0) {if (!this->loadWave()) {Director::getInstance()->replaceScene(TransitionSplitCols::create(1, HelloWorld::createScene()));}}
}void HelloWorld::getHpDamage()
{}

在init函数里面,添加如下代码:

        wave = 0;ui_wave_lbl = LabelBMFont::create(String::createWithFormat("WAVE:%d",wave)->getCString(), "font_red_14.fnt");ui_wave_lbl->setScale(2);this->addChild(ui_wave_lbl, 10);ui_wave_lbl->setPosition(Point(400, winSize.height - 12));ui_wave_lbl->setAnchorPoint(Point(0, 0.5));_enemies = Array::create();_enemies->retain();this->loadWave();

现在对上面的代码进行一些解释。最重要的部分是 loadWave 方法,它从 Waves.plist 文件读取数据。查看这个文件,可以看到它包含了3个数组,每个数组代表着一波敌人。第一个数组包含6个字典,每个字典定义了一个敌人。在本篇文章中,这个字典仅存储敌人的出现时间,但是也可用于定义敌人类型或者其他特殊属性,以区分不同的敌人。 loadWave 方法检查下一波应出现的敌人,根据波信息创建相应的敌人,并安排它们在规定的时间出现在屏幕上。 enemyGotKilled 方法检查当前屏幕上的敌人数量,如果已经没有敌人的话,那么就让下一波敌人出现。之后,还使用这个方法来判断玩家是否赢得了游戏。编译运行,敌人正向玩家基地前进,如下图所示:

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

class Enemy;

添加以下变量:

    bool attacking;Enemy* chosenEnemy;

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

#include "Enemy.h"

在 initWithTheGame 函数开头if条件之后,添加如下代码:

        chosenEnemy = NULL;

添加以下方法:

void Tower::attackEnemy()
{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 delta)
{Sprite* bullet = Sprite::create("bullet.png");_theGame->addChild(bullet);bullet->setPosition(_mySprite->getPosition());bullet->runAction(Sequence::create(MoveTo::create(0.1f, chosenEnemy->getMySprite()->getPosition()),CallFunc::create(std::bind(&Tower::damageEnemy, this)),CallFuncN::create(std::bind(&Tower::removeBullet, this,bullet)),NULL));
}void Tower::removeBullet(Sprite* bullet)
{bullet->getParent()->removeChild(bullet);
}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 delta)
{if (chosenEnemy) {Point normalized = Point(chosenEnemy->getMySprite()->getPosition().x - _mySprite->getPosition().x, chosenEnemy->getMySprite()->getPosition().y - _mySprite->getPosition().y).normalize();_mySprite->setRotation(CC_RADIANS_TO_DEGREES(atan2(normalized.y, -normalized.x)) + 90);if (!_theGame->collisionWithCircle(_mySprite->getPosition(), attackRange, chosenEnemy->getMySprite()->getPosition(), 1)) {this->lostSightOfEnemy();}}else{Object* 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.h 文件,添加以下代码:

    Array* attackedBy;

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

        attackedBy = Array::create();attackedBy->retain();

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

    Object* 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();_theGame->awardGold(200);}
}

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

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

        gameEnded = false;playerHp = 5;ui_hp_lbl = LabelBMFont::create(String::createWithFormat("HP: %d",playerHp)->getCString(), "font_red_14.fnt");ui_hp_lbl->setScale(2);this->addChild(ui_hp_lbl,10);ui_hp_lbl->setPosition(Point(35, winSize.height - 12));

添加如下方法:

void HelloWorld::getHpDamage()
{playerHp--;ui_hp_lbl->setString(String::createWithFormat("HP:%d",playerHp)->getCString());if (playerHp <= 0) {this->doGameOver();}
}void HelloWorld::doGameOver()
{if (!gameEnded) {Director::getInstance()->replaceScene(TransitionRotoZoom::create(1, HelloWorld::createScene()));}
}

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

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

    int playerGold;LabelBMFont* ui_gold_lbl;

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

        playerGold = 1000;ui_gold_lbl = LabelBMFont::create(String::createWithFormat("GOLD:%d",playerGold)->getCString(), "font_red_14.fnt");this->addChild(ui_gold_lbl,10);ui_gold_lbl->setPosition(Point(135, winSize.height - 12));ui_gold_lbl->setScale(2);ui_gold_lbl->setAnchorPoint(Point(0, 0.5f));

添加如下方法:

void HelloWorld::awardGold(int gold)
{playerGold += gold;ui_gold_lbl->setString(String::createWithFormat("GOLD: %d",playerGold)->getCString());
}

替换 canBuyTower 方法,代码如下:

bool HelloWorld::canBuyTower()
{if (playerGold - kTOWER_COST >= 0) {return true;}else{return false;}
}

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

            playerGold -= kTOWER_COST;ui_gold_lbl->setString(String::createWithFormat("GOLD: %d",playerGold)->getCString());

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

        _theGame->awardGold(200);

参考资料:
1.How To Make a Tower Defense Game  http://www.raywenderlich.com/15730/how-to-make-a-tower-defense-game
2.钓龟岛保卫战-如何从零开始制作一款iOS塔防游戏(新)  http://article.ityran.com/archives/1941
非常感谢以上资料,本例子源代码附加资源下载地址:http://pan.baidu.com/s/19ZfaC

如文章存在错误之处,欢迎指出,以便改正。

如何制作一个塔防游戏 Cocos2d-x 3.0alpha0相关推荐

  1. 如何制作一个塔防游戏 Cocos2d x 2 0 4

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 本文实践 ...

  2. (译)如何使用cocos2d制作一个塔防游戏:引子

    原文链接地址:http://www.iphonegametutorials.com/2011/04/11/cocos2d-game-tutorial-how-to-build-a-tower-defe ...

  3. 如何制作一个塔防游戏 Cocos2d-x 2.0.4

    本文实践自 Pablo Ruiz 的文章<How To Make a Tower Defense Game>,文中使用Cocos2D,我在这里使用Cocos2D-x 2.0.4进行学习和移 ...

  4. (译)如何使用cocos2d制作一个塔防游戏:第三部分

    原文链接地址:http://www.iphonegametutorials.com/2011/04/19/cocos2d-game-tutorial-%E2%80%93-how-to-build-a- ...

  5. (译)如何做一个塔防游戏(cocos2d 2012-8-17)

    PS:一直关注http://www.raywenderlich.com/这个网站,前几天看了他们8月17发的一个塔防游戏教程,试了一下感觉不错,搜了一下没发现没有译成中文的(不知道现在有没有),就自己 ...

  6. 基于Python实现制作的塔防游戏

    导语 最近发现很多人对 python 制作小游戏感兴趣,于是花了半天时间做了个塔防小游戏,在这里分享给大家,希望对大家有帮助. 让我们愉快地开始吧~ 开发工具 **Python 版本:**3.6.4 ...

  7. 如何制作一个简单的游戏 Cocos2d x 2 0 4

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 本文实践 ...

  8. wp7使用Cocos2d-X for XNA制作一个塔防类游戏 (二)在游戏中加入地图和怪物。(上)

    地图编辑器的使用 首先先来介绍一下使用地图编辑器tIDE Tile Map Editor来生成TMX文件.tIDE Tile Map Editor的下载地址  http://tide.codeplex ...

  9. 从零开始手把手教你使用javascript+canvas开发一个塔防游戏02敌人自动寻路

    项目演示 项目演示地址: 体验一下 项目源码: 项目源码 代码结构 本节做完效果 Enemy.js //敌人类function Enemy(cxt,img,type,x,y,width,height) ...

最新文章

  1. 为什么一些现有成熟客户不愿意上S/4
  2. 全文详解:「深度学习」如何协助处理医疗中的「数据难题」
  3. python到底可以做什么-Python究竟是什么?能干嘛?
  4. iMX8方案服务-辰汉
  5. 朝花夕拾:代码生成器的基础——获取数据源的架构信息
  6. C语言计数排序Counting sort 算法(附完整源码)
  7. React demo:express、react-redux、react-router、react-roter-redux、redux-thunk(一)
  8. easymock教程_EasyMock教程–入门
  9. ZZULIJ 1129: 第几天
  10. 第 10 章 树结构的基础部分
  11. ECSHOP商品描述和文章里不加水印,只在商品图片和商品相册加水印
  12. access 跳过一次for循环_Java中的循环结构
  13. Mac Python下载安装教程
  14. Exchange反垃圾防病毒网关——SecurityGateway基本部署
  15. html表格中boder属性与style中boder属性区别
  16. 完全免费无限量京东联盟高级API - 高并发京东联盟转链接口 京东客转链接口 京粉转链接口 京东联盟返利接口 京东返利接口,线报无广告接口
  17. 用直接插入法进行数组排序
  18. 生活中的定律——墨菲定律
  19. OMF(Oracle Managed Files,Oracle管理的文件)介绍
  20. 报错:Illegal mix of collations

热门文章

  1. 结构力学程序算法理论基础(一)————虚功原理
  2. python 模块包裹
  3. 【报告分享】 美妆行业营销报告-从“她经济”到“TA经济“-WEIQ(附下载)
  4. 线性插值改变图像尺寸_Photoshop从入门到精通:修改画布尺寸旋转,改变图像大小分辨率...
  5. QT、linux驱动以及应用基础学习总结-基于IMX6ULL的翻金币游戏点灯以及蜂鸣器报警
  6. 计算机打字测试标准,拼音打字考试标准练习题200字
  7. matlab路径选择函数,Matlab路径设置相关函数及指令
  8. mysql5.7绿色版——我的安装姿势
  9. 智慧幼儿园信息管理系统的设计与实现
  10. U3D log Flash shader 效果(标题流光效果)