学了点COCOS2DX,一直感觉也没什么大意思,所以就找个教程做个小游戏出来,反正国庆在校也没啥事,自娱自乐。
PS;我用的版本是cocos2dx3.2
一、创建项目
cocos new -p com.donttouchwhiteblock.xuran -l cpp -d .
二、创建block类
因为别猜白块里面最重要的一个元素就是“块”,所以我们要为这个元素创建一个类,然后实例化一些方法,以便完成游戏中的诸多行为
首先是gameblock.h文件
#pragma once #include <iostream> #include <cocos2d.h> USING_NS_CC; class Block:public Sprite { public: static GameBlock* CreateWithArgs(Color3B color, Size size, std::string label, float fontsize, Color3B textcolor); virtual bool initWithArgs(Color3B color, Size size, std::string label, float fontsize, Color3B textcolor); void removeblock(); private: static Vector<GameBlock*> *blocks; };
头文件里面定义了三个成员函数:
第一个是创建一个block,根据所获取到的参数
第二个是初始化一个新的块根据参数。
第三个是移除一个块
还有创建了一个block指针类型的数组,用于存储我们创建的block的对象。
其次是GameBlock.cpp文件
#include "GameBlock.h" Vector<GameBlock*> *GameBlock::blocks = new Vector<GameBlock*>(); GameBlock* GameBlock::CreateWithArgs(Color3B color, Size size, std::string label, float fontsize, Color3B textcolor) { auto b = new GameBlock(); b->initWithArgs(color, size, label, fontsize, textcolor); b->autorelease(); blocks->pushBack(b); return b; } void GameBlock::removeblock() { removeFromParent(); blocks->eraseObject(this); //删除向量中特定的对象 } bool GameBlock::initWithArgs(Color3B color, Size size, std::string label, float fontsize, Color3B textcolor) { Sprite::init(); setContentSize(size); setAnchorPoint(Point::ZERO); setTextureRect(Rect(0,0,size.width, size.height)); setColor(color); auto l = Label::create(); l->setString(label); l->setSystemFontSize(fontsize); l->setColor(textcolor); addChild(l); l->setPosition(size.width/2, size.height/2); return true; }
有对block数组的初始化,一个块的初始化函数,包括各种参数的设置以及一个创建函数,一个释放删除函数。
三、添加开始条
创建了块对应的类和相应的方法之后,我们就要开始真正的一步一步来做了。首先,别踩白块的游戏在开始的时候是有一条黄色的部分,代表着起点。那么首先就来为我们的游戏添加这个黄色的起点块。
很简单,从HelloWorldScene文件里面添加一个新的函数叫做AddStartLine,函数的实现很简单,就是通过CreateWithArgs函数创建一个块,然后添加进来即可。
bool HelloWorld::init() { ////////////////////////////// // 1. super init first if ( !Layer::init() ) { return false; } visibleSize = Director::getInstance()->getVisibleSize(); AddStartLine(); return true; } void HelloWorld::AddStartLine() { auto b = GameBlock::CreateWithArgs(Color3B::YELLOW, Size(visibleSize.width, visibleSize.height/8), "Start", 20, Color3B::BLACK); addChild(b); }
既然开始条是这么添加的,那么结束条应该也是同理,再次就不再多做介绍了。
添加完开始和结束之后,我们要添加的就是普通的黑白块。
四、添加黑白块
首先我们要在类里面加入一个添加黑白块的方法叫做AddNormal
我们一般玩的别踩白块的版本的游戏通常一排有四个块,黑快是随机的出现在这四块中的其中一块,其余的都是白块。
既然是随机的,那么我们很自然的就要用到随机数。
利用rand函数产生一个0-4之间的随机数用来确定黑块的位置。
我们要有这样的一个概念,程序现在面对着一块白色的画布,我们要按着行的顺序,在按照列的顺序依次把每一行的白块和黑块都画好。
那么首先,我们需要创建一个块,究竟创建的是黑快还是白块,由上面的随机数来确定
void HelloWorld::AddNormal(int lineindex) { int blackindex = rand()%4; GameBlock *b; for(int i = 0; i < 4; i++) { auto b = GameBlock::CreateWithArgs(blackindex == i?Color3B::BLACK:Color3B::WHITE, Size(visibleSize.width/4-1, visibleSize.height/4-1), "", 20, Color3B::BLACK); addChild(b); b->setPosition(i*visibleSize.width/4, lineindex*visibleSize.height/4); b->setlineindex(lineindex); } }
每排有四个块,我们是一排一排的进行设置。传进来的参数代表的是排数。
设置一个for循环,用来设定一行中的四个快。
首先创建一个块,因为上面生成一个blackindex的随机数,所以黑块的位置也就相应的指定了。添加到场景中之后,就要设置这个黑块的准确位置。
首先我们要把我们的屏幕宽度分为四部分,设置哪一部分是靠i来决定的,随意每一块的x坐标是用i来乘上一个块的宽度大小,纵坐标呢,当然是要设置第几行的黑白块,所以是靠我们传进来的要设置第几行来决定的。
void HelloWorld::StartGame() { AddStartLine(); AddNormal(1); AddNormal(2); AddNormal(3); }
最后我们直接再添加一个开始游戏的函数,里面调用添加这些块的方法,在init函数里面运行这个即可看到效果。
五、事件交互
事件交互实际上就是你对这个游戏,或者对屏幕上的事务做出了点击,那么这个事务理应给一些一些反馈。
别再白块中的时间交互其实只有一个,就是你点黑快,游戏继续,你点白块游戏结束。就是这样。
我们以第一行为例,首先如果是要对一些操作做出反应的话,那么我们就需要一个监听器,来监听动作的发生,你可以选择监控一个控件是否被操作了,或者是监控整个场景是否被操作了。
首先我们要创建一个触摸事件的监听器
auto listener = EventListenerTouchOneByOne::create();
如果这个监听器接受到了触摸信号,那么他肯定要对这个触摸的动作做出反应,调用一个函数进行反应操作。
listener->onTouchBegan = [this](Touch *t, Event *e){ log("xuran is winner"); GameBlock *b; auto bs = GameBlock::getBlocks(); for(auto it = bs->begin(); it != bs->end(); it++) { b = *it; if(b->getlineindex() == 1 && b->getBoundingBox().containsPoint(t->getLocation())) { if(b->getColor() == Color3B::BLACK) { b->setColor(Color3B::GRAY); this->movedown(); } else { MessageBox("游戏失败", "GameOver"); } } } return false; };
所以在这里用了一个闭包函数。
这个闭包函数里面出现了一些新的函数,比如getblocks。这个函数是在GameBlock类里面进行定义的,他的作用就是返回存储块的vector,因为假如我创建了第一层,那么第一层肯定会创建四个块,其中一个黑块,三个白块,这些块里面都会有一个lineindex的属性,可以表明他们是第几行的块,这些块也都会加入到这个数组里面,所以在交互的时候,肯定要判断是触摸了白块还是触摸了黑块,所以要利用迭代器的遍历来进行确认。
listener->onTouchBegan
也表明了这是对触碰事件的一个回调函数。
在遍历数组中的块时,因为是游戏开始,为了能够正常的让游戏开始,我们首先要确定,vector中的第一个块是第一行的,并且这个块的范围是包含我们的触点的,由于这个if已经能够确定我们的手指触碰到了游戏中的块时,接下来要判断的就是触摸的是白块还是黑块。
如果是黑快,那么按下的黑块会变为黑色,并且把这一行下移,如果是白块的话,那么提示游戏失败。
bool HelloWorld::init() { ////////////////////////////// // 1. super init first if ( !Layer::init() ) { return false; } visibleSize = Director::getInstance()->getVisibleSize(); StartGame(); auto listener = EventListenerTouchOneByOne::create(); listener->onTouchBegan = [this](Touch *t, Event *e){ log("xuran is winner"); GameBlock *b; auto bs = GameBlock::getBlocks(); for(auto it = bs->begin(); it != bs->end(); it++) { b = *it; if(b->getlineindex() == 1 && b->getBoundingBox().containsPoint(t->getLocation())) { if(b->getColor() == Color3B::BLACK) { b->setColor(Color3B::GRAY); this->movedown(); } else { MessageBox("游戏失败", "GameOver"); } } } return false; }; Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, this); //对当前场景进行监听 return true; }
六、设计游戏逻辑
这个游戏的逻辑很简单,不比2048.无非就是点击黑快所有的块向下移动,一直到结束,点击白块游戏失败。
所以说,主要实现的一个函数就是movedown。movedown的功能其实也很简单,就是把所有的块都要向下移动。首先在helloworldscene里面的movedown函数里面要遍历整个块的数组,依次让这些块执行下移的动作,至于这个具体的下移方法,要在块类的内部实现,因为这毕竟是一个块的行为。
首先,每个移动的块的所在行数,也就是lineindex都会随着移动的发生而减掉1.
其次便是要为每一个块执行一个下移动作
void GameBlock::moveon() { Size visable = Director::getInstance()->getVisibleSize(); this->lineindex--; //可以向下移动的行,移动之后所在的行会减去1 runAction(Sequence::create(MoveTo::create(0.1f,Point(getPositionX(), lineindex*visable.height/4)),CallFunc::create([this]() { if(lineindex < 0) { this->removeblock(); } } ),NULL)); //执行一个移动的动作,把一个块移动到对应的位置上,间隔0.1 }
这里面的runaction函数将执行一个系列动作,执行完块的移动之后还要调用一个函数,如果这个块被移出了屏幕,那么他就应该被销毁,调用removeblock函数实现。
其次,我们要设置一个游戏的终点,也就是说游戏过程中如果一直没有踩到白块,那么究竟什么时候停止呢。
一般来说踩50个黑快之后应该游戏就停止了。
所以说要在helloworldscene类里面设置一个linecount,每次添加一行的普通块时都要加1,在所有的块向下移动的时候也是要判断,如果已经踩了50块了,那么再添加到屏幕顶部的就不应该是正常的 黑白块而是最终结束的绿色的游戏界面。
并且,在下移块的函数中,如果已经添加了一个游戏结束的绿色块,那么下次就不用添加了,直接下移就可以,因为一个游戏结束块的尺寸是整个屏幕,不用再多添加几个。
bool HelloWorld::init() { ////////////////////////////// // 1. super init first if ( !Layer::init() ) { return false; } visibleSize = Director::getInstance()->getVisibleSize(); StartGame(); auto listener = EventListenerTouchOneByOne::create(); listener->onTouchBegan = [this](Touch *t, Event *e){ //log("xuran is winner"); GameBlock *b; auto bs = GameBlock::getBlocks(); for(auto it = bs->begin(); it != bs->end(); it++) { b = *it; if(b->getlineindex() == 1 && b->getBoundingBox().containsPoint(t->getLocation())) { if(b->getColor() == Color3B::BLACK) { b->setColor(Color3B::GRAY); this->movedown(); } else if (b->getColor() == Color3B::GREEN) { this->movedown(); } else { MessageBox("游戏失败", "GameOver"); } break; } } return false; }; Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, this); //对当前场景进行监听 return true; } void HelloWorld::movedown() { if(linecount < 50) { AddNormal(4); //在屏幕最上面添加一行,因为设置了屏幕内一次最多存在四行的块 } else if(!showend) { showend = true; AddEndLine(); } auto bs = GameBlock::getBlocks(); for(auto it = bs->begin(); it != bs->end(); it++) { (*it)->moveon(); } }
七、添加计时器
http://blog.csdn.net/xr_acmer/article/details/38706835关于计时器的使用我这里应该写了一些。
我是用的是scheduleupdate这个计时器,所以自己先定义一个update函数 。
之后我们要定义两个函数,一个是启动计时器的函数,一个是终止计时器的函数,无论是开始计时器还是终止计时器都只需要做一次,所以用一个bool变量来控制他的运行。
void HelloWorld::update(float dt) { long offet = clock()-gametime; timelabel->setString(StringUtils::format("%g", ((double)offet)/1000)); } void HelloWorld::starttime() { if(!timerrunning) { gametime = clock(); //获取当前系统运行这个程序的时间。 scheduleUpdate(); //开始执行计时器 timerrunning = true; } } void HelloWorld::endtime() { if(timerrunning) { unscheduleUpdate(); //停止执行计时器 timerrunning = false; } }
scheduleupdate这个计时器会根据每一帧的变化调用update函数。
因为我们要在游戏界面中添加一个计时器,这个计时器是不断变化的,但是随着块的移动很可能会覆盖掉计时器,所以我们就想能够把游戏和计时器分开,把他们分别存在两个层中,这样一来两这就都不影响了。
gamelayer = Node::create(); addChild(gamelayer); timelabel = Label::create(); timelabel->setColor(Color3B::RED); timelabel->setSystemFontSize(38); timelabel->setPosition(visibleSize.width/2, visibleSize.height-50); timelabel->setString("0.000\""); addChild(timelabel);
创建一个新的层叫做gamelayer,之后再创建一个label标签,用于时刻显示时间,并添加到我当前的层里,把之前所有的游戏里面添加开始快结束快的函数里面的addchild都变成gamelayer->addchild(b),也就是说把游戏当中的元素都添加到游戏层里面,计时器添加到当前的层里面。
if(b->getlineindex() == 1 && b->getBoundingBox().containsPoint(t->getLocation())) { if(b->getColor() == Color3B::BLACK) { if(!timerrunning) { this->starttime(); } b->setColor(Color3B::GRAY); this->movedown(); } else if (b->getColor() == Color3B::GREEN) { this->movedown(); this->endtime(); } else { MessageBox("游戏失败", "GameOver"); } break; }
最后根据踩的是黑快还是绿快要确定计时器的停止和开始。
PS:第一版基本就是这样了,算是可以玩了,但是还是有很多需要改进完善的地方,以后有心情再搞
https://github.com/Harkphoenix/DontTouchWhiteBlock 源码在这里,我是用vs写的