首页 > 代码库 > Cocos2dx学习之-----别踩白块V1.0

Cocos2dx学习之-----别踩白块V1.0

学了点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写的

Cocos2dx学习之-----别踩白块V1.0