接制作一个塔防类游戏 cocos2d-x 2.14(一)
资源级源代码下载:http://download.csdn.net/detail/yangshuo528/7426007
以下函数有的需要在.h文件中添加函数声明,别忘了添加!
7.添加路点。敌人将会沿着一系列的路点前进,这些简单相互连接的点构成了一条路径,敌人在这条路径上进行行走。敌人会出现在第一个路点,搜寻列表中的下一个路点,移动到那个位置,重复这个过程,直到他们到达列表中的最后一个路点——玩家基地。如果被敌人到达基地,那么玩家就会受到损害。添加Waypoint类,派生自CCNode类,Waypoint.h文件代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#ifndef __WAYPOINT_H__ #define __WAYPOINT_H__ #include "cocos2d.h" #include "HelloWorldScene.h" class Waypoint : public cocos2d::CCNode { public: Waypoint(void); ~Waypoint(void); static Waypoint* nodeWithTheGame(HelloWorld* game, cocos2d::CCPoint location); bool initWithTheGame(HelloWorld* game, cocos2d::CCPoint location); void draw(void); CC_SYNTHESIZE(cocos2d::CCPoint, _myPosition, MyPosition); CC_SYNTHESIZE(Waypoint*, _nextWaypoint, NextWaypoint); private: HelloWorld* theGame; }; #endif // __WAYPOINT_H__ |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
#include "Waypoint.h" using namespace cocos2d; Waypoint::Waypoint(void) { _nextWaypoint = NULL; } Waypoint::~Waypoint(void) { } Waypoint* Waypoint::nodeWithTheGame(HelloWorld* game, CCPoint location) { Waypoint *pRet = new Waypoint(); if (pRet && pRet->initWithTheGame(game, location)) { return pRet; } else { delete pRet; pRet = NULL; return NULL; } } bool Waypoint::initWithTheGame(HelloWorld* game, CCPoint location) { bool bRet = false; do { theGame = game; _myPosition = location; this->setPosition(CCPointZero); theGame->addChild(this); bRet = true; } while (0); return bRet; } void Waypoint::draw(void) { #ifdef COCOS2D_DEBUG ccDrawColor4F(0, 255, 0, 255); ccDrawCircle(_myPosition, 6, 360, 30, false); ccDrawCircle(_myPosition, 2, 360, 30, false); if (_nextWaypoint) { ccDrawLine(_myPosition, _nextWaypoint->_myPosition); } #endif CCNode::draw(); } |
8.创建路点列表。打开HelloWorldScene.h文件,添加以下代码:
1 |
CC_SYNTHESIZE_RETAIN(cocos2d::CCArray*, _waypoints, Waypoints);
|
1 |
#include "Waypoint.h"
|
1 |
_waypoints->release();
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
void HelloWorld::addWaypoints() { _waypoints = CCArray::create(); _waypoints->retain(); Waypoint *waypoint1 = Waypoint::nodeWithTheGame(this, ccp(420, 35)); _waypoints->addObject(waypoint1); Waypoint *waypoint2 = Waypoint::nodeWithTheGame(this, ccp(35, 35)); _waypoints->addObject(waypoint2); waypoint2->setNextWaypoint(waypoint1); Waypoint *waypoint3 = Waypoint::nodeWithTheGame(this, ccp(35, 130)); _waypoints->addObject(waypoint3); waypoint3->setNextWaypoint(waypoint2); Waypoint *waypoint4 = Waypoint::nodeWithTheGame(this, ccp(445, 130)); _waypoints->addObject(waypoint4); waypoint4->setNextWaypoint(waypoint3); Waypoint *waypoint5 = Waypoint::nodeWithTheGame(this, ccp(445, 220)); _waypoints->addObject(waypoint5); waypoint5->setNextWaypoint(waypoint4); Waypoint *waypoint6 = Waypoint::nodeWithTheGame(this, ccp(-40, 220)); _waypoints->addObject(waypoint6); waypoint6->setNextWaypoint(waypoint5); } |
在init函数,添加如下代码:
1 |
this->addWaypoints();
|
![](https://img-my.csdn.net/uploads/201303/14/1363254180_6128.png)
在地图上有6个路点,这是敌人的行走路线。在让敌人出现在游戏中前,还需要添加一个辅助方法。打开HelloWorldScene.cpp文件,添加方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
bool HelloWorld::collisionWithCircle(CCPoint circlePoint, float radius, CCPoint 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; } return false; } |
方法collisionWithCircle用于判断两个圆是否碰撞或者相交。这将用于判断敌人是否到达一个路点,同时也可以检测敌人是否在炮塔的攻击范围之内。
9.添加敌人。打开HelloWorldScene.h文件,添加以下代码:
1 2 3 4 |
CC_SYNTHESIZE_RETAIN(cocos2d::CCArray*, _enemies, Enemies); int wave; cocos2d::CCLabelBMFont* ui_wave_lbl; |
打开HelloWorldScene.cpp文件,在析构函数里,添加如下代码:
1 |
_enemies->release();
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
#ifndef __ENEMY_H__ #define __ENEMY_H__ #include "cocos2d.h" #include "HelloWorldScene.h" #include "Waypoint.h" class Enemy : public cocos2d::CCNode { public: Enemy(void); ~Enemy(void); static Enemy* nodeWithTheGame(HelloWorld* game); bool initWithTheGame(HelloWorld* game); void doActivate(float dt); void getRemoved(); void update(float dt); void draw(void); CC_SYNTHESIZE(HelloWorld*, _theGame, TheGame); CC_SYNTHESIZE(cocos2d::CCSprite*, _mySprite, MySprite); private: cocos2d::CCPoint myPosition; int maxHp; int currentHp; float walkingSpeed; Waypoint *destinationWaypoint; bool active; }; #endif // __ENEMY_H__ |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
#include "Enemy.h" using namespace cocos2d; #define HEALTH_BAR_WIDTH 20 #define HEALTH_BAR_ORIGIN -10 Enemy::Enemy(void) { } Enemy::~Enemy(void) { } Enemy* Enemy::nodeWithTheGame(HelloWorld* game) { Enemy *pRet = new Enemy(); if (pRet && pRet->initWithTheGame(game)) { return pRet; } else { delete pRet; pRet = NULL; return NULL; } } bool Enemy::initWithTheGame(HelloWorld* game) { bool bRet = false; do { maxHp = 40; currentHp = maxHp; active = false; walkingSpeed = 0.5; _theGame = game; _mySprite = CCSprite::create("enemy.png"); this->addChild(_mySprite); Waypoint *waypoint = (Waypoint*)_theGame->getWaypoints()->objectAtIndex(_theGame->getWaypoints()->count() - 1); destinationWaypoint = waypoint->getNextWaypoint(); CCPoint 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, true); _theGame->getEnemies()->removeObject(this); //Notify the game that we killed an enemy so we can check if we can send another wave _theGame->enemyGotKilled(); } void Enemy::update(float dt) { if (!active) { return; } if (_theGame->collisionWithCircle(myPosition, 1, destinationWaypoint->getMyPosition(), 1)) { if (destinationWaypoint->getNextWaypoint()) { destinationWaypoint = destinationWaypoint->getNextWaypoint(); } else { //Reached the end of the road. Damage the player _theGame->getHpDamage(); this->getRemoved(); } } CCPoint targetPoint = destinationWaypoint->getMyPosition(); float movementSpeed = walkingSpeed; CCPoint normalized = ccpNormalize(ccp(targetPoint.x - myPosition.x, targetPoint.y - myPosition.y)); _mySprite->setRotation(CC_RADIANS_TO_DEGREES(atan2(normalized.y, - normalized.x))); myPosition = ccp(myPosition.x + normalized.x * movementSpeed, myPosition.y + normalized.y * movementSpeed); _mySprite->setPosition(myPosition); } void Enemy::draw(void) { CCPoint healthBarBack[] = { ccp(_mySprite->getPosition().x - 10, _mySprite->getPosition().y + 16), ccp(_mySprite->getPosition().x + 10, _mySprite->getPosition().y + 16), ccp(_mySprite->getPosition().x + 10, _mySprite->getPosition().y + 14), ccp(_mySprite->getPosition().x - 10, _mySprite->getPosition().y + 14) }; ccDrawSolidPoly(healthBarBack, 4, ccc4f(255, 0, 0, 255)); CCPoint healthBar[] = { ccp(_mySprite->getPosition().x + HEALTH_BAR_ORIGIN, _mySprite->getPosition().y + 16), ccp(_mySprite->getPosition().x + HEALTH_BAR_ORIGIN + (float)(currentHp * HEALTH_BAR_WIDTH) / maxHp, _mySprite->getPosition().y + 16), ccp(_mySprite->getPosition().x + HEALTH_BAR_ORIGIN + (float)(currentHp * HEALTH_BAR_WIDTH) / maxHp, _mySprite->getPosition().y + 14), ccp(_mySprite->getPosition().x + HEALTH_BAR_ORIGIN, _mySprite->getPosition().y + 14) }; ccDrawSolidPoly(healthBar, 4, ccc4f(0, 255, 0, 255)); CCNode::draw(); } |
首先,通过传递一个HelloWorld对象引用进行初始化。在初始化函数里面,对一些重要的变量进行设置:
- maxHP: 敌人的生命值。
- walkingSpeed: 敌人的移动速度。
- mySprite: 存储敌人的可视化表现。
- destinationWaypoint: 存储下一个路点的引用。
①计算出从当前位置到目标位置的向量,然后将其长度设置为1(向量标准化)
②将移动速度乘以标准化向量,得到移动的距离,将它与当前坐标进行相加,得到新的坐标位置。
最后,draw方法在精灵上面简单的实现了一条血量条。它首先绘制一个红色背景,然后根据敌人的当前生命值用绿色进行覆盖血量条。
10.显示敌人。打开HelloWorldScene.cpp文件,添加头文件声明:
1 |
#include "Enemy.h"
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
bool HelloWorld::loadWave() { CCArray *waveData = CCArray::createWithContentsOfFile("Waves.plist"); if (wave >= waveData->count()) { return false; } CCArray *currentWaveData = (CCArray*)waveData->objectAtIndex(wave); CCObject *pObject = NULL; CCARRAY_FOREACH(currentWaveData, pObject) { CCDictionary* enemyData = (CCDictionary*)pObject; Enemy *enemy = Enemy::nodeWithTheGame(this); _enemies->addObject(enemy); enemy->schedule(schedule_selector(Enemy::doActivate), ((CCString*)enemyData->objectForKey("spawnTime"))->floatValue()); } wave++; ui_wave_lbl->setString(CCString::createWithFormat("WAVE: %d", wave)->getCString()); return true; } void HelloWorld::enemyGotKilled() { //If there are no more enemies. if (_enemies->count() <= 0) { if (!this->loadWave()) { CCLog("You win!"); CCDirector::sharedDirector()->replaceScene(CCTransitionSplitCols::create(1, HelloWorld::scene())); } } } void HelloWorld::getHpDamage() { } |
在init函数里面,添加如下代码(在do while循环中添加哦):
1 2 3 4 5 6 7 8 9 |
wave = 0; ui_wave_lbl = CCLabelBMFont::create(CCString::createWithFormat("WAVE: %d", wave)->getCString(), "font_red_14.fnt"); this->addChild(ui_wave_lbl, 10); ui_wave_lbl->setPosition(ccp(400, wins.height - 12)); ui_wave_lbl->setAnchorPoint(ccp(0, 0.5)); _enemies = CCArray::create(); _enemies->retain(); this->loadWave(); |
现在对上面的代码进行一些解释。最重要的部分是loadWave方法,它从Waves.plist文件读取数据。查看这个文件,可以看到它包含了3个数组,每个数组代表着一波敌人。第一个数组包含6个字典,每个字典定义了一个敌人。在本篇文章中,这个字典仅存储敌人的出现时间,但是也可用于定义敌人类型或者其他特殊属性,以区分不同的敌人。loadWave方法检查下一波应出现的敌人,根据波信息创建相应的敌人,并安排它们在规定的时间出现在屏幕上。enemyGotKilled方法检查当前屏幕上的敌人数量,如果已经没有敌人的话,那么就让下一波敌人出现。之后,还使用这个方法来判断玩家是否赢得了游戏。编译运行,敌人正向玩家基地前进,如下图所示:
10.炮塔攻击。每座塔进行检查是否有敌人出现在攻击范围之内,如果有的话,对敌人进行开火,直到以下两种情况之一发生:敌人移动出范围;敌人被消灭。那么炮塔就会寻找下一个敌人。打开Tower.h文件,添加以下代码:
1 |
class Enemy;
|
1 2 |
bool attacking; Enemy *chosenEnemy; |
1 |
#include "Enemy.h"
|
1 |
chosenEnemy = NULL;
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
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 dt) { CCSprite *bullet = CCSprite::create("bullet.png"); _theGame->addChild(bullet); bullet->setPosition(_mySprite->getPosition()); bullet->runAction(CCSequence::create( CCMoveTo::create(0.1, chosenEnemy->getMySprite()->getPosition()), CCCallFunc::create(this, callfunc_selector(Tower::damageEnemy)), CCCallFuncN::create(this, callfuncN_selector(Tower::removeBullet)), NULL)); } void Tower::removeBullet(CCSprite *bullet) { 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)); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
void Tower::update(float dt) { if (chosenEnemy) { //We make it turn to target the enemy chosen CCPoint normalized = ccpNormalize(ccp(chosenEnemy->getMySprite()->getPosition().x - _mySprite->getPosition().x, chosenEnemy->getMySprite()->getPosition().y - _mySprite->getPosition().y)); _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 { 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.h文件,添加以下代码:
1 |
cocos2d::CCArray *attackedBy;
|
1 2 |
attackedBy = CCArray::createWithCapacity(5); attackedBy->retain(); |
1 2 3 4 5 6 |
CCObject *pObject = NULL; CCARRAY_FOREACH(attackedBy, pObject) { Tower *attacker = (Tower*)pObject; attacker->targetKilled(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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文件,添加以下代码:
1 2 3 |
int playerHp; cocos2d::CCLabelBMFont *ui_hp_lbl; bool gameEnded; |
1 2 3 4 5 |
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)); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
void HelloWorld::getHpDamage() { playerHp--; ui_hp_lbl->setString(CCString::createWithFormat("HP: %d", playerHp)->getCString()); if (playerHp <= 0) { this->doGameOver(); } } void HelloWorld::doGameOver() { if (!gameEnded) { gameEnded = true; CCDirector::sharedDirector()->replaceScene(CCTransitionRotoZoom::create(1, HelloWorld::scene())); } } |
添加的方法为减少玩家生命值,更新标签,并检查玩家生命是否耗尽,如果是的话,游戏就结束了。当敌人到达基地的时候,getHpDamage方法被调用。编译运行,让敌人到达基地,你将会看到玩家的生命在减少,直到游戏失败。如下图所示:
12.限制金币供应量。大多数游戏都实现了“零和”功能,建造每座炮塔需要一定的资源,并给玩家有限的资源进行分配。打开HelloWorldScene.h文件,添加如下代码:
1 2 |
int playerGold; cocos2d::CCLabelBMFont *ui_gold_lbl; |
1 2 3 4 5 |
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(135, wins.height - 12)); ui_gold_lbl->setAnchorPoint(ccp(0, 0.5)); |
1 2 3 4 5 |
void HelloWorld::awardGold(int gold) { playerGold += gold; ui_gold_lbl->setString(CCString::createWithFormat("GOLD: %d", playerGold)->getCString()); } |
1 2 3 4 5 6 7 8 |
bool HelloWorld::canBuyTower() { if (playerGold - kTOWER_COST >= 0) { return true; } return false; } |
1 2 |
playerGold -= kTOWER_COST; ui_gold_lbl->setString(CCString::createWithFormat("GOLD: %d", playerGold)->getCString()); |
1 |
_theGame->awardGold(200);
|
![](https://img-my.csdn.net/uploads/201303/14/1363254363_7800.png)
13.加入背景音乐和音效。打开HelloWorldScene.cpp文件,添加头文件声明:
1 |
#include "SimpleAudioEngine.h"
|
1 |
CocosDenshion::SimpleAudioEngine::sharedEngine()->playBackgroundMusic("8bitDungeonLevel.mp3", true);
|
1 |
CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect("tower_place.wav");
|
1 |
CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect("life_lose.wav");
|
1 |
#include "SimpleAudioEngine.h"
|
1 |
CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect("laser_shoot.wav");
|
参考资料:1.How To Make a Tower Defense Game http://www.raywenderlich.com/15730/how-to-make-a-tower-defense-game
如何制作一个塔防游戏:http://blog.csdn.net/akof1314/article/details/8674186
资源级源代码下载:http://download.csdn.net/detail/yangshuo528/7426007