使用
Cocos2d-x-3.0
游戏引擎。编写一个瓦片地图游戏
游戏设计的主题总是使人们激动。 在我看来,一个学徒级的游戏设计者或者个人看法者应该用一个小的经典事例来分析这会比看纯理论的文章更加具有实用性。在这个系列里,我将想你展示如何使用Cocos2d-x-3.0引擎来编写一个简单的塔防游戏,坦克防御者。当我提及到瓦片地图游戏时,你可能已经知道了一些这一类的经典游戏,或者你只是没有认出他们罢了。这些图片会帮助你记忆起他来。这两个都是瓦片地图游戏。

在这篇文章中,我将向你展示如何使用瓦片地图编辑器去构建并载入地图。管理不同的图片层和物体层。载入人物并使它动起来。载入你的敌人使它一起动起来。同样也会告诉你如何防卫你自己。
1、准备工作
Game Engine: Cocos2d-x-3.0rc0(游戏引擎3.0rc0): (http://www.cocos2d-x.org/download)
Tile Map Editor-9.1(瓦片地图编辑器9.1): (http://sourceforge.net/projects/tiled/files/)
VS2012: (http://www.visualstudio.com/zh-cn/visual-studio-homepage-vs.aspx)
首先,我们需要一个游戏引擎——Cocos2d-x-3.0rc0,用C++、OpenGL和ES 1.1/2.0编写, 可在iOS、安卓、黑莓、Bada、Marmalade、Windows和Windows Phone系统上运行。
接下来我们还需要一个编辑器来写代码并展示结果(我选择使用工具VS2012.)(重要!需要VA插件。或者需要更高的版本,像VS2013。总之要支持C++11)。
瓦片地图编辑器:版本9.1。它能够创建你所想要的地图。
瓦片地图材料:用来展示效果和创建地图的图片。
人物/敌人的精灵:表示自己人物和敌人的图片。
音乐:包含背景音乐和不同的效果音。
如果一切顺利,这些音乐及图片资源应当放在Resources文件夹里。
通过上一个例子,小小口袋怪兽的学习,我们已经知道了一些关于他们如何运转的模式。(像是精灵、菜单、还有瓦片地图)。现在我们将要制作另一个塔防游戏。但首先我们要了解一些有关这款游戏的基本概念。
·当敌人出现,他们会沿着我们事先设计好的路线前进。通常这条路会带着他们到达目的地。(地图的另一侧)
·沿路上,我们可以部署一些坦克拖延并摧毁敌人。当敌人进入事业范围后,坦克将会自动侦测敌人的位置并开始向敌人射击。并且你能建造更多的这些坦克来防御越来越多的敌人。
·守住防线。直到消灭最后一波敌人,你就能取得胜利。
制作地图
我们现在已经有了关于如何编写这款游戏的概念。在我们开始编码以前,我们仍需做些准备:
·首先,我们必定需要一张地图来实现我们的程序。
·其次,设定多个路标来引导我们的敌人。
·第三,我们会添加不同种类的敌人。

使用瓦片地图编辑器像我在上面展示的那样制造地图。

在编辑器的右上角创建一个“Object”层。按下“矩形”按钮如我上图所示来创造8个路标。并将它们命名为Waypoing00, Waypoint01, Waypoint02……
4.读取地图并创建我们那些令人胆寒的敌人:
接下来我们将要做的是载入我们的敌人。这可能会有点困难,但也并没有那么复杂。
把“HelloWorld”的头文件和执行文件名称都改为“TutorialScene”。并且把文件里每一个“HelloWorld”改成“TutorialScene”(直接用“HelloWorld”,而不改名字也可以,这里只是想展示一下更改文件名后需要注意的有哪些步骤。)
Delete:
删除:
#ifndef HELLOWORLD_SCENE_H
#define HELLOWORLD_SCENE_H
#endif // HELLOWORLD_SCENE_H
同样的把“AppDelegate.cpp”里的“HelloWorld”都改为“TutorialScene”。
改:
#include “HelloWorld.h”
auto scene = HelloWorld::createScene();
Into:
成:
#include “TutorialScene.h”
auto scene = TutorialScene::createScene();
我们要用到的功能基本都存在于“TutorialScene”。所以毫无疑问这个文件将会成为最为复杂的一个。
In the ‘TutorialScene.h’:
#pragma once
#include “cocos2d.h”
USING_NS_CC;
……
private:
cocos2d::TMXTiledMap *_tileMap;
cocos2d::TMXLayer *_background;
……
替换所有在“TutorialScene.cpp”里“bool TutorialScene::init()”中的所有代码:
bool TutorialScene::init()
{
if ( !Layer::init() )
{
return false;
}
Size visibleSize = Director::getInstance()->getVisibleSize();
Point origin = Director::getInstance()->getVisibleOrigin();
std::string file = "01.tmx";
auto str = String::createWithContentsOfFile (FileUtils::getInstance()-> fullPathForFilename(file.c_str()).c_str());
this->_tileMap = TMXTiledMap::createWithXML(str->getCString(),"");
this->_background = _tileMap->layerNamed("Background");
addChild(_tileMap, -1);
return true;
}
```
这里,我们调用TMXTilemap的类。现在我们运行VS2012的调试功能。
5.注意!一大波代码正在靠近。
创建四对头文件和实现文件命名为“Creep”“WayPoint”“Wave”“DataModel”(或者其他你喜欢的名称,就是后面用的时候别弄混了)。把它们创建在“Class”文件夹下面而不是默认文件夹中。
AppDelegate— 建立窗口,载入Director和场景。
WayPoint—从瓦片地图编辑器中读取路标。
DataModel—一个数据界面,用来存储数据。
Wave—控制敌人的类型,出现速度,总数量。
Creep—坏蛋= =||
TutorialScene –用来载入地图并设定不同的参数给不同的变量。
AppDelegate
在AppDelegate.Cpp里:
只需要更改
‘auto scene = HelloWorld::createScene();’
成
‘auto scene = TutorialScene::createScene();’
我们之前已经把‘HelloWorld’ 改成了 ‘TutorialWorld’。
WayPoint:继承自“Node”。并且得到x, y的值。
在WayPoint.h里:
#pragma once
#include "cocos2d.h"
USING_NS_CC;
class WayPoint:public Node
{
public:
virtual bool init();
CREATE_FUNC(WayPoint);
};
```
在WayPoint.cpp里:
#include "WayPoint.h"
USING_NS_CC;
bool WayPoint::init()
{
if (!Node::init())
{
return false;
}
return true;
}
```
DataModel:被用来存储游戏状态。并且我们能从任何一个类接入DataModel,通过包含它的头文件‘#include “DataModel.h” ’和下面这行代码:
DataModel *m = DataModel::getModel();
在DataModel.h里面:
#pragma once
#include "cocos2d.h"
#include "CCVector.h"
#include "WayPoint.h"
#include "Creep.h"
#include "TutorialScene.h"
USING_NS_CC;
class DataModel
{
public:
TutorialScene* _gameLayer;
static DataModel* getModel();
Vector waypoints;
Vector targets;
Vector waves;
//Vector towers; // We will deal with it later.
//Vector projectiles; // We will deal with it later.
//GameHUD* _gameHUDLayer; // We will deal with it later.
private:
DataModel(){};
static DataModel * m_pInstance;
};
```
在DataModel.cpp里面:
#include "DataModel.h"
USING_NS_CC;
DataModel* DataModel::m_pInstance;
DataModel* DataModel::getModel()
{
if (m_pInstance == NULL)
{
m_pInstance = new DataModel();
}
return m_pInstance;
}
```
这个类包含了这款游戏的所有元素和特征,目标(敌人)和我们的塔(小精灵),即将使用的子弹,用来指路的路标和攻击的波数。 并且类“Wave”控制敌人的类型,出生速度和总数。
在Wave.h里面:
#pragma once
#include "cocos2d.h"
#include "Creep.h"
USING_NS_CC;
class Wave: public Node
{
public:
Point position;
int totalCreeps;
double spawnRate;
Creep* creepType;
virtual bool init();
Wave* initWithCreep(Creep* creep, double SpawnRate,int TotalCreeps);
CREATE_FUNC(Wave);
};
在Wave.cpp里面:
#include "Wave.h"
USING_NS_CC;
bool Wave::init()
{
if (!Node::init())
{
return false;
}
return true;
}
Wave* Wave::initWithCreep(Creep* creep, double SpawnRate, int TotalCreeps)
{
this->creepType = creep;
this->spawnRate = SpawnRate;
this->totalCreeps = TotalCreeps;
return this;
}
```
现在我们已经知道了“WayPoint”“Wave”“DataModel”的作用了。现在我们来看看这些坏蛋吧~

在Creep.h里
#pragma once
#include "cocos2d.h"
#include "WayPoint.h"
USING_NS_CC;
class Creep: public Sprite
{
public:
int curHp;
int moveDuration;
int curWaypoint;
int tag;
Sprite* sprite;
virtual bool init();
Creep* initWithCreep(Creep* copyFrom);
WayPoint* getNextWaypoint();
WayPoint* getCurrentWaypoint();
CREATE_FUNC(Creep);
};
class FastRedCreep: public Creep
{
public:
static Creep* creep();
};
class StrongGreenCreep: public Creep
{
public:
static Creep* creep();
};
```
这里我们要开始定义敌人的值。包括现有体力、移动所需时间、现在以及接下来的位置等等。
这里就是我们怎么完成这个方法的:

在Creep.cpp里面:
#include "Creep.h"
#include "WayPoint.h"
#include "DataModel.h"
USING_NS_CC;
bool Creep::init()
{
if (!Sprite::init())
{
return false;
}
return true;
}
Creep* Creep::initWithCreep(Creep* copyFrom)
{
return NULL;
}
Creep* FastRedCreep::creep()
{
auto creep = Creep::create();
creep->sprite = Sprite::create("Enemy1.png");
creep->setScale(0.4);
creep->addChild(creep->sprite, 0);
creep->curHp = 10;
creep->moveDuration = 3;
creep->curWaypoint = 0;
return creep;
}
Creep* StrongGreenCreep::creep()
{
auto creep = Creep::create();
creep->sprite = Sprite::create("Enemy2.png");
creep->setScale(0.4);
creep->addChild(creep->sprite, 0);
creep->curHp = 30;
creep->moveDuration = 8;
creep->curWaypoint = 0;
return creep;
}
```
这就是我们如何让他工作的—我们定义了一个类。我们引用“FastRedCreep”来引用它。然后返回一个’Creep’物体,然后我们就可以把它添加进场景里面了。毕竟‘Creep’是从Sprite里继承过来的。所以它有着Sprite的所有特征。
接下来,在类‘Creep’中,我们将要使用到类‘DataModel’和 ’WayPoint’。
WayPoint* Creep::getCurrentWaypoint()
{
DataModel* m = DataModel::getModel();
WayPoint* waypoint = (WayPoint *)m->waypoints.at(this->curWaypoint);
return waypoint;
}
WayPoint* Creep::getNextWaypoint()
{
DataModel* m = DataModel::getModel();
if (this->curWaypoint != 7)
{
this->curWaypoint++;
} else
{
this->curWaypoint = 0;
}
CCLOG("%d",this->curWaypoint); // For testing.
WayPoint *waypoint = (WayPoint *)m->waypoints.at(curWaypoint);
return waypoint;
}
```
这里,我们定义了关于怎样获取现有路标和下一个路标的方法。当我们的敌人到达了第七个路标后,会重设路标的值为‘0’然后敌人会重头开始行动。
把这些头文件添加进TutorialScene.h里面:
#include "cocos2d.h"
#include "Creep.h"
#include "WayPoint.h"
#include "Wave.h"
//#include "GameHUD .h" // We will deal with it later.
and .cpp.
//#include "Tower.h" // We will deal with it later.
#include "DataModel.h"
#include
#include
In the TutorialScene.h
Class TutorialScene:public cocos2d::Layer
{
public:
// other codes……
int currentLevel;
void addWayPoint();
void addWaves();
void FollowPath(Node *sender);
void gameLogic(float dt);
void addTarget();
virtual void update(float dt);
Wave* getCurrentWave();
Wave* getNextWave();
// other code……
}
In the TutorialScene.cpp
bool TutorialScene::init()
{
……
// The code we already added.
……
this->addWayPoint();
this->addWaves();
this->scheduleUpdate();
this->schedule(schedule_selector(TutorialScene::gameLogic), 1.0f);
this->currentLevel = 0;
……
return true; // This one should place at the last.
}
void TutorialScene::FollowPath(Node *sender)
{
Creep *creep = (Creep *)sender;
WayPoint *waypoint = creep->getNextWaypoint();
int moveDuration = creep->moveDuration;
auto actionMove = MoveTo::create(moveDuration, waypoint->getPosition());
auto actionMoveDone = CallFuncN::create(this, callfuncN_selector(TutorialScene::FollowPath));
creep->stopAllActions();
creep->runAction(Sequence::create(actionMove, actionMoveDone, NULL));
}
// Here we read the value from the class ‘creep’. And we make the enemy move.
// 这里我们从类‘Creep’中读取了相应的值。并且让敌人开始移动。
void TutorialScene::addWaves()
{
DataModel *m = DataModel::getModel();
Wave *wave = NULL;
wave = Wave::create()->initWithCreep(FastRedCreep::creep(), 0.3, 75);
m->waves.pushBack(wave);
wave = NULL;
wave = Wave::create()->initWithCreep(StrongGreenCreep::creep(), 1.0, 10);
m->waves.pushBack(wave);
wave = NULL;
}
//Here, we set the parameters for the class ‘Wave’ about the creep type, spawn rate and the number of the creep.
//这里,我们为类‘Wave’设定关于不同类型敌人、重生速度和敌人总量的参数。
void TutorialScene::addWayPoint()
{
DataModel *m = DataModel::getModel();
auto *objects = this->_tileMap->objectGroupNamed("Objects");
WayPoint *wp = NULL;
std::string stringWithFormat = "Waypoint";
int wayPointCounter = 0;
ValueMap wayPoint;
wayPoint = objects->objectNamed(stringWithFormat + std::to_string(wayPointCounter));
while (wayPoint.begin() != wayPoint.end())
{
int x = wayPoint.at("x").asInt();
int y = wayPoint.at("y").asInt();
wp = WayPoint::create();
wp->setPosition(ccp(x, y));
m->waypoints.pushBack(wp);
wayPointCounter++;
wayPoint = objects->objectNamed(stringWithFormat + std::to_string(wayPointCounter));
}
wp =NULL;
}
void TutorialScene::addTarget()
{
DataModel *m = DataModel::getModel();
Wave *wave = this->getCurrentWave();
if (wave->totalCreeps < 0)
{
return;
}
wave->totalCreeps--;
Creep *target = NULL;
int random = CCRANDOM_0_1() * 2;
if (random == 0)
{
target = FastRedCreep::creep();
}
else
{
target = StrongGreenCreep::creep();
}
WayPoint *waypoint = target->getCurrentWaypoint();
target->setPosition(waypoint->getPosition());
waypoint = target->getNextWaypoint();
this->addChild(target, 1);
int moveDuration = target->moveDuration;
auto actionMove = CCMoveTo::create(moveDuration, waypoint->getPosition());
auto actionMoveDone = CallFuncN::create(this, callfuncN_selector(TutorialScene::FollowPath));
target->runAction(CCSequence::create(actionMove, actionMoveDone, NULL));
target->tag = 1;
m->targets.pushBack(target);
}
void TutorialScene::gameLogic(float dt)
{
DataModel *m = DataModel::getModel();
Wave *wave = this->getCurrentWave();
static double lastTimeTargetAdded = 0;
double now = 0;
if (lastTimeTargetAdded == 0 || now - lastTimeTargetAdded >= wave->spawnRate)
{
this->addTarget();
lastTimeTargetAdded = now;
}
}
void TutorialScene::update(float dt)
{
}
Wave *TutorialScene::getCurrentWave()
{
DataModel *m = DataModel::getModel();
Wave *wave = (Wave *)m->waves.at(this->currentLevel);
return wave;
}
Wave *TutorialScene::getNextWave()
{
DataModel *m = DataModel::getModel();
this->currentLevel++;
if (this->currentLevel > 1)
{
this->currentLevel = 0;
}
Wave *wave = (Wave *)m->waves.at(this->currentLevel);
return wave;
}
```

游戏HUD
首先,我们需要调整一下屏幕,毕竟我们创建了一些放置在地图外的物体。这么一块黑色区域确实会让一些人不太喜欢偶尔也会引起一些bug。

把这些代码输入AppDelete.cpp
bool AppDelegate::applicationDidFinishLaunching()
{
……
glview->setDesignResolutionSize(864, 640, kResolutionExactFit);
……
}

现在我们就要新建两对类,Tower.h和相应cpp还有GameHud.h及cpp,所以我们将有机会选择不同类型的防御者。(就像我们建立不同类型的敌人那样,一旦你掌握了这个教程你就能自己动手试着完成这一任务)。现在将他们部署在地图上。

在Tower.h里面
#pragma once
#include "cocos2d.h"
#include "Creep.h"
class Tower: public Sprite
{
public:
int range;
Sprite* sprite;
Creep* target;
Sprite * selSpriteRange;
Creep* getClosesTarget();
CREATE_FUNC(Tower);
};
class MachineGunTower : public Tower
{
public:
static Tower* tower();
bool virtual init();
void towerLogic(float dt);
};
```
正如你所看到的,在这里我们首先创建了一个基本的类,里面包括它的射程,距离最近目标,并继承自类Sprite
在Tower.cpp里面
#include "Tower.h"
#include "DataModel.h"
Tower* MachineGunTower::tower()
{
Tower* tower = Tower::create();
tower->sprite = Sprite::create("MachineGunTurret.png");
tower->setScale(0.5);
tower->addChild(tower->sprite, 0);
tower->range = 200;
tower->schedule(schedule_selector(towerLogic), 0.2);
return tower;
}
```
就像之前那样。‘MachineGunTower’继承自‘Tower’,并载入图片素材,设置它的比例大小和射程。最后用‘schedule’功能每0.2秒刷新‘towerLogic’功能。
bool MachineGunTower::init()
{
if (!Sprite::init())
{
return false;
}
return true;
}
void MachineGunTower::towerLogic(float dt)
{
// Coming soon…
}
Uncomment the code in the DataModel.h of the tutorial part 02:
//GameHUD* _gameHUDLayer; // We will deal with it later.
在GameHUD.h中
#pragma once
#include "cocos2d.h"
USING_NS_CC;
class GameHUD: public Layer
{
public:
Sprite* background;
Sprite* selSpriteRange;
Sprite* selSprite;
Vector movableSprites;
static GameHUD* _sharHUD;
virtual bool init();
static GameHUD* shareHUD();
CREATE_FUNC(GameHUD);
virtual void onEnter();
bool onTouchBegan(Touch *touch, Event *event);
void onTouchMoved(Touch *touch, Event *event);
void onTouchEnded(Touch* touch, Event* event);
};
```

是的。他应该能能够被拖拽到任何地方,也因此我们需要监听事件‘Listener Event’(onTouchXXX)。
然后再GameHUD.cpp里面:
#include "GameHUD .h"
#include "DataModel.h"
GameHUD* GameHUD::_sharHUD;
bool GameHUD::init()
{
if (!Layer::init())
{
return false;
}
Size winSize = CCDirector::getInstance()->getWinSize();
// Draw the background of the game HUD
CCTexture2D::setDefaultAlphaPixelFormat(kCCTexture2DPixelFormat_RGBA8888);
background = Sprite::create("hud.png");
background->setScaleX (2);
background->setAnchorPoint(ccp(0, 0));
this->addChild(background);
//CCTexture2D::setDefaultAlphaPixelFormat(kCCTexture2DPixelFormat_Default);
```

// 读取防御塔的图片素材,我们会把他们绘制在游戏的HUD层上。

Vector images;
images.pushBack(StringMake("MachineGunTurret.png"));
images.pushBack(StringMake("MachineGunTurret.png"));
images.pushBack(StringMake("MachineGunTurret.png"));
images.pushBack(StringMake("MachineGunTurret.png"));
for (int i = 0; i < images.size(); ++i)
{
String* image = images.at(i);
auto *sprite = Sprite::create(image->getCString());
float offsetFraction = ((float)(i + 1)) / (images.size() + 1);
sprite->setScale(0.6);
sprite->setPosition(ccp(winSize.width*offsetFraction, 35));
this->addChild(sprite);
movableSprites.pushBack(sprite);
}
return true;
}
GameHUD* GameHUD::shareHUD()
{
if (_sharHUD == NULL)
{
_sharHUD = GameHUD::create();
}
return _sharHUD;
}
void GameHUD::onEnter()
{
Layer::onEnter();
auto listener = EventListenerTouchOneByOne::create();
listener->setSwallowTouches(true);
listener->onTouchBegan = CC_CALLBACK_2(GameHUD::onTouchBegan, this);
listener->onTouchMoved = CC_CALLBACK_2(GameHUD::onTouchMoved, this);
listener->onTouchEnded = CC_CALLBACK_2(GameHUD::onTouchEnded, this);
auto dispatcher = Director::getInstance()->getEventDispatcher();
dispatcher->addEventListenerWithSceneGraphPriority(listener, this);
//dispatcher->addEventListenerWithFixedPriority(listener, 0);
}
bool GameHUD::onTouchBegan(Touch *touch, Event *event)
{
Point touchLocation = this->convertToWorldSpace(this->convertTouchToNodeSpace(touch));
Sprite * newSprite = NULL;
for each(Sprite* sprite in this->movableSprites)
//for (int i = 0; i < movableSprites.size(); i++) // Use this if your VC doesn’t support C++11
{
// Sprite* sprite = (Sprite*)(movableSprites.at(i)); // Use this if your VC doesn’t support C++11
Rect pos_rect = Rect((sprite->getPositionX()-sprite->getContentSize().width/2), (sprite->getPositionY()-sprite->getContentSize().height/2), sprite->getContentSize().width, sprite->getContentSize().height);
// This determines the area which you can drag our ‘tower’
float xMin = pos_rect.getMinX();
float xMax = pos_rect.getMaxX();
float yMIn = pos_rect.getMinY();
float yMax = pos_rect.getMaxY();
if (pos_rect.containsPoint(touchLocation))
{
DataModel *m = DataModel::getModel();
//m.gestureRecognizer.enabled = NO;
selSpriteRange = Sprite::create("Range.png");
selSpriteRange->setScale(4);
this->addChild(selSpriteRange, -1);
selSpriteRange->setPosition(sprite->getPosition());
newSprite = Sprite::createWithTexture(sprite->getTexture()); //sprite;
newSprite->setPosition(sprite->getPosition());
newSprite->setScale(0.6);
selSprite = newSprite;
this->addChild(newSprite);
}
}
return true;
}
void GameHUD::onTouchMoved(Touch* touch,Event* event)
{
Point touchLocation = this->convertToWorldSpace(this->convertTouchToNodeSpace(touch));
Point oldTouchLocation = touch->getPreviousLocationInView();
oldTouchLocation = Director::getInstance()->convertToGL(oldTouchLocation);
oldTouchLocation = this->convertToNodeSpace(oldTouchLocation);
Point translation = ccpSub(touchLocation,oldTouchLocation);
if (selSprite)
{
Point newPos = selSprite->getPosition()+translation;
selSprite->setPosition(newPos);
selSpriteRange->setPosition(newPos);
DataModel *m = DataModel::getModel();
Point touchLocationInGameLayer = m->_gameLayer->convertTouchToNodeSpace(touch);
BOOL isBuildable = m->_gameLayer->canBuildOnTilePosition(touchLocationInGameLayer);
if (isBuildable)
{
selSprite->setOpacity(200);
}
else
{
selSprite->setOpacity(50);
}
}
}
```
等一下。你是否注意到了‘bool isBuildable’? 我们接下来应该要为我们的瓷砖设置一些属性。

void GameHUD::onTouchEnded(Touch* touch, Event* event)
{
Point touchLocation = this->convertTouchToNodeSpace(touch);
DataModel *m = DataModel::getModel();
if (selSprite)
{
Rect backgroundRect = Rect(background->getPositionX(),
background->getPositionY(),
background->getContentSize().width,
background->getContentSize().height);
if (!backgroundRect.containsPoint(touchLocation) && m->_gameLayer->canBuildOnTilePosition(touchLocation))
{
Point touchLocationInGameLayer = m->_gameLayer->convertTouchToNodeSpace(touch);
m->_gameLayer->addTower(touchLocationInGameLayer);
}
this->removeChild(selSprite,true);
selSprite = NULL;
this->removeChild(selSpriteRange,true);
selSpriteRange = NULL;
}
}
```
现在,把这些东西扔进TutorialScene.h里面
#include "GameHUD .h"
……
class TutorialScene : public cocos2d::Layer
{
public:
……
void addTower(Point pos);
Point tileCoordForPosition(Point position);
bool canBuildOnTilePosition(Point pos);
Point boundLayerPos(Point newPos);
Point position;
GameHUD *gameHUD;
……
private:
…...
};
In TutorialScene.cpp:
在TutorialScene.cpp:
……
#include "Tower.h"
……
Scene* TutorialScene::createScene()
{
……
auto myGameHUD = GameHUD::shareHUD();
m->_gameHUDLayer = myGameHUD;
scene->addChild(myGameHUD, 1);
……
}
```
再一次,我们使用了‘DataModel’来记录‘gameHUDLayer’。
Point TutorialScene::tileCoordForPosition(Point position)
{
int x = position.x / this->_tileMap->getTileSize().width;
int y = ((this->_tileMap->getMapSize().height * this->_tileMap->getTileSize().height) - position.y) / this->_tileMap->getTileSize().height;
return ccp(x, y);
}
```
对int y的公式感到疑惑么?因为坐标原点是放置在地图的左上角的。所以接下来的图片应该能帮助你的理解。

bool TutorialScene::canBuildOnTilePosition(Point pos)
{
Point towerLoc = this->tileCoordForPosition(pos);
int tileGid = this->_background->getTileGIDAt(towerLoc);
Value props = this->_tileMap->getPropertiesForGID(tileGid);
ValueMap map = props.asValueMap();
int type_int;
if (map.size() == 0)
{
type_int = 0;
}
else
{
type_int = map.at("buildable").asInt();
}
if (1 == type_int)
{
return true;
}
return false;
}
void TutorialScene::addTower(Point pos)
{
DataModel *m = DataModel::getModel();
Tower *target = NULL ;
Point towerLoc = this->tileCoordForPosition(pos);
int tileGid = this->_background->tileGIDAt(towerLoc);
Value props = this->_tileMap->propertiesForGID(tileGid);
ValueMap map = props.asValueMap();
int type_int = map.at("buildable").asInt();
if (1 == type_int)
{
target = MachineGunTower::tower();
target->setPosition(ccp((towerLoc.x * 32) + 16, this->_tileMap->getContentSize().height - (towerLoc.y * 32) - 16));
this->addChild(target,1);
target->setTag(1);
m->towers.pushBack(target);
}
else
{
log("Tile Not Buildable");
}
}
//检测属性和其相应的值。
Point TutorialScene::boundLayerPos(Point newPos)
{
Size winSize = CCDirector::getInstance()->getWinSize();
Point retval = newPos;
retval.x = MIN(retval.x, 0);
retval.x = MAX(retval.x, _tileMap->getContentSize().width + winSize.width);
retval.y = MIN(0, retval.y);
retval.y = MAX(_tileMap->getContentSize().height + winSize.height, retval.y);
return retval;
}
```
这个功能决定了你能拖拽你的Tower的最大范围。
看看结果如何:

旋转
在Tower.h里面:
#pragma once
#include "cocos2d.h"
#include "Creep.h"
class Tower: public Sprite
{
public:
……
Creep* getClosestTarget();
……
};
In tower.cpp:
Creep* Tower::getClosestTarget()
{
Creep *closestCreep = NULL;
double maxDistant = 99999; // It should be bigger than the range.
DataModel *m = DataModel::getModel();
for each(Sprite *target in m->targets)
// for(Vector::iterator i = m->targets.begin(); i < m->targets.end(); ++i) // Use these code if your VC doesn’t support C++11.
{
// Sprite *target = *i; // Use these code if your VC doesn’t support C++11.
Creep *creep = (Creep *)target;
double curDistance = ccpDistance(this->getPosition(), creep->getPosition());
if (curDistance < maxDistant) {
closestCreep = creep;
maxDistant = curDistance;
}
}
if (maxDistant < this->range)
return closestCreep;
return NULL;
}
//把这些代码加进‘void MachineGunTower::towerLogic(float dt)’:
void MachineGunTower::towerLogic(float dt) {
this->target = this->getClosestTarget();
if(this->target != NULL)
{
Point shootVector = this->target->getPosition() - this->getPosition();
float shootAngle = ccpToAngle(shootVector);
float cocosAngle = CC_RADIANS_TO_DEGREES(-1 * shootAngle);
float rotateSpeed = 0.5 / M_PI;
float rotateDuration = fabs(shootAngle * rotateSpeed);
this->runAction(Sequence::create(RotateTo::create(rotateDuration, cocosAngle), NULL));
}
}
```
是的。没错。现在是时间装填子弹了。这里或许会是最为有趣和激动的事情了。在完成这个教程后,我希望你知道如何去制作不同类型的防御塔,就像我们当时创建不同的敌人那样。并且对体力、射速、机动性和伤害值设定不同的属性。
首先,我们要做的事创建子弹——我们的类‘Projectile’。创建另一对文档——Projectile.h和Projectile.cpp。
在DataModel里面:
#include "Projectile.h"
和
Vector projectiles;
你知道应该要把他们放置在哪里。
In the Projectile.h:
#pragma once
#include "cocos2d.h"
USING_NS_CC;
class Projectile: public Sprite
{
public:
static Projectile* projectile();
};
In the Projectile.cpp:
#include "Projectile.h"
Projectile* Projectile::projectile()
{
Projectile* projectile = (Projectile*)Sprite::create("Projectile.png");
if (projectile != NULL)
{
return projectile;
} else {
return NULL;
}
}
```
现在我们添加一些新方法进“void TutorialScene::update(float dt)”。(这个方法名我们已经添加进了TutorialScene.cpp里面。)
void TutorialScene::update(float dt) {
DataModel *m = DataModel::getModel();
Vector projectilesToDelete;
for each(Projectile *projectile in m->projectiles)
// for (int i = 0; i < m->projectiles.size(); i++) // Use these code if your VC doesn’t support C++11.
{
// Projectile* projectile = (Projectile*)(m->projectiles.at(i)); // Use these code if your VC doesn’t support C++11.
Rect projectileRect = Rect(projectile->getPositionX() - (projectile->getContentSize().width / 2),
projectile->getPositionY() - (projectile->getContentSize().height / 2),
projectile->getContentSize().width,
projectile->getContentSize().height);
Vector targetsToDelete;
for each(Creep *target in m->targets)
// for (int i = 0; i < m->targets.size(); i++) // Use these code if your VC doesn’t support C++11.
{
// Creep* target = (Creep*)(m->targets.at(i)); // Use these code if your VC doesn’t support C++11.
Rect targetRect = Rect(target->getPositionX() - (target->sprite->getContentSize().width / 2),
target->getPositionY() - (target->sprite->getContentSize().height / 2),
target->sprite->getContentSize().width,
target->sprite->getContentSize().height);
if (projectileRect.intersectsRect(targetRect))
{
projectilesToDelete.pushBack(projectile);
Creep *creep = target;
creep->curHp -= 1;
if (creep->curHp <= 0)
{
targetsToDelete.pushBack(creep);
}
break;
}
}
//for each(Creep *target in targetsToDelete)
for (int i = 0; i < targetsToDelete.size(); i++)
{
Creep* target = (Creep*)(targetsToDelete.at(i));
m->targets.eraseObject(target);
this->removeChild(target, true);
}
}
for each(Projectile *projectile in projectilesToDelete)
// for (int i =0; i < projectilesToDelete.size(); i++) // Use these code if your VC doesn’t support C++11.
{
// Projectile* projectile = (Projectile*)(projectilesToDelete.at(i)); // Use these code if your VC doesn’t support C++11.
m->projectiles.eraseObject(projectile);
this->removeChild(projectile,true);
}
}
```
这里,我们遍历所有的投射物。对每一个投射物我们遍历所有的敌人。并且决定子弹是否和目标重叠。如果结果为真,敌人的体力将会减1,撞击到敌人的子弹将会被放入数组。一旦敌人的体力降为0,我们就将敌人添加进另一个用来删除的数组里。
最后,回到类“Tower”里面,让我们看看开火的机制。
别忘了添加这些代码到Tower.h里面:
#include "Projectile.h"
void finishFiring();
void creepMoveFinished(Node* sender);
//在“void MachineGunTower::towerLogic(float dt)”里面:
this->runAction(Sequence::create(RotateTo::create(rotateDuration, cocosAngle), CallFunc::create(this, callfunc_selector(MachineGunTower::finishFiring)), NULL)); // Delete the old one.
Then:
void MachineGunTower::finishFiring()
{
DataModel *m = DataModel::getModel();
if (this->target != NULL && this->target->curHp > 0 && this->target->curHp < 100)
{
this->nextProjectile = Projectile::projectile();
this->nextProjectile->setPosition(this->getPosition());
this->getParent()->addChild(this->nextProjectile, 1);
m->projectiles.pushBack(this->nextProjectile);
float delta = 1.0f;
Point shootVector = -(this->target->getPosition() - this->getPosition());
Point normalizedShootVector = ccpNormalize(shootVector);
Point overshotVector = normalizedShootVector * 320;
Point offscreenPoint = (this->getPosition() - overshotVector);
this->nextProjectile->runAction(Sequence::create(MoveTo::create(delta, offscreenPoint), CallFuncN::create(this, callfuncN_selector(MachineGunTower::creepMoveFinished)), NULL));
this->nextProjectile->setTag(2);
this->nextProjectile = NULL;
}
}
void MachineGunTower::creepMoveFinished(Node* sender)
{
DataModel * m = DataModel::getModel();
Sprite *sprite = (Sprite *)sender;
this->getParent()->removeChild(sprite,true);
m->projectiles.eraseObject((Projectile*)sprite);
}
```
一旦你的他面对着敌人,它就会开始射击。这个功能是用来专门制造子弹——“projectile”的。正如你所看到的。在转向正确的方向后,投射物会“runAction”和“MoveTo”到指定的位置。并且我们也设置了射程的最大值。一旦子弹超出了射程,它们就都会被消除。
现在,好好享受吧。




