自己学了快两个月的cocos2d-x了,前几天自己看着教学视频,跟着做了一个小游戏——别踩白块。
今天来说一说我自己是如何写这个项目的。逻辑有点乱,不知道看不看得懂。。。。
首先,这个游戏有两个场景,一个是开始场景,一个是失败场景。
开始场景如下:
开始场景里面有三类块:
- 起始块(黄色,有Start Game)
- 正常块(黑白块)
- 结束块(绿色,有You Win)
起始块如下:
正常块如下:
结束块如下:
因为这个游戏都是以块为单位的所以可以将这些块统称为抽象块,所以要给块来写一个 create 方法如下:
static Block * create(Size size,Color3B color,String str,float strSize,Color3B strColor);
这个函数里面有五个参数,第一个参数是块的大小 size ;第二个参数是块的颜色 color ;第三个参数是在块上要显示的字符串 str ;第四个参数是字符串的大小 strSize ;第五个参数是字符串的颜色 strColor 。
我们创建块的时候要确定块的大小,如
- 起始块:宽为,winSize.width;高为,winSize.height/4
- 正常块:宽为,winSize.width/4;高为,winSize.height/4
这里除以 4 是将屏幕给分成了4×4个正常块 - 结束块:宽为,winSize.width;高为,winSize.height
注意:这里的 winSize 就是屏幕的大小。
现在把 Block 类写好:
#ifndef __Block_H_
#define __Block_H_
#include "cocos2d.h"
USING_NS_CC;
class Block : public Sprite
{
public:
static Block * create(Size size,Color3B strColor);
bool init(Size size,Color3B strColor);
//类内部静态成员在单独的内存里的,
//在定义的时候已经存在了,它不属于某一个对象,只属于这个类
static Vector<Sprite *> vec; //用来存放已经创建好的块
static Vector<Sprite *> getBlockVector();
//产生get方法和set方法
CC_SYNTHESIZE(int,_LineIndex,LineIndex);
//向下移动块,并且清除
void moveDownAndCleanUp();
};
#endif
将 Block 类写好后,就可以开始写游戏逻辑了。
- 游戏一开是就进入开始场景,只要玩家一开始点击黑块游戏就会开始。
- 玩家只能按顺序点击黑块,即每次都是点击最下面的黑块。
- 玩家只要正确地点击了黑块,那个被点击的黑块就会变成灰色,
被点击那一行块就会往下移动,并且在屏幕上方添加一新行。 - 玩家在点击玩所有的黑块后,即赢得胜利,出现结束块。
- 若玩家点到了白块则游戏失败,切换到游戏失败的场景。
失败场景如下:
开始游戏,我们需要将屏幕最开始的布局给设置好,大家可能已经注意到 Block 类中有一个行号函数
CC_SYNTHESIZE(int,LineIndex);
行号函数就是用来设置(set)和得到(get)块的行号的,行号在屏幕上只有0到3行,
如下图所示
在开始的布局中第0行添加开始块,第 1,2,3行添加正常块。
在点击完所有的黑块后添加结束块。
void LayerGame::addStartLineBlock()
{
Size startBlockSize = Size(winSize.width,winSize.height/4);
Block * b = Block::create(startBlockSize,Color3B::YELLOW,"Start Game",30,Color3B::BLUE);
b->setPosition(Point(0,0));
this->addChild(b);
b->setLineIndex(0);//设置行号为0
_lineCount++;//行数+1,注意,不是行号,行号只有0,1,2,3这4行,而行数可以一直增加
}
void LayerGame::addNormalLineBlocks(int LineIndex)
{
Size normalBlockSize = Size(winSize.width/4,winSize.height/4);
int idx = rand()%4;//取一个0到3之间的随机值
for(int i = 0; i < 4; i++)//一下添加一行,即4块
{
Block * b = Block::create(normalBlockSize,i == idx ? Color3B::BLACK : Color3B::WHITE,/*设置黑白块*/
"",Color3B::WHITE);
b->setLineIndex(LineIndex);
//根据 i 和 LineIndex 来设置位置
b->setPosition(Point( i * winSize.width/4,LineIndex * winSize.height/4 ) );
this->addChild(b);
}
_lineCount++;//行数+1
}
void LayerGame::addEndLineBlock()
{
Block * b = Block::create(winSize,Color3B::GREEN,"You Win!",50,Color3B::RED);
b->setAnchorPoint(Point(0,0));
b->setPosition(Point(0,winSize.height));
this->addChild(b);
b->setLineIndex(4);
_lineCount++;//行数+1
//添加两个按钮 Again 和 Exit
LabelBMFont * bm = LabelBMFont::create("Again","fonts/arial-unicode-26.fnt");
LabelBMFont * bm1 = LabelBMFont::create("Exit","fonts/arial-unicode-26.fnt");
MenuItem * again = MenuItemLabel::create(bm);
MenuItem * ext = MenuItemLabel::create(bm1);
again->setTarget(this,menu_selector(LayerGame::tryAgainCallback));
ext->setTarget(this,menu_selector(LayerGame::exitCallback));
Menu * menu = Menu::create(again,ext,NULL);
b->addChild(menu);
menu->setPosition(Point::ZERO);
again->setPosition(Point(again->getBoundingBox().size.width/2,winSize.height-again->getBoundingBox().size.height/2));
ext->setPosition(Point(winSize.width-ext->getBoundingBox().size.width/2,winSize.height-ext->getBoundingBox().size.height/2));
}
到这里,我们就可以设置好最开始的布局了,写一个StatGame函数如下:
void LayerGame::startGame() { addStartLineBlock();
addNormalLineBlocks(1);
addNormalLineBlocks(2);
addNormalLineBlocks(3);
}
现在来有了最开始的布局了,就可以开始写点击事件了,因为我看的视频教程是cocos2d-x3.0以下的版本,而我自己用的却是3.0的版本,所以,就造成了我写的代码3.0以下的版本和3.0版本的混搭……
首先设置触摸监听:
//3.0版本
auto listener = EventListenerTouchOneByOne::create();
listener->setSwallowTouches(true);//吞噬
listener->onTouchBegan = CC_CALLBACK_2(LayerGame::onTouchBegan,this);
this->_eventDispatcher->addEventListenerWithSceneGraPHPriority(listener,this);
然后在写点击事件函数:
bool LayerGame::onTouchBegan(Touch *touch,Event *unused_event)
{
//遍历所有存在vec中的块 C++11方法
for (auto obj : Block::getBlockVector())
{
Block * b = (Block *)obj;
//判断是否点击到该块,且行号为1
if (b->boundingBox().containsPoint(touch->getLocation()) &&
b->getLineIndex() == 1)
{
if (b->getColor() == Color3B::BLACK)//点到黑块
{
//播放音效
SimpleAudioEngine::getInstance()->playEffect("onclick.wav");
//开始计时
this->startTimer();
//将被点击的黑块变灰
b->setColor(Color3B::GRAY);
//被点击的这行下移
this->moveDown();
}
else if (b->getColor() == Color3B::GREEN )//点到结束块
{
//播放音效
SimpleAudioEngine::getInstance()->playEffect("gamewin.wav");
//终止计时
this->stopTimer();
//下移
this->moveDown();
}
else if (b->getColor() == Color3B::WHITE)//点到白块
{
//播放音效
SimpleAudioEngine::getInstance()->playEffect("wrong.wav");
Scene * scene = LayerFailed::scene();
Director::getInstance()->replaceScene(scene);//切换到失败场景
}
break;
}
}
return false;
}
void LayerGame::moveDown()
{
if (this->getLineCount() < 20)//如果行号小于20
{
this->addNormalLineBlocks(4);//则添加一正常行,并设置行号为4
}
else if (!showEnd)//如果行号不小于20且标志showEnd为false
{
this->addEndLineBlock();//则添加结束块
showEnd = true;//并将showEnd设置为true
}
for (auto obj : Block::getBlockVector())//遍历这一行所有块
{
Block * b = (Block *)obj;
b->moveDownAndCleanUp();//将他们下移且从渲染树中删除
}
}
moveDown函数中又有一个moveDownAndCleanUp函数,我们把它写在了Block类里面
void Block::moveDownAndCleanUp()
{
//行号减1,因为这个函数是在moveDown函数的for循环里面,所以vec中所有的行号都减1
_LineIndex--;
//下移动作(时间,位移距离)
MoveTo * to = MoveTo::create(0.01,Point(getPositionX(),getPositionY()-winSize.height/4));
this->runAction(to);//执行动作
if (_LineIndex < 0)//判断当前行号是否小于0
{
//从渲染树上拿下来
this->removeFromParentAndCleanup(true);
//从vec中删除当前块
vec.eraSEObject(this);
}
}
上面moveDown函数和moveDownAndCleanUp函数都是为了实现玩家在点击时的滚屏效果。
前面说的都是游戏顺利完成的情况,下面来说,游戏失败的情况——在点击到白块时,游戏失败
我们用了一个 if 语句来判断是否点击到了白块:
else if (b->getColor() == Color3B::WHITE)
{
SimpleAudioEngine::getInstance()->playEffect("wrong.wav");
Scene * scene = LayerFailed::scene();
Director::getInstance()->replaceScene(scene);
}
如果踩到白块,则播放 wrong 音效,并且切换到游戏失败的场景。
首先说说计时功能:
1.首先设置一个标志和一个变量
long startTime = 0;//全部变量
bool isRunning = false;//全局变量
2.再创建一个 Label 来显示时间
LabelTTF * ttf = LabelTTF::create("0.000","Courier New",30); //ttf为全局变量
ttf->setZOrder(100);
ttf->setPosition(Point(winSize.width/2,winSize.height - 20));
ttf->setColor(Color3B::BLUE);
this->addChild(ttf);
3.然后再 写两个函数计时器startTimer和stopTimer
void LayerGame::startTimer()
{
if (!isRunning)//设置标志,只让其运行一次
{
this->scheduleUpdate();//开始帧循环定时器,每一帧都会调用默认的update函数
startTime = clock();//获取系统当前时间
isRunning = true;
}
}
void LayerGame::stopTimer()
{
if (isRunning)//只让其运行一次
{
this->unscheduleUpdate();//关闭帧循环定时器
}
}
4.然后将startTimer与stopTimer分别放入触摸响应函数的 if 条件中
if (b->getColor() == Color3B::BLACK)
{
SimpleAudioEngine::getInstance()->playEffect("onclick.wav");
this->startTimer();//开启计时器
b->setColor(Color3B::GRAY);
this->moveDown();
}
else if (b->getColor() == Color3B::GREEN )
{
SimpleAudioEngine::getInstance()->playEffect("gamewin.wav");
this->stopTimer();//关闭计时器
this->moveDown();
}
再说说播放音乐的功能:
1.首先要包含头文件
#include "SimpleAudioEngine.h"
using namespace CocosDenshion;
if (b->getColor() == Color3B::BLACK)
{
//播放点击音效
SimpleAudioEngine::getInstance()->playEffect("onclick.wav");
this->startTimer();
b->setColor(Color3B::GRAY);
this->moveDown();
}
else if (b->getColor() == Color3B::GREEN )
{
//播放胜利音效
SimpleAudioEngine::getInstance()->playEffect("gamewin.wav");
this->stopTimer();
this->moveDown();
}
else if (b->getColor() == Color3B::WHITE)
{
//播放失败音效
SimpleAudioEngine::getInstance()->playEffect("wrong.wav");
Scene * scene = LayerFailed::scene();
Director::getInstance()->replaceScene(scene);
}
好了,到这里差不多完成了。上面给了一些代码片段,若需要看完整代码,请移步至:(github)别踩白块
写得有点乱,若有不足之处还请斧正。