[cocos2dx学习笔记]用cocos2dx3.X完成塔防游戏王国保卫战--地图(一)

[cocos2dx学习笔记]用cocos2dx3.X完成塔防游戏王国保卫战--地图(一),第1张

概述上面3张图是完成后的效果图 游戏已完成,除了英雄外,基本还原了90%的游戏内容,一共13关,20种防御塔,30+种敌人,如上图,以假乱真吧 下面从地图模块起介绍我的方法,如有更好的方法,请留言一起讨论,游戏资源下载原版游戏数据包,解压即可 推荐一款软件,TextureUnpackerRelease1.04可以分割plist形式的图片,提高效率 经过一个月学习也发现之前有很多化简为繁的错误 ----




上面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完成塔防游戏王国保卫战--地图(一)所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://www.outofmemory.cn/web/1052826.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-25
下一篇 2022-05-25

发表评论

登录后才能评论

评论列表(0条)

保存