书生教你cocos2d-x-保卫萝卜(四)

这一篇博客我们做选关界面,内容没什么复杂的,之后就可以做游戏场景了。先看看效果图。

选关界面里结构如下。

一个背景一个开始按钮。然后中间部分是关卡的预览内容。鼠标(手指)拖动的时候内容画面会跟着动,松开鼠标(手指),画面会定格到相应关卡。

往左滑动是下一关,往右滑是上一关。

×××地址

创建背景以及开始按钮返回按钮什么的我就略过不提了。

重点内容有2块:

1.创建关卡预览内容。

我们一共9个关卡,每个关卡的预览内容有一个地图的预览背景,和一个塔的图标(标志着这个关卡能出现哪些塔)。

保卫萝卜把这些资源都做成图片了,包括每个关卡的预览图以及下面的塔的小图标,并没有重复利用地图里的资源。这样做是非常可怕和浪费的,不过对于我们学习来说,省了不少功夫。

我做了一个xml来记录这些信息。

 
一共9个关卡,每个关卡有2个熟悉,预览图和小图标。
不建议大家直接把这些写死在代码里,会造成难以修改。
同样的我们在代码里构造了相关的类来读取这些信息。
class LevelSummary:public cocos2d::CCObject{
public:
static LevelSummary* create(std::string bg_name,std::string towers_icon_name);
virtual ~LevelSummary();
private:
LevelSummary();
bool init(std::string bg_name,std::string towers_icon_name);
CC_SYNTHESIZE_READONLY(std::string ,bg_name,BgName);
CC_SYNTHESIZE_READONLY(std::string ,towers_icon_name,TowerIconName);
};
 
class LevelsSummary:public cocos2d::CCObject{
public:
static LevelsSummary* ShardLevelsSummary();
virtual ~LevelsSummary();
bool init();
LevelSummary* GetLevel(int index);
private:
LevelsSummary();
CC_SYNTHESIZE_READONLY(int ,level_count,LevelCount);
cocos2d::CCArray* levels_array;
};
由于我们现在只记录了关卡预览所需要的信息,因此我称这个类为levelSummary,如果之后把每关的出兵序列也加进来,就变成levelbase了。
每个levelSummary 有2个熟悉

CC_SYNTHESIZE_READONLY(std::string ,bg_name,BgName);
CC_SYNTHESIZE_READONLY(std::string ,towers_icon_name,TowerIconName);

预览的背景图的名字,和图标的名字。

而存储和管理它们的类为LevelsSummary(这里容易混淆,只多了个S,如果改成LevelsManager更合理)。

这是一个单例。他给我们提供了一个接口GetLevel让我们能拿到每一关的信息。

bool LevelsSummary::init(){
tinyxml2::XMLDocument* doc=new tinyxml2::XMLDocument();
doc->LoadFile("levels_summary.xml");
tinyxml2::XMLElement *root_node=doc->RootElement();
std::string count_str=   root_node->Attribute("count");
this->level_count=cocos2d::CCString::create(count_str)->intValue();
tinyxml2::XMLElement *level_node=root_node->FirstChildElement("level");
 
levels_array=cocos2d::CCArray::create();
levels_array->retain();
while (level_node)
{
std::string bg=level_node->Attribute("bg");
std::string towers_icon=level_node->Attribute("towers_icon");
LevelSummary* ls=LevelSummary::create(bg,towers_icon);
levels_array->addObject(ls);
level_node=level_node->NextSiblingElement();
}
delete doc;
 
returntrue;
}

在创建这个类的时候,会读取xml里的内容,把每关的信息读进内存里。

下面看选关界面内如何根据这个类创建我们要显示的内容。

levels_node=cocos2d::CCNode::create();
this->addChild(levels_node);
levels_node->setPosition(ccp(0,0));
cocos2d::CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile("stages_theme1.plist");
int start_btn_y=0;
for(int i=0;i
std::string temp_map_name=level_summary->GetLevel(i)->getBgName();
std::string temp_icon_name=level_summary->GetLevel(i)->getTowerIconName();
 
cocos2d::CCSprite* level_map_bac=cocos2d::CCSprite::createWithSpriteFrameName(temp_map_name.c_str());
levels_node->addChild(level_map_bac);
level_map_bac->setPosition(ccp(win_size.width/2+i*win_size.width,win_size.height/2));
cocos2d::CCSprite* level_towers_icon=cocos2d::CCSprite::createWithSpriteFrameName(temp_icon_name.c_str());
levels_node->addChild(level_towers_icon);
level_towers_icon->setPosition(ccp(win_size.width/2+i*win_size.width,win_size.height/2-level_map_bac->getContentSize().height/2
-level_towers_icon->getContentSize().height/2));
if(start_btn_y==0){
start_btn_y=(level_towers_icon->getPositionY()-level_towers_icon->getContentSize().height/2)/2;
}
}

中间的部分是可以滑动的,所以我们单开了一个节点levels_node来存储这些内容。当手指滑动屏幕时,这个节点会跟着偏移。

节点默认初始位置是屏幕左下角。之后遍历所有的关卡。创建出关卡背景预览图和小图标。

第一个关卡的背景图是在屏幕正中央,而之后的关卡预览图每个向右偏移半个屏幕大小。

诺,效果如图。

读取并创建完这些信息后,我们实现手指滑动改变当前关卡的操作。

void SelectLevelLayer::ccTouchesBegan(CCSet *pTouches, CCEvent *pEvent){
cocos2d::CCTouch* touch=dynamic_cast
(pTouches->anyObject());
 
start_drag_point=touch->getLocation();
start_drag_level_node_point=levels_node->getPosition();
}

当我们手指按下时,记录此时手指的坐标start_drag_point,以及此时levels_node的坐标start_drag_level_node_point

void SelectLevelLayer::ccTouchesMoved(CCSet *pTouches, CCEvent *pEvent){    cocos2d::CCTouch* touch=dynamic_cast
(pTouches->anyObject());float px=touch->getLocation().x-start_drag_point.x;float py=0;float new_node_point_x=start_drag_level_node_point.x+px;float new_node_point_y=start_drag_level_node_point.y+py; levels_node->setPosition(ccp(new_node_point_x,new_node_point_y));}

在手指滑动时,取得此时手指的坐标,算出偏移值

然后利用偏移值px和刚才记录的关卡节点的坐标start_drag_level_node_point,算出新的坐标,赋给节点,就可以实现关卡跟着手指移动的效果了。

void SelectLevelLayer::ccTouchesEnded(CCSet *pTouches, CCEvent *pEvent){
cocos2d::CCTouch* touch=dynamic_cast
(pTouches->anyObject());
int new_level_index= this->select_level_index;
 
float px=touch->getLocation().x-start_drag_point.x;
if(px>200){
//右移 上一关
new_level_index=this->select_level_index-1;
if(new_level_index<0){
new_level_index=0;
}
 
}elseif(px<-200){
//左移,下一关
new_level_index=this->select_level_index+1;
if(new_level_index>  LevelsSummary::ShardLevelsSummary()->getLevelCount()-1){
new_level_index=LevelsSummary::ShardLevelsSummary()->getLevelCount()-1;
}
}
this->ChangeSelectLevel(new_level_index);
}

        最后,当手指抬起时,我们用此时的坐标和刚才按下时记录的坐标进行比较。看看到底用户是想切换到下一关还是上一关。

        右移是上一关,左移是下一关,同时我们设置如果移动的偏移没有超过200像素则该操作无效。并且我们对关卡进行了保护,不能切换到-1或是大于关卡总数的关卡索引。

         最后根据我们得到的关卡索引new_level_index校准关卡节点的坐标。本例中它的范围是0-8,因为我们之后9关。

ChangeSelectLevel(int index)函数是根据最后得到的关卡索引校准关卡节点的坐标,因为我们不能让关卡节点保持上图那种样子,最后比如让玩家选中的关卡的预览信息处于屏幕正中。

void SelectLevelLayer::ChangeSelectLevel(int new_level_index){    cocos2d::CCSize win_size=cocos2d::CCDirector::sharedDirector()->getWinSize();    select_level_index=new_level_index;    levels_node->setPositionX(-1*select_level_index*win_size.width);}

当选中第0关时,节点x坐标是0。每增加一个关卡,则节点整体左移一个屏幕的偏移,使得对应关卡预览图在屏幕中央。

效果如图。同时我们记录了这个关卡的索引,知道当前选的是第几关。

选关界面到底结束。点击开始按钮后,我们更具当前选的关卡id,去找到关卡信息(可能我将这些内容添加到levelsummary里,也可能单独开个类)。然后根据关卡的具体信息创建场景,切换过去。

游戏场景里的内容我尽快更新。

除去界面,场景里的东西无非下面这些,大家可以先思考一下如何实现:

1,萝卜,10点血

2,怪物,出现后,按路径向萝卜移动

3,防御塔,会***范围内的怪物

4,障碍物,阻挡我们建塔,打掉后有奖励,并且可以空出空间造塔

5,地图

6,×××

由于部分×××有减速效果,我们可能还要写个buff类。

今天就更新到这里,大家下次见