学了点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.youkuaiyun.com/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写的