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

转载自:http://blog.csdn.net/akof1314/article/details/8674186

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
boolHelloWorld::init()
{
boolbRet= false;
do
{
CC_BREAK_IF(!CCLayer::init());

this->setTouchEnabled( true);
CCSizewins=CCDirector::sharedDirector()->getWinSize();
CCSprite*background=CCSprite::create( "Bg.png");
this->addChild(background);
background->setPosition(ccp(wins.width/ 2,wins.height/ 2));

bRet= true;
} while( 0);
returnbRet;
}

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

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

1
cocos2d::CCArray*towerBases;
打开 HelloWorldScene.cpp 文件,添加如下方法:

voidHelloWorld::loadTowerPositions()
{
CCArray*towerPositions=CCArray::createWithContentsOfFile( "TowersPosition.plist");
towerBases=CCArray::createWithCapacity( 10);
towerBases->retain();

CCObject*pObject= NULL;
CCARRAY_FOREACH(towerPositions,pObject)
{
CCDictionary*towerPos=(CCDictionary*)pObject;
CCSprite*towerBase=CCSprite::create( "open_spot.png");
this->addChild(towerBase);
towerBase->setPosition(ccp(((CCString*)towerPos->objectForKey( "x"))->intValue(),
((CCString*)towerPos->objectForKey( "y"))->intValue()));
towerBases->addObject(towerBase);
}
}

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

this->loadTowerPositions();
在析构函数里面,添加如下代码:
towerBases->release();
编译运行,就可以看到道路两侧的方块,这些是做为玩家炮塔的基座。如下图所示:

5.开始建立炮塔。打开 HelloWorldScene.h 文件,添加如下代码:
CC_SYNTHESIZE_RETAIN(cocos2d::CCArray*,_towers,Towers);
添加 Tower 类,派生自 CCNode 类, Tower.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
#ifndef__TOWER_H__
#define__TOWER_H__

#include "cocos2d.h"
#include "HelloWorldScene.h"

#definekTOWER_COST 300

classTower: publiccocos2d::CCNode
{
public:
Tower( void);
~Tower( void);

staticTower*nodeWithTheGame(HelloWorld*game,cocos2d::CCPointlocation);
boolinitWithTheGame(HelloWorld*game,cocos2d::CCPointlocation);

voidupdate( floatdt);
voiddraw( void);

CC_SYNTHESIZE(HelloWorld*,_theGame,TheGame);
CC_SYNTHESIZE(cocos2d::CCSprite*,_mySprite,MySprite);

private:
intattackRange;
intdamage;
floatfireRate;
};

#endif //__TOWER_H__

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

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
#include "Tower.h"
using namespacecocos2d;

Tower::Tower( void)
{
}

Tower::~Tower( void)
{
}

Tower*Tower::nodeWithTheGame(HelloWorld*game,CCPointlocation)
{
Tower*pRet= newTower();
if(pRet&&pRet->initWithTheGame(game,location))
{
returnpRet;
}
else
{
deletepRet;
pRet= NULL;
return NULL;
}
}

boolTower::initWithTheGame(HelloWorld*game,CCPointlocation)
{
boolbRet= false;
do
{
attackRange= 70;
damage= 10;
fireRate= 1;

_mySprite=CCSprite::create( "tower.png");
this->addChild(_mySprite);
_mySprite->setPosition(location);
_theGame=game;
_theGame->addChild( this);

this->scheduleUpdate();

bRet= true;
} while( 0);

returnbRet;
}

voidTower::update( floatdt)
{

}

voidTower::draw( void)
{
#ifdefCOCOS2D_DEBUG
ccDrawColor4F( 255,255,255);
ccDrawCircle(_mySprite->getPosition(),attackRange,360,30,false);
#endif
CCNode::draw();
}

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

  • attackRange: 炮塔可以攻击敌人的距离。
  • damage: 炮塔对敌人造成的伤害值。
  • fireRate: 炮塔再次攻击敌人的时间间隔。
有了这三个变量,就可以创建各种不同攻击属性的炮塔,比如需要很长时间来重新加载的远程重击,或者范围有限的快速攻击。最后,代码中的 draw 方法,用于在炮塔周围绘制一个圆,以显示出它的攻击范围,这将方便调试。
6.让玩家添加炮塔。打开 HelloWorldScene.cpp 文件,加入以下头文件声明:
#include "Tower.h"
在析构函数中添加如下代码:
_towers->release();
init 函数,添加如下代码:
1
2
_towers=CCArray::create();
_towers->retain();
添加如下两个方法,代码如下:

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
boolHelloWorld::canBuyTower()
{
return true;
}

voidHelloWorld::ccTouchesBegan(CCSet*pTouches,CCEvent*pEvent)
{
CCSetIteratoriter=pTouches->begin();
for(;iter!=pTouches->end();iter++)
{
CCTouch*pTouch=(CCTouch*)(*iter);
CCPointlocation=pTouch->getLocation();

CCObject*pObject= NULL;
CCARRAY_FOREACH(towerBases,pObject)
{
CCSprite*tb=(CCSprite*)pObject;
if( this->canBuyTower()&&tb->boundingBox().containsPoint(location)&&!tb->getUserData())
{
//Wewillspendourgoldlater.

Tower*tower=Tower::nodeWithTheGame( this,tb->getPosition());
_towers->addObject(tower);
tb->setUserData(tower);
}
}
}
}

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

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"

classWaypoint: publiccocos2d::CCNode
{
public:
Waypoint( void);
~Waypoint( void);

staticWaypoint*nodeWithTheGame(HelloWorld*game,cocos2d::CCPointlocation);

voiddraw( void);

CC_SYNTHESIZE(cocos2d::CCPoint,_myPosition,MyPosition);
CC_SYNTHESIZE(Waypoint*,_nextWaypoint,NextWaypoint);

private:
HelloWorld*theGame;
};

#endif //__WAYPOINT_H__
打开 Waypoint.cpp 文件,代码如下:
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 namespacecocos2d;

Waypoint::Waypoint( void)
{
_nextWaypoint= NULL;
}

Waypoint::~Waypoint( void)
{
}

Waypoint*Waypoint::nodeWithTheGame(HelloWorld*game,CCPointlocation)
{
Waypoint*pRet= newWaypoint();
if(pRet&&pRet->initWithTheGame(game,location))
{
returnpRet;
}
else
{
deletepRet;
pRet= NULL;
return NULL;
}
}

boolWaypoint::initWithTheGame(HelloWorld*game,CCPointlocation)
{
boolbRet= false;
do
{
theGame=game;
_myPosition=location;

this->setPosition(CCPointZero);
theGame->addChild( this);

bRet= true;
} while( 0);

returnbRet;
}

voidWaypoint::draw( void)
{
#ifdefCOCOS2D_DEBUG
ccDrawColor4F( 0,0,255);
ccDrawCircle(_myPosition,6,false);
ccDrawCircle(_myPosition,2,false);

if(_nextWaypoint)
{
ccDrawLine(_myPosition,_nextWaypoint->_myPosition);
}
#endif

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

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

打开 HelloWorldScene.cpp 文件,加入以下头文件声明:
#include "Waypoint.h"
在析构函数中添加如下代码:
_waypoints->release();
添加以下方法:

voidHelloWorld::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,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,220));
_waypoints->addObject(waypoint5);
waypoint5->setNextWaypoint(waypoint4);

Waypoint*waypoint6=Waypoint::nodeWithTheGame( this,ccp(- 40,220));
_waypoints->addObject(waypoint6);
waypoint6->setNextWaypoint(waypoint5);
}

init函数,添加如下代码:

this->addWaypoints();
编译运行,效果如下图所示:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
boolHelloWorld::collisionWithCircle(CCPointcirclePoint,floatradius,CCPointcirclePointTwo,floatradiusTwo)
{
floatxdif=circlePoint.x-circlePointTwo.x;
floatydif=circlePoint.y-circlePointTwo.y;

floatdistance=sqrt(xdif*xdif+ydif*ydif);

if(distance<=radius+radiusTwo)
{
return true;
}
return false;
}

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

1
2
3
4
intwave;
cocos2d::CCLabelBMFont*ui_wave_lbl;

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

_enemies->release();
添加 Enemy 类,派生自 CCNode 类, 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
#ifndef__ENEMY_H__
#define__ENEMY_H__

#include "cocos2d.h"
#include "HelloWorldScene.h"
#include "Waypoint.h"

classEnemy: publiccocos2d::CCNode
{
public:
Enemy( void);
~Enemy( void);

staticEnemy*nodeWithTheGame(HelloWorld*game);
boolinitWithTheGame(HelloWorld*game);
voiddoActivate( floatdt);
voidgetRemoved();

voidupdate( floatdt);
voiddraw( void);

CC_SYNTHESIZE(HelloWorld*,MySprite);

private:
cocos2d::CCPointmyPosition;
intmaxHp;
intcurrentHp;
floatwalkingSpeed;
Waypoint*destinationWaypoint;
boolactive;
};

#endif //__ENEMY_H__
打开 Enemy.cpp 文件,代码如下:
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 namespacecocos2d;

#defineHEALTH_BAR_WIDTH 20
#defineHEALTH_BAR_ORIGIN- 10

Enemy::Enemy( void)
{
}

Enemy::~Enemy( void)
{
}

Enemy*Enemy::nodeWithTheGame(HelloWorld*game)
{
Enemy*pRet= newEnemy();
if(pRet&&pRet->initWithTheGame(game))
{
returnpRet;
}
else
{
deletepRet;
pRet= NULL;
return NULL;
}
}

boolEnemy::initWithTheGame(HelloWorld*game)
{
boolbRet= 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();
CCPointpos=waypoint->getMyPosition();
myPosition=pos;
_mySprite->setPosition(pos);
_theGame->addChild( this);

this->scheduleUpdate();

bRet= true;
} while( 0);

returnbRet;
}

voidEnemy::doActivate( floatdt)
{
active= true;
}

voidEnemy::getRemoved()
{
this->getParent()->removeChild( this,true);
_theGame->getEnemies()->removeObject( this);

//Notifythegamethatwekilledanenemysowecancheckifwecansendanotherwave
_theGame->enemyGotKilled();
}

voidEnemy::update( floatdt)
{
if(!active)
{
return;
}

if(_theGame->collisionWithCircle(myPosition,1,destinationWaypoint->getMyPosition(),1))
{
if(destinationWaypoint->getNextWaypoint())
{
destinationWaypoint=destinationWaypoint->getNextWaypoint();
}
else
{
//Reachedtheendoftheroad.Damagetheplayer
_theGame->getHpDamage();
this->getRemoved();
}
}

CCPointtargetPoint=destinationWaypoint->getMyPosition();
floatmovementSpeed=walkingSpeed;

CCPointnormalized=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);
}

voidEnemy::draw( void)
{
CCPointhealthBarBack[]={
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,255));

CCPointhealthBar[]={
ccp(_mySprite->getPosition().x+HEALTH_BAR_ORIGIN,
ccp(_mySprite->getPosition().x+HEALTH_BAR_ORIGIN+( float)(currentHp*HEALTH_BAR_WIDTH)/maxHp,
ccp(_mySprite->getPosition().x+HEALTH_BAR_ORIGIN,_mySprite->getPosition().y+ 14)
};
ccDrawSolidPoly(healthBar,ccc4f( 0,255));

CCNode::draw();
}

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

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

voidHelloWorld::enemyGotKilled()
{
//Iftherearenomoreenemies.
if(_enemies->count()<= 0)
{
if(! this->loadWave())
{
CCLog( "Youwin!");
CCDirector::sharedDirector()->replaceScene(CCTransitionSplitCols::create( 1,HelloWorld::scene()));
}
}
}

voidHelloWorld::getHpDamage()
{

}

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

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文件,添加以下代码:

classEnemy;
添加以下变量:
boolattacking;
Enemy*chosenEnemy;
打开 Tower.cpp 文件,添加头文件声明:
#include "Enemy.h"
initWithTheGame 函数开头if条件之后,添加如下代码:
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
voidTower::attackEnemy()
{
this->schedule(schedule_selector(Tower::shootWeapon),fireRate);
}

voidTower::chosenEnemyForAttack(Enemy*enemy)
{
chosenEnemy= NULL;
chosenEnemy=enemy;
this->attackEnemy();
enemy->getAttacked( this);
}

voidTower::shootWeapon( floatdt)
{
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));
}

voidTower::removeBullet(CCSprite*bullet)
{
bullet->getParent()->removeChild(bullet,true);
}

voidTower::damageEnemy()
{
if(chosenEnemy)
{
chosenEnemy->getDamaged(damage);
}
}

voidTower::targetKilled()
{
if(chosenEnemy)
{
chosenEnemy= NULL;
}

this->unschedule(schedule_selector(Tower::shootWeapon));
}

voidTower::lostSightOfEnemy()
{
chosenEnemy->gotLostSight( this);
if(chosenEnemy)
{
chosenEnemy= NULL;
}

this->unschedule(schedule_selector(Tower::shootWeapon));
}
最后,更新 update 方法为如下:
voidTower::update( floatdt)
{
if(chosenEnemy)
{
//Wemakeitturntotargettheenemychosen
CCPointnormalized=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(),chosenEnemy->getMySprite()->getPosition(),1))
{
this->lostSightOfEnemy();
}
}
else
{
CCObject*pObject= NULL;
CCARRAY_FOREACH(_theGame->getEnemies(),pObject)
{
Enemy*enemy=(Enemy*)pObject;
if(_theGame->collisionWithCircle(_mySprite->getPosition(),enemy->getMySprite()->getPosition(),1))
{
this->chosenEnemyForAttack(enemy);
break;
}
}
}
}

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

cocos2d::CCArray*attackedBy;
打开 Enemy.cpp 文件,在 initWithTheGame 函数开头if条件之后,添加如下代码:
attackedBy=CCArray::createWithCapacity( 5);
attackedBy->retain();
getRemoved 函数开头,添加如下代码:
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
voidEnemy::getAttacked(Tower*attacker)
{
attackedBy->addObject(attacker);
}

voidEnemy::gotLostSight(Tower*attacker)
{
attackedBy->removeObject(attacker);
}

voidEnemy::getDamaged( intdamage)
{
currentHp-=damage;
if(currentHp<= 0)
{
this->getRemoved();
}
}

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

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

1
2
3
intplayerHp;
cocos2d::CCLabelBMFont*ui_hp_lbl;
boolgameEnded;
变量playerHp表示玩家的生命值,CCLabelBMFont对象是一个标签,用来显示生命数值。gameEnded用来表示游戏是否结束。打开 HelloWorldScene.cpp 文件,在 init 函数里面,添加如下代码:
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));
添加如下方法:

voidHelloWorld::getHpDamage()
{
playerHp--;
ui_hp_lbl->setString(CCString::createWithFormat( "HP:%d",playerHp)->getCString());
if(playerHp<= 0)
{
this->doGameOver();
}
}

voidHelloWorld::doGameOver()
{
if(!gameEnded)
{
gameEnded= true;
CCDirector::sharedDirector()->replaceScene(CCTransitionRotoZoom::create( 1,HelloWorld::scene()));
}
}

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

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

intplayerGold;
cocos2d::CCLabelBMFont*ui_gold_lbl;
就像显示生命数值一样,一个变量表示玩家的金币数,一个标签对象显示金币数值。打开 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( 135,wins.height- 12));
ui_gold_lbl->setAnchorPoint(ccp( 0,0. 5));
添加如下方法:
voidHelloWorld::awardGold( intgold)
{
playerGold+=gold;
ui_gold_lbl->setString(CCString::createWithFormat( "GOLD:%d",playerGold)->getCString());
}
替换 canBuyTower 方法,代码如下:
1
2
3
4
5
6
7
8
boolHelloWorld::canBuyTower()
{
if(playerGold-kTOWER_COST>= 0)
{
return true;
}
return false;
}
ccTouchesBegan 函数里面,语句 //We will spend our gold later. 的后面,添加如下代码:
playerGold-=kTOWER_COST;
ui_gold_lbl->setString(CCString::createWithFormat( "GOLD:%d",playerGold)->getCString());
上述的代码在玩家尝试放置炮塔时,检查是否有足够的金币。如果足够的话,炮塔就会放置上去,并从玩家的金币数中减去炮塔的费用。每次杀死敌人的时候也应该奖励玩家一些金币。打开 Enemy.cpp 文件,在 getDamaged 函数里面,if条件后面,添加如下语句:
_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");
编译运行,现在游戏将有配乐,关闭掉调试绘制后,效果图:

参考资料:
1.How To Make a Tower Defense Gamehttp://www.raywenderlich.com/15730/how-to-make-a-tower-defense-game
2.钓龟岛保卫战-如何从零开始制作一款iOS塔防游戏(新)http://article.ityran.com/archives/1941
非常感谢以上资料,本例子源代码附加资源下载地址http://download.csdn.net/detail/akof1314/5143209 如文章存在错误之处,欢迎指出,以便改正。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


    本文实践自 RayWenderlich、Ali Hafizji 的文章《How To Create Dynamic Textures with CCRenderTexture in Cocos2D 2.X》,文中使用Cocos2D,我在这里使用Cocos2D-x 2.1.4进行学习和移植。在这篇文章,将会学习到如何创建实时纹理、如何用Gimp创建无缝拼接纹
Cocos-code-ide使用入门学习地点:杭州滨江邮箱:appdevzw@163.com微信公众号:HopToad 欢迎转载,转载标注出处:http://blog.csdn.netotbaron/article/details/424343991.  软件准备 下载地址:http://cn.cocos2d-x.org/download 2.  简介2.1         引用C
第一次開始用手游引擎挺激动!!!进入正题。下载资源1:从Cocos2D-x官网上下载,进入网页http://www.cocos2d-x.org/download,点击Cocos2d-x以下的Download  v3.0,保存到自定义的文件夹2:从python官网上下载。进入网页https://www.python.org/downloads/,我当前下载的是3.4.0(当前最新
    Cocos2d-x是一款强大的基于OpenGLES的跨平台游戏开发引擎,易学易用,支持多种智能移动平台。官网地址:http://cocos2d-x.org/当前版本:2.0    有很多的学习资料,在这里我只做为自己的笔记记录下来,错误之处还请指出。在VisualStudio2008平台的编译:1.下载当前稳
1.  来源 QuickV3sample项目中的2048样例游戏,以及最近《最强大脑》娱乐节目。将2048改造成一款挑战玩家对数字记忆的小游戏。邮箱:appdevzw@163.com微信公众号:HopToadAPK下载地址:http://download.csdn.net/detailotbaron/8446223源码下载地址:http://download.csdn.net/
   Cocos2d-x3.x已经支持使用CMake来进行构建了,这里尝试以QtCreatorIDE来进行CMake构建。Cocos2d-x3.X地址:https://github.com/cocos2d/cocos2d-x1.打开QtCreator,菜单栏→"打开文件或项目...",打开cocos2d-x目录下的CMakeLists.txt文件;2.弹出CMake向导,如下图所示:设置
 下载地址:链接:https://pan.baidu.com/s/1IkQsMU6NoERAAQLcCUMcXQ提取码:p1pb下载完成后,解压进入build目录使用vs2013打开工程设置平台工具集,打开设置界面设置: 点击开始编译等待编译结束编译成功在build文件下会出现一个新文件夹Debug.win32,里面就是编译
分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!http://www.captainbed.net前言上次用象棋演示了cocos2dx的基本用法,但是对cocos2dx并没有作深入的讨论,这次以超级马里奥的源代码为线索,我们一起来学习超级马里奥的实
1. 圆形音量button事实上作者的本意应该是叫做“电位计button”。可是我觉得它和我们的圆形音量button非常像,所以就这么叫它吧~先看效果:好了,不多解释,本篇到此为止。(旁白: 噗。就这样结束了?)啊才怪~我们来看看代码:[cpp] viewplaincopyprint?CCContro
原文链接:http://www.cnblogs.com/physwf/archive/2013/04/26/3043912.html为了进一步深入学习贯彻Cocos2d,我们将自己写一个场景类,但我们不会走的太远,凡是都要循序渐进,哪怕只前进一点点,那也至少是前进了,总比贪多嚼不烂一头雾水的好。在上一节中我们建
2019独角兽企业重金招聘Python工程师标准>>>cocos2d2.0之后加入了一种九宫格的实现,主要作用是用来拉伸图片,这样的好处在于保留图片四个角不变形的同时,对图片中间部分进行拉伸,来满足一些控件的自适应(PS: 比如包括按钮,对话框,最直观的形象就是ios里的短信气泡了),这就要求图
原文链接:http://www.cnblogs.com/linji/p/3599478.html1.环境和工具准备Win7VS2010/2012,至于2008v2版本之后似乎就不支持了。 2.安装pythonv.2.0版本之前是用vs模板创建工程的,到vs2.2之后就改用python创建了。到python官网下载版本2.7.5的,然后
环境:ubuntu14.04adt-bundle-linux-x86_64android-ndk-r9d-linux-x86_64cocos2d-x-3.0正式版apache-ant1.9.3python2.7(ubuntu自带)加入环境变量exportANDROID_SDK_ROOT=/home/yangming/adt-bundle-linux/sdkexportPATH=${PATH}:/$ANDROID_SDK_ROOTools/export
1开发背景游戏程序设计涉及了学科中的各个方面,鉴于目的在于学习与进步,本游戏《FlappyBird》采用了两个不同的开发方式来开发本款游戏,一类直接采用win32底层API来实现,另一类采用当前火热的cocos2d-x游戏引擎来开发本游戏。2需求分析2.1数据分析本项目要开发的是一款游
原文链接:http://www.cnblogs.com/linji/p/3599912.html//纯色色块控件(锚点默认左下角)CCLayerColor*ccc=CCLayerColor::create(ccc4(255,0,0,128),200,100);//渐变色块控件CCLayerGradient*ccc=CCLayerGradient::create(ccc4(255,0,0,
原文链接:http://www.cnblogs.com/linji/p/3599488.html//载入一张图片CCSprite*leftDoor=CCSprite::create("loading/door.png");leftDoor->setAnchorPoint(ccp(1,0.5));//设置锚点为右边中心点leftDoor->setPosition(ccp(240,160));/
为了答谢广大学员对智捷课堂以及关老师的支持,现购买51CTO学院关老师的Cocos2d-x课程之一可以送智捷课堂编写图书一本(专题可以送3本)。一、Cocos2d-x课程列表:1、Cocos2d-x入门与提高视频教程__Part22、Cocos2d-x数据持久化与网络通信__Part33、Cocos2d-x架构设计与性能优化内存优
Spawn让多个action同时执行。Spawn有多种不同的create方法,最终都调用了createWithTwoActions(FiniteTimeAction*action1,FiniteTimeAction*action2)方法。createWithTwoActions调用initWithTwoActions方法:对两个action变量初始化:_one=action1;_two=action2;如果两个a
需要环境:php,luajit.昨天在cygwin上安装php和luajit环境,这真特么是一个坑。建议不要用虚拟环境安装打包环境,否则可能会出现各种莫名问题。折腾了一下午,最终将环境转向linux。其中,luajit的安装脚本已经在quick-cocos2d-x-develop/bin/中,直接luajit_install.sh即可。我的lin
v3.0相对v2.2来说,最引人注意的。应该是对触摸层级的优化。和lambda回调函数的引入(嗯嗯,不枉我改了那么多类名。话说,每次cocos2dx大更新。总要改掉一堆类名函数名)。这些特性应该有不少人研究了,所以今天说点跟图片有关的东西。v3.0在载入图片方面也有了非常大改变,仅仅只是