上面3张图是完成后的效果图
游戏已完成,除了英雄外,基本还原了90%的游戏内容,一共13关,20种防御塔,30+种敌人,如上图,以假乱真吧
下面从地图模块起介绍我的方法,如有更好的方法,请留言一起讨论,游戏资源下载原版游戏数据包,解压即可
推荐一款软件,TextureUnpackerRelease1.04可以分割pList形式的图片,提高效率
经过一个月学习也发现之前有很多化简为繁的错误
----------------------------------------------------------------------------------------------------------------------------------------------------
地图模块我采用3层结构,从下到上分别为地图层,触摸层,按键层
地图层负责地图的绘制,添加防御塔、敌人等,触摸层负责拦截一些触摸事件,添加技能/商店技能触摸响应,同时兼顾防御塔升级菜单d出层,按键层则负责技能和商店按键、玩家生命金钱状态,暂停按键,暂停菜单等。
-----------------------------------------------------------------------------------------------------------------------------------------------------
首先是地图层,新建一个基类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 > </span></array><span > </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 > </span>default: break; <span > </span> <span > </span>}<span > </span>}<span > </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添加怪物
根据自己设计的逻辑添加怪物即可
地图类第一张就差不多了,下一章将讲解触摸层
总结以上是内存溢出为你收集整理的[cocos2dx学习笔记]用cocos2dx3.X完成塔防游戏王国保卫战--地图(一)全部内容,希望文章能够帮你解决[cocos2dx学习笔记]用cocos2dx3.X完成塔防游戏王国保卫战--地图(一)所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)