[2048源码分析-2]游戏主场景

来源:互联网 发布:淘宝里哪里看装修日记 编辑:程序博客网 时间:2024/04/29 20:23

摘要:在这一篇里,阐述一下游戏主场景的工作机制,以及核心逻辑。


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个小网格,每个网格继承nodevirtual 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;#endifDirector::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到3uniform_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());}


0 0
原创粉丝点击