您的位置:首页 > 移动开发 > Cocos引擎

[Cocos2d塔防游戏开发]Cocos2dx-3.X完成塔防游戏《王国保卫战》--地图(一)

2015-04-04 10:06 441 查看






上面3张图是完成后的效果图

游戏已完成,除了英雄外,基本还原了90%的游戏内容,一共13关,20种防御塔,30+种敌人,如上图,以假乱真吧

下面从地图模块起介绍我的方法,如有更好的方法,请留言一起讨论,游戏资源下载原版游戏数据包,解压即可

推荐一款软件,TextureUnpackerRelease1.04可以分割plist形式的图片,提高效率

经过一个月学习也发现之前有很多化简为繁的错误


----------------------------------------------------------------------------------------------------------------------------------------------------

地图模块我采用3层结构,从下到上分别为地图层,触摸层,按键层

地图层负责地图的绘制,添加防御塔、敌人等,触摸层负责拦截一些触摸事件,添加技能/商店技能触摸响应,同时兼顾防御塔升级菜单弹出层,按键层则负责技能和商店按键、玩家生命金钱状态,暂停按键,暂停菜单等。

-----------------------------------------------------------------------------------------------------------------------------------------------------

首先是地图层,新建一个基类BaseMap,基层与Layer

class BaseMap : public Layer
{
public:
CREATE_FUNC(BaseMap);
//当前关卡
CC_SYNTHESIZE(int, level, Level);
//绑定按键层
void bindPlayerStateMenu(PlayerStateMenu* playerState);
Sprite* mapSprite;
//触摸层
TouchLayer* mTouchLayer;
protected:
//存储每一波敌人信息容器
std::vector<std::vector<Vector<GroupMonster*>>> waveVector;
//存储敌人路线
std::vector<std::vector<std::vector<Point>>> path;
//下一波敌人提示(玩过游戏的人知道就是那个一闪一闪的骷髅头)
Vector<WaveFlag*> waveFlags;
void addWaveProgressBars(std::vector<Point> waveFlagLocations);
void showWaveProgressBars(float dt);
virtual void addWaves(float dt);
//添加怪物
virtual void addMonsters(float dt);
//初始化地图
void initMap();
//添加不同地图装饰物
virtual void addOrnament(){};
//添加建塔点
virtual void addTerrains(){};
//退出
virtual void onExitTransitionDidStart();
virtual void onExit(){};
//其他
};


下面是我采用的plist格式,用文件的形式来保存每一关的信息

<dict>
<key>data</key>
<array>
<dict>
<key>gold</key>
<string>500</string>
<key>life</key>
<string>20</string>
<key>wave</key>
<string>3</string>
</dict>
</array>
<key>monsters</key>
<array>
<array>
<array>
<dict>
<key>path</key>
<string>0</string>
<key>road</key>
<string>0</string>
<key>type</key>
<string>0</string>
</dict>
<dict>
<key>path</key>
<string>2</string>
<key>road</key>
<string>1</string>
<key>type</key>
<string>0</string>
</dict>
<dict>
<key>path</key>
<string>0</string>
<key>road</key>
<string>2</string>
<key>type</key>
<string>-1</string>
</dict>
<dict>
<key>path</key>
<string>1</string>
<key>road</key>
<string>3</string>
<key>type</key>
<string>-1</string>
</dict>
</array>
<span style="white-space:pre">			</span></array>
<span style="white-space:pre">		</span></array>


第一个key date保存关卡的信息包括初始金钱,初始生命与一关的敌人波数

第二个Key monster保存这一关的的怪物信息

其中分为3个array

第一层array保存本关所有敌人

第二层array保存本波敌人,比如有6波敌人,就有6个array,addwave(float dt)将读取这层

第三层array保存本波敌人这一帧(我设置1.0秒,即1.0秒刷新一次)刷新出的敌人,addmonsters(float dt)将读取这层信息

玩过KingdomRush的知道这个游戏一般有2-5条道路,每条道路有3个分支,path保存的就是分支,Road保存就是道路,type保存怪物类型,-1表示这一个1.0s不刷新敌人

读取如下

void BaseMap::loadAndSetLevelData()
{
//加载初始血量金钱等
auto dataDic = Dictionary::createWithContentsOfFile(String::createWithFormat("level%d_%d_monsters.plist", getLevel(),difficulty)->getCString());
auto data_array = dynamic_cast<__Array*>(dataDic->objectForKey("data"));
auto data_tempDic = dynamic_cast<__Dictionary*>(data_array->getObjectAtIndex(0));
startGold = dynamic_cast<__String*>(data_tempDic->objectForKey("gold"))->intValue();
maxLife = dynamic_cast<__String*>(data_tempDic->objectForKey("life"))->intValue();
maxWave = dynamic_cast<__String*>(data_tempDic->objectForKey("wave"))->intValue();
//加载怪物数据
auto monsters_array = dynamic_cast<__Array*>(dataDic->objectForKey("monsters"));

for(int i =0 ;i < monsters_array->count();i++)
{
auto monster_array = dynamic_cast<__Array*>(monsters_array->getObjectAtIndex(i));
std::vector<Vector<GroupMonster*>> thisTimeMonster;
for(int j =0;j<monster_array->count();j++)
{
auto tempArray = dynamic_cast<__Array*>(monster_array->getObjectAtIndex(j));
Vector<GroupMonster*> monsterVector;
for(int k =0;k<tempArray->count();k++)
{
auto tempDic = dynamic_cast<__Dictionary*>(tempArray->getObjectAtIndex(k));
monsterVector.pushBack(GroupMonster::initGroupEnemy(
dynamic_cast<__String*>(tempDic->objectForKey("type"))->intValue(),
dynamic_cast<__String*>(tempDic->objectForKey("road"))->intValue(),
dynamic_cast<__String*>(tempDic->objectForKey("path"))->intValue()));
}
thisTimeMonster.push_back(monsterVector);
monsterVector.clear();
}
waveVector.push_back(thisTimeMonster);
thisTimeMonster.clear();
}
}


GroupMonster是用于保存敌人信息的类

class GroupMonster: public Node
{
public:
// virtual bool init();
static GroupMonster* initGroupEnemy(int type, int road, int path);
CREATE_FUNC(GroupMonster);
//保存怪物类型
CC_SYNTHESIZE(int, type, Type);
//不同出口
CC_SYNTHESIZE(int, road, Road);
//不同路线
CC_SYNTHESIZE(int, path, Path);
};

#endif


另外还需要读取本关路线,这个是原版数据包解压后就有的,例如Levelx.plist文件

void BaseMap::loadPathFromPlist()
{
winSize = Director::getInstance()->getWinSize();
auto plistDic = Dictionary::createWithContentsOfFile(String::createWithFormat("level%d_paths.plist",getLevel())->getCString());

auto path_array = dynamic_cast<__Array*>(plistDic->objectForKey("paths"));

for(int i = 0;i<path_array->count();i++)
{
std::vector<std::vector<Point>> tempPathVector;
auto path_array2 = dynamic_cast<__Array*>(path_array->getObjectAtIndex(i));
for(int j = 0;j<path_array2->count();j++)
{
std::vector<Point> tempRandomPathVector;
auto path_array3 = dynamic_cast<__Array*>(path_array2->getObjectAtIndex(j));
for(int k =0;k<path_array3->count();k++)
{
auto tempDic = dynamic_cast<__Dictionary*>(path_array3->getObjectAtIndex(k));
Point tempPath = Point();
tempPath.x = dynamic_cast<__String*>(tempDic->objectForKey("x"))->floatValue()*1.15;
tempPath.y = dynamic_cast<__String*>(tempDic->objectForKey("y"))->floatValue()*1.20+50;
tempRandomPathVector.push_back(tempPath);
}
tempPathVector.push_back(tempRandomPathVector);
}
path.push_back(tempPathVector);
}
}


因为原版游戏针对低分辨率和高分辨率,使用的是同一个plist文件,所以我猜还需要对路线进行不不同分辨率的修正,将X轴乘以1.15,Y轴乘以1.2加上50敌人就正好可以在高清版上正确行走了(高分辨率对应xxx-hd.png,低分辨率对应xxx.png,我只做了高清的)

其中

std::vector<std::vector<std::vector<Point>>> path;


path.at(x).at(x).at(x)即为敌人的路线

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

自定义一个精灵类来实现倒计时的图标,里面是一个ProgressTimer,用一个定时器来实现ProgressTimer的更新同时实现图标放大->缩小->放大这种呼吸灯的效果



#ifndef _WAVE_FLAG_H_
#define _WAVE_FLAG_H_

#include "cocos2d.h"

USING_NS_CC;

class WaveFlag : public Sprite
{
public:
virtual bool init();
CREATE_FUNC(WaveFlag);
//获得progressTimer百分比
float getWavePercentage();
//重新开始计时
void restartWaveFlag();
//停止计时
void stopRespiration();
//设置百分比,用于双击立即开始
void setWavePercentage(float per);
ProgressTimer* waveProgressTimer;
//点击后发光效果
Sprite* selected;
bool isShown;
void setSelected();
private:
float percentage;
//呼吸&计时效果定时器
void startRespiration(float dt);
};

#endif




接下来是Init()初始化这个图标

bool WaveFlag::Init()
{
if (!Sprite::init())
{
return false;
}
waveProgressTimer = ProgressTimer::create(Sprite::createWithSpriteFrameName("waveFlag_0003.png"));
waveProgressTimer->setType(ProgressTimer::Type::RADIAL);
auto flag = Sprite::createWithSpriteFrameName("waveFlag_0001.png");
flag->setPosition(Point(waveProgressTimer->getContentSize().width/2,waveProgressTimer->getContentSize().height/2));
selected = Sprite::createWithSpriteFrameName("waveFlag_selected.png");
selected->setPosition(Point(waveProgressTimer->getContentSize().width/2,waveProgressTimer->getContentSize().height/2));
waveProgressTimer->addChild(flag);
waveProgressTimer->addChild(selected);
selected->setVisible(false);
addChild(waveProgressTimer);
setScale(0.8f);
setVisible(false);
isShown = false;
return true;
}


对应图片可以数据包中找到

void WaveFlag::stopRespiration()
{
waveProgressTimer->setPercentage(100);
isShown = false;
setVisible(false);
unschedule(schedule_selector(WaveFlag::startRespiration));
}

void WaveFlag::restartWaveFlag()
{
isShown = true;
setVisible(true);
waveProgressTimer->setPercentage(0);
percentage = 0;
schedule(schedule_selector(WaveFlag::startRespiration),0.5f);
}
void WaveFlag::startRespiration(float dt)
{
waveProgressTimer->setPercentage(percentage);
runAction(Sequence::create(ScaleTo::create(0.25f,0.6f,0.6f),ScaleTo::create(0.25f,0.8f,0.8f),NULL));
percentage = percentage + 2.5f;
if(percentage >100){
isShown = false;
setVisible(false);
unschedule(schedule_selector(WaveFlag::startRespiration));
}
}
初始化地图后调用restartWaveFlag()来开始计时

调用startRespiration(float dt)来实现动态效果并且更新ProgressTimer()

下面是BaseMap的addWave函数

void BaseMap::addWaves(float dt)
{
for(int i = 0;i<waveFlags.size();i++){
if(waveFlags.at(i)->getWavePercentage() == 100.0f){
if(wave<maxWave)
{
isStart = true;
SoundManager::playIncomingWave();
wave ++;
for(int i = 0;i<waveFlags.size();i++){
waveFlags.at(i)->setWavePercentage(0.0f);
}
playerState->setWave(wave+1,maxWave);
waveEvent();
}
break;
}
}
}


当上面的waveFlag的ProgressTimer到达100%后,则开始刷新这一波的怪物,初始wave = -1,更新按键层玩家信息后,进入waveEvent()


void BaseMap::waveEvent()
{
schedule(schedule_selector(BaseMap::addMonsters), 1.0f, waveVector.at(wave).size(), 0);
}


waveEvent()在父类中只是开始addMonsters(float dt)的功能,没1.0f刷新一次怪(刷新最里层的一个array),刷新的怪物个数为读取文件的该层dict的个数。

在子类中,需要添加特殊的时间,比如上图猩猩BOSS这波怪,可以在此添加一些新时间,将最后的0S改成需要延时的时间,进行其他时间后再刷新这波敌人(比如酷炫的BOSS入场动画)

void BaseMap::addMonsters(float dt)
{
//waveVector.size()为波数
//waveVector.at()保存该wave怪物,size为怪物个数
//waveVector.at().at()保存该0.5s内需要创建的怪物,.size为怪物个数
if( time < waveVector.at(wave).size())
{
for(int i=0 ;i<waveVector.at(wave).at(time).size();i++)
{
auto monsterData = waveVector.at(wave).at(time).at(i);
switch (monsterData->getType())
{
case(0):{
auto thug = Thug::createMonster(path.at(monsterData->getRoad()).at(monsterData->getPath()));
addChild(thug);
GameManager::getInstance()->monsterVector.pushBack(thug);}
break;
<span style="white-space:pre">			</span>default:
break;
<span style="white-space:pre">	</span> <span style="white-space:pre">	</span>}
<span style="white-space:pre">			</span>}
<span style="white-space:pre">		</span>}
}
time ++;
}else{
time = 0;
if(wave!=maxWave-1)
//15秒后显示WaveProgressBar
{
SoundManager::playNextWaveReady();
scheduleOnce(schedule_selector(BaseMap::showWaveProgressBars),15.0f);
}else{
isEnd = true;
}
}
}
将路线赋给怪物,让怪物根据路线在地图上移动,即可实现怪物行走,怪物类将在下面章节中讲解

当这一层array刷新完毕,并且wave!=maxWave-1即还不是最后一波时

延迟15S将waveFlag执行上述restart,重新开始计时

若是最后一波,将IsEnd标记置为true,等待结束画面

主要流程就是

1根据自己的格式读取关卡信息,包括路线,怪物数量,类型等

2读取地图

3计时刷新

因为需要仿照原版游戏,我采用的是progressTimer来计时,通过读取他的百分比来判断是否开始新的一波

4添加怪物

根据自己设计的逻辑添加怪物即可

地图类第一张就差不多了,下一章将讲解触摸层
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐