摘要:在这一篇里,阐述一下游戏主场景的工作机制,以及核心逻辑。
GameScene只有一个函数(重载)
bool GameScene::init(){
bool bRet=false;
do{
CC_BREAK_IF(!Scene::init());
auto layer=GameLayer::create();
CC_BREAK_IF(!layer);
this->addChild(layer);
bRet=true;
}while(0);
return bRet;
}
函数功能单一:加载GameLayer
看一下GameLayer.h
#ifndef _GameLayer_H_
#define _GameLayer_H_
#include "cocos2d.h"
#include "Tiled.h"
USING_NS_CC;
using namespace std;
class GameLayer:public Layer{
private:
virtual bool init();
Vec2 touchDown;//触摸点
Label *lScore;//得分
public:
CREATE_FUNC(GameLayer);
//GameLayer();
void gameInit(); //初始化网格
static int score;
private:
Tiled* tables[4][4];//包含4*4个小网格,每个网格继承node
virtual bool onTouchBegan(Touch *touch, Event *unused_event);
virtual void onTouchEnded(Touch *touch, Event *unused_event);
void move(int, int, int, int);//移动的效果显示
//四个移动方向,返回是否有砖块移动过
bool moveToTop();
bool moveToDown();
bool moveToLeft();
bool moveToRight();
void addTiled();
bool isOver();
};
#endif
大部分的函数和成员都有注释,就不做解释。看一下GameLayer.cpp。
GameLayer保存4*4个Tiled 对象(下面会介绍Tiled对象)。当用户移动的时候,发生的事情:
1)如果有消除的Tiled。则对其中一个Tiled对象level+1,另一个Tiled对象level置为0.
2)移动。移动时,当从位置(fi,fj)移动(ti,tj)时, tables[ ti ][ tj] 的节点需要removeFromParent(), 然后 tables[ti][tj]=tables[fi][fj],
tables[fi][fj]需要重新生成一个Tiled 对象,同时this->addChild 。这样的逻辑过程可以保证一直有16个对象,不会增多。
#define RC_CONVERT_TO_XY(rc) (rc*105+60)
这个宏是把坐标点转换成场景的位置。脑子里要清楚的是tables的对象必须要通过this->addChild()才能显示到Lay中。
先看init()函数,init()函数做以下事:
1)加载背景
2)加载标题
3)加载分数
4)重新开始和退出按钮
5)网格部分初始化
6)添加触摸监听事件
bool GameLayer::init(){
bool bRet=false;
do{
CC_BREAK_IF(!Layer::init());
auto cache=SpriteFrameCache::getInstance(); //共享的帧序列
auto size=Director::getInstance()->getVisibleSize();
//添加背景
auto background=Sprite::createWithSpriteFrame(cache->getSpriteFrameByName("background.png"));
background->setPosition(Point(size.width/2,size.height/2));
this->addChild(background);
//添加标题背景
auto headBg=Sprite::createWithSpriteFrame(cache->getSpriteFrameByName("title_bg.png"));
headBg->setAnchorPoint(Vec2(0,1));
headBg->setPosition(Vec2(0, 640));
this->addChild(headBg,1);
//添加退出和重新开始按钮
//这里用了新的c++语法,lambda表达式,换成以前的语法就是CALL_BACK函数
auto exitItem=MenuItemSprite::create(Sprite::createWithSpriteFrame(cache->getSpriteFrameByName("exit_norm.png")),
Sprite::createWithSpriteFrame(cache->getSpriteFrameByName("exit_press.png")),NULL,[](Ref *psender){
#if(CC_TARGET_PLATFORM==CC_PLATFORM_WP8||CC_TARGET_PLATFORM==CC_PLATFORM_WINRT)
MessageBox("You pressed the close button. Windows Store Apps do not implement a close button.","Alert");
return;
#endif
Director::getInstance()->end();
#if(CC_TARGET_PLATFORM==CC_PLATFORM_IOS)
exit(0);
#endif
});
exitItem->setPosition(Point(65,600));
auto restartItem=MenuItemSprite::create(Sprite::createWithSpriteFrame(cache->getSpriteFrameByName("restart_norm.png")),
Sprite::createWithSpriteFrame(cache->getSpriteFrameByName("restart_press.png")),NULL,[=](Ref *psender){
Director::getInstance()->replaceScene(GameScene::create());
});
restartItem->setPosition(Point(375,600));
auto menu=Menu::create(exitItem,restartItem,NULL);
menu->setAnchorPoint(Point::ZERO);
menu->setPosition(Point::ZERO);
this->addChild(menu,2);
//添加砖块部分背景
auto gameBg=Sprite::createWithSpriteFrame(cache->getSpriteFrameByName("game_bg.png"));
gameBg->setAnchorPoint(Point::ZERO);
gameBg->setPosition(Point(5,5));
this->addChild(gameBg);
//添加分数背景
auto scoreBg=Sprite::createWithSpriteFrame(cache->getSpriteFrameByName("score_bg.png"));
scoreBg->setAnchorPoint(Point::ZERO);
scoreBg->setPosition(Point(5,435));
this->addChild(scoreBg);
//添加分数显示
lScore=Label::create("0","Arial",40);
lScore->setPosition(Point(size.width/4,470));
this->addChild(lScore);
//添加最高分显示
int high=UserDefault::getInstance()->getIntegerForKey("HighScore",0);
auto hScore=Label::create(String::createWithFormat("%d",high)->getCString(),"Arial",40);
hScore->setPosition(Point(size.width/4*3,470));
this->addChild(hScore);
//初始化游戏界面
gameInit();
//添加监听器
auto listener=EventListenerTouchOneByOne::create();
listener->setSwallowTouches(true);
listener->onTouchBegan=CC_CALLBACK_2(GameLayer::onTouchBegan,this);
//listener->onTouchMoved=CC_CALLBACK_2(GameLayer::onTouchMoved,this);
listener->onTouchEnded=CC_CALLBACK_2(GameLayer::onTouchEnded,this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener,this);
bRet=true;
}while(0);
return bRet;
}
看一下网格初始化函数 gameInit()
void GameLayer::gameInit(){
GameLayer::score=0;
auto cache=SpriteFrameCache::getInstance();
//初始化砖块
for(int i=0;i<4;i++){
for(int j=0;j<4;j++){
auto tiled=Tiled::create();
//tiled->level=0;
tiled->setAnchorPoint(Point::ZERO);
tiled->setPosition(Point(RC_CONVERT_TO_XY(j),RC_CONVERT_TO_XY(i)));
tiled->setVisible(false);
this->addChild(tiled,1);//把tiled加入到layer中,别忘记哦
tables[i][j]=tiled;
}
}
//获取两个随机坐标
//c++11的随机数产生方式
default_random_engine e(time(NULL));
//这里是设定产生的随机数的范围,这里是0到3
uniform_int_distribution<unsigned> u(0,3);
int row1=u(e);
int col1=u(e);
int row2=u(e);
int col2=u(e);
//这个循环是保证两个砖块的坐标不会重复
do{
row2=u(e);
col2=u(e);
}while(row1==row2&&col1==col2);
//添加第一个砖块
auto tiled1=tables[row1][col1];
int isFour = e() % 10;
if(isFour==0){
tiled1->setLevel(2);
tiled1->setVisible(true);
}else{
tiled1->setLevel(1);
tiled1->setVisible(true);
}
//添加第二个砖块
auto tiled2=tables[row2][col2];
isFour = e() % 10;
if(isFour==0){
tiled2->setLevel(2);
tiled2->setVisible(true);
}else{
tiled2->setLevel(1);
tiled2->setVisible(true);
}
}
bool GameLayer::onTouchBegan(Touch *touch, Event *unused_event){
this->touchDown=touch->getLocationInView();
this->touchDown=Director::getInstance()->convertToGL(this->touchDown);
return true;
}
onTouchBegan函数记录下触摸点。convertToGL把屏幕坐标系改成cocos2d采用的坐标系
onTouchEnded函数是核心逻辑。当触摸结束后,1)判断移动方向,2)方块的消除,移动。3)增加新的元素块 4)判断游戏是否结束
void GameLayer::onTouchEnded(Touch *touch, Event *unused_event){
bool hasMoved=false;
Point touchUp=touch->getLocationInView();
touchUp=Director::getInstance()->convertToGL(touchUp);
if(touchUp.getDistance(touchDown)>50){
//判断上下还是左右
if(abs(touchUp.x-touchDown.x)>abs(touchUp.y-touchDown.y)){
//左右滑动
if(touchUp.x-touchDown.x>0){
//向右
log("toRight");
hasMoved=moveToRight();
}else{
//向左
log("toLeft");
hasMoved=moveToLeft();
}
}else{
//上下滑动
if(touchUp.y-touchDown.y>0){
//向上
log("toTop");
hasMoved=moveToTop();
}else{
//向下
log("toDown");
hasMoved=moveToDown();
}
}
if(hasMoved){
addTiled();
}
if(isOver()){
//存放分数
int high=UserDefault::getInstance()->getIntegerForKey("HighScore",0);
if(GameLayer::score>high){
UserDefault::getInstance()->setIntegerForKey("HighScore",GameLayer::score);
UserDefault::getInstance()->flush();
}
GameLayer::score=0;
//切换画面
Director::getInstance()->replaceScene(TransitionSlideInB::create(1.0f,OverScene::createScene()));
}
}
}
四个方向的逻辑差不多,区别就在于两个for循环。比如我从上往下。就需要从最下面往上找可以消除的块。如果从左往右,则从右往左边找可以消除的块。因此下面只针对moveToDown()介绍。
我们把过程分成两部分:
1)针对每一块tiled,寻找可以消除的块,如果找到,则将该块level设置为0,找到的那一块level+1.(这里设置那一块而不是当前块是为了有移动的效果)
2)移动所有可以移动的块。
bool GameLayer::moveToDown(){
bool hasMoved=false;
for (int col = 0; col<4; col++){
for (int row = 0; row<4; row++){
//遍历的每一次获得的方块
auto tiled = tables[row][col];
//找到不为空的方块
if (tiled->getLevel() != 0){
int k = row + 1;
//看这一列有没有等级和这个方块等级相同的
while (k<4){
auto nextTiled = tables[k][col];
if (nextTiled->getLevel() != 0){
if (tiled->getLevel() == nextTiled->getLevel()){
//找到等级和这个砖块等级相同的就把他们合并
tiled->setLevel(nextTiled->getLevel() + 1);
nextTiled->setLevel(0);
nextTiled->setVisible(false);
GameLayer::score += Tiled::nums[tiled->getLevel()];
this->lScore->setString(String::createWithFormat("%d", GameLayer::score)->getCString());
hasMoved = true;
}
k = 4;
}
k++;
}
}
}
}
//将有数的格子填入空格子
for (int col = 0; col<4; col++){
for (int row = 0; row<4; row++){
//遍历每一次的砖块
auto tiled = tables[row][col];
//找到空格子
if (tiled->getLevel() == 0){
int k = row + 1;
while (k<4){
auto nextTiled = tables[k][col];
if (nextTiled->getLevel() != 0){
//将不为空的格子移到这里
move(k, col, row, col);
hasMoved = true;
k = 4;
}
k++;
}
}
}
}
return hasMoved;
}
move函数如下,思想就是之前说的那样,removeFromParent一个节点,就addChild另一个。
void GameLayer::move(int fr, int fc,int tr,int tc)
{
Tiled* from = tables[fr][fc];
Tiled* to = tables[tr][tc];
from->runAction(MoveTo::create(0.15, Point(RC_CONVERT_TO_XY(tc), RC_CONVERT_TO_XY(tr))));
tables[tr][tc] = from;
to->removeFromParent();
auto n_tiled = Tiled::create();
//tiled->level=0;
n_tiled->setAnchorPoint(Point::ZERO);
n_tiled->setPosition(Point(RC_CONVERT_TO_XY(fc), RC_CONVERT_TO_XY(fr)));
n_tiled->setVisible(false);
this->addChild(n_tiled, 1);
tables[fr][fc] = n_tiled;
}
Tiled类继承Node.包含一个sprite和一个label
#ifndef _Tiled_H_
#define _Tiled_H_
#include "cocos2d.h"
USING_NS_CC;
class Tiled:public Node{
private:
int level;
Sprite *backround;
Label *label;
public:
static const int nums[16];
Tiled();
virtual bool init();
CREATE_FUNC(Tiled);
void setLevel(int l);
int getLevel()
{
return level;
}
};
#endif
#include "Tiled.h"
const int Tiled::nums[16]={0,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768};
Tiled::Tiled(){
level=0;
}
bool Tiled::init(){
bool bRet=false;
do{
CC_BREAK_IF(!Node::init());
auto cache=SpriteFrameCache::getInstance();
this->backround=Sprite::createWithSpriteFrame(cache->spriteFrameByName("level0.png"));
this->backround->setPosition(Point::ZERO);
this->addChild(backround);
this->label=Label::create(String::createWithFormat("%d",Tiled::nums[level])->getCString(),"Arial",40);
this->label->setPosition(Point::ZERO);
this->addChild(label,1);
bRet=true;
}while(0);
return bRet;
}
void Tiled::setLevel(int l){
auto cache=SpriteFrameCache::getInstance();
this->level=l;
this->backround->setDisplayFrame(cache->spriteFrameByName(String::createWithFormat("level%d.png",level)->getCString()));
this->label->setString(String::createWithFormat("%d",Tiled::nums[level])->getCString());
}