自己写的2048(基于3.0)

最近在学cocos2dx 3.0,看到2048这个游戏这么火,所以自己尝试一下,基本功能界面都可以了,就差移动时的动画没加进去,因为一加进去显示就出问题了,哪位大神要是看到了帮忙解决哈,先在这谢谢了~ :856:

从小就不爱写文章,写的不好大家体谅哈,废话不多说,我们开始。
首先是资源加载,这个界面就是为了加载图片资源,也没什么好说的,直接上代码

Tiled.cpp

 
#include "Tiled.h" 
//这个数组是方便等级和显示数字间的转换 
const int Tiled::nums={0,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768}; 
Tiled::Tiled(){ 
 //初始化的时候把级别设为零,0就是不显示 
 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)->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)->getCString()); 
} 

```
 

接下来是游戏图层的设计 
先把头文件贴上我们再细细说源文件

GameLayer.h 
 
#ifndef _GameLayer_H_ 
#define _GameLayer_H_ 
#include "cocos2d.h" 
#include "Tiled.h" 
USING_NS_CC; 
using namespace std; 
class GameLayer:public Layer{ 
public: 
 virtual bool init(); 
 CREATE_FUNC(GameLayer); 
 GameLayer(); 
 void gameInit(); 
 Point touchDown; 
 static int score; 
 Label *lScore; 
private: 
 Tiled* tables; 
 virtual bool onTouchBegan(Touch *touch, Event *unused_event); 
 virtual void onTouchMoved(Touch *touch, Event *unused_event); 
 virtual void onTouchEnded(Touch *touch, Event *unused_event); 
 //四个移动方向,返回是否有砖块移动过 
 bool moveToTop(); 
 bool moveToDown(); 
 bool moveToLeft(); 
 bool moveToRight(); 
 void swapTiled(Tiled *tiled1,Tiled * tiled2); 
 void addTiled(); 
 bool isOver(); 
}; 
#endif 

```
 

首先是界面的初始化 

 
bool GameLayer::init(){ 
 bool bRet=false; 
 do{ 
  CC_BREAK_IF(!Layer::init()); 
  auto cache=SpriteFrameCache::getInstance(); 
  Size size=Director::getInstance()->getVisibleSize(); 
  //添加背景 
  auto background=Sprite::createWithSpriteFrame(cache->spriteFrameByName("background.png")); 
  background->setPosition(Point(size.width/2,size.height/2)); 
  this->addChild(background); 
  //添加标题背景 
  auto headBg=Sprite::createWithSpriteFrame(cache->spriteFrameByName("title_bg.png")); 
  headBg->setAnchorPoint(Point(0,1)); 
  headBg->setPosition(Point(0,640)); 
  this->addChild(headBg,1); 
  //添加退出和重新开始按钮 
  auto exitItem=MenuItemSprite::create(Sprite::createWithSpriteFrame(cache->spriteFrameByName("exit_norm.png")), 
  Sprite::createWithSpriteFrame(cache->spriteFrameByName("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->spriteFrameByName("restart_norm.png")), 
  Sprite::createWithSpriteFrame(cache->spriteFrameByName("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->spriteFrameByName("game_bg.png")); 
  gameBg->setAnchorPoint(Point::ZERO); 
  gameBg->setPosition(Point(5,5)); 
  this->addChild(gameBg); 
  //添加分数背景 
  auto scoreBg=Sprite::createWithSpriteFrame(cache->spriteFrameByName("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; 
} 

```
 

然后是游戏的初始化 
游戏初始化时要做的事情有: 
①创建16个砖块并存放在一个数组中,然后将所有砖块的级别设为0,不可见; 
②随机产生四个数来作为初始化的两个砖块的坐标,注意这里两个砖块的坐标不能重复,具体实现代码里有 
为了方便数组坐标和实际的像素坐标的转换,我们先写个转换的宏 
#define RC_CONVERT_TO_XY(rc) (rc*105+60) 

 
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); 
   tables=tiled; 
  } 
 } 
 //获取两个随机坐标 
 //c++11的随机数产生方式 
 default_random_engine e(time(NULL)); 
 //这里是设定产生的随机数的范围,这里是0到3 
 uniform_int_distribution 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; 
 int isFour=e()%10; 
 if(isFour==0){ 
  tiled1->level=2; 
  tiled1->backround->setDisplayFrame(cache->spriteFrameByName(String::createWithFormat("level2.png")->getCString())); 
  tiled1->label->setString("4"); 
  tiled1->setVisible(true); 
 }else{ 
  tiled1->level=1; 
  tiled1->backround->setDisplayFrame(cache->spriteFrameByName(String::createWithFormat("level1.png")->getCString())); 
  tiled1->label->setString("2"); 
  tiled1->setVisible(true); 
 } 
 //添加第二个砖块 
 auto tiled2=tables; 
 isFour=e()%10; 
 if(isFour==0){ 
  tiled2->level=2; 
  tiled2->backround->setDisplayFrame(cache->spriteFrameByName(String::createWithFormat("level2.png")->getCString())); 
  tiled2->label->setString("4"); 
  tiled2->setVisible(true); 
 }else{ 
  tiled2->level=1; 
  tiled2->backround->setDisplayFrame(cache->spriteFrameByName(String::createWithFormat("level1.png")->getCString())); 
  tiled2->label->setString("2"); 
  tiled2->setVisible(true); 
 } 
} 

```
 

下来是事件监听函数 
我们记录手指按下和抬起的坐标,首先判断是否滑动超过一定距离,超过一定距离才算滑动过,然后根据横向坐标差和纵向坐标差判断是上下滑动还是左右滑动,最后判断是向哪个方向并调用响应的函数 

 
bool GameLayer::onTouchBegan(Touch *touch, Event *unused_event){ 
 this->touchDown=touch->getLocationInView(); 
 this->touchDown=Director::getInstance()->convertToGL(this->touchDown); 
 return true; 
} 
void GameLayer::onTouchMoved(Touch *touch, Event *unused_event){ 
} 
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(); 
   } 
  } 
 } 
} 

```
 


接下来是四个方向移动时调用的函数,这里参考了http://www.cocoachina.com/bbs/read.php?tid=197710http://www.cocoachina.com/bbs/read.php?tid=197710的算法,我说一下向下移动的算法,其他方向都差不多,只是遍历顺序变了。 
首先向上移动肯定是一列一列的移动,所以这里从最左边到最右边遍历或从最右边到最左边遍历都可以,但是在遍历行的时候就不一样了,因为我们在玩的时候会发现方块会优先合并靠近板边的,所以在向下移动的时候就要从下向上遍历,他这个算法遍历了两遍,第一遍是遍历到一个不为空的方块时,看看这一列上有没有和这个方块级别一样的方块,如果有就合并两个方块,并将分数相加,第二遍是遍历到一个空格,然后看这一列上有没有不为空的,如果有不为空的就把那个不为空的格子移到这个空格子上。 
 
bool GameLayer::moveToDown(){ 
 bool hasMoved=false; //这个变量以后再解释 
 //将数字相同的格子合一 
 for(int col=0;col<4;col++){ 
  for(int row=0;row<4;row++){ 
   //遍历的每一次获得的方块 
   auto tiled=tables; 
   //找到不为空的方块 
   if(tiled->level!=0){ 
    int k=row+1; 
    //看这一列有没有等级和这个方块等级相同的 
    while(k<4){ 
     auto nextTiled=tables; 
     if(nextTiled->level!=0){ 
      if(tiled->level==nextTiled->level){ 
       //找到等级和这个砖块等级相同的就把他们合并 
       tiled->setLevel(nextTiled->level+1); 
       nextTiled->setLevel(0); 
       nextTiled->setVisible(false); 
       GameLayer::score+=Tiled::nums; 
       //将砖块上的分数加入总分 
       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; 
   //找到空格子 
   if(tiled->level==0){ 
    int k=row+1; 
    while(k<4){ 
     auto nextTiled=tables; 
     if(nextTiled->level!=0){ 
      //将不为空的格子移到这里 
      tiled->setLevel(nextTiled->level); 
      nextTiled->setLevel(0); 
      tiled->setVisible(true); 
      nextTiled->setVisible(false); 
      hasMoved=true; 
      k=4; 
     } 
     k++; 
    } 
   } 
  } 
 } 
return hasMoved; 
} 

```
 

然后是每次移动后添加一个新的方块到界面,这个和初始化的时候差不多,就是加了一个从小变大的动画,就不多说了,这个函数在移动完方块后调用
 
 
void GameLayer::addTiled(){ 
 auto cache=SpriteFrameCache::getInstance(); 
 //获取两个随机坐标 
 default_random_engine e(time(NULL)); 
 uniform_int_distribution u(0,3); 
 int row=0; 
 int col=0; 
 do{ 
  row=u(e); 
  col=u(e); 
 }while(tables->level!=0); 
 //添加砖块 
 auto tiled=tables; 
 int isFour=e()%10; 
 if(isFour==0){ 
  tiled->level=2; 
  tiled->backround->setDisplayFrame(cache->spriteFrameByName(String::createWithFormat("level2.png")->getCString())); 
  tiled->label->setString("4"); 
  tiled->setVisible(true); 
 }else{ 
  tiled->level=1; 
  tiled->backround->setDisplayFrame(cache->spriteFrameByName(String::createWithFormat("level1.png")->getCString())); 
  tiled->label->setString("2"); 
  tiled->setVisible(true); 
 } 
 tiled->setScale(0.5,0.5); 
 tiled->runAction(ScaleTo::create(0.1f,1.0f)); 
} 

```
 

现在我们说下砖块移动中的hasMoved变量的作用,因为我们在玩的过程中总有移动后砖块填满整个屏幕但是还没有over的时候,这个时候如果如果滑动屏幕即使没有砖块移动还是会调用添加砖块的函数,而这个时候就会因为找不到空格子而陷入死循环,所以这个变量就是指示有没有砖块移动,如果本次滑动没有砖块移动的话就不调用添加砖块的函数了 
所以我们要在砖块添加前调用这个 
 
if(hasMoved){ 
addTiled(); 
} 

```
 

判断是否结束了 
我这里的判断方法就是看每个格子的周围有没有空格子,如果有空格子肯定不结束,如果没有空格子就看有没有和自己级别一样的格子,如果都不满足就GAME OVER了,这个判断函数要加在每次添加新砖块的函数后边 
 
bool GameLayer::isOver(){ 
 for(int row=0;row<4;row++){ 
  for(int col=0;col<4;col++){ 
   //判断是否存在空格子 
   if(tables->level==0){ 
    //有空格子肯定不会OVER 
    return false; 
   } 
   //判断周围格子,如果存在相等的数字则不OVER 
   //上 
   int c=col; 
   int r=row+1; 
   if(r!=-1&&r!=4){ 
    if(tables->level==tables->level){ 
     return false; 
    } 
   } 
   //左 
   c=col-1; 
   r=row; 
   if(c!=-1&&c!=4){ 
    if(tables->level==tables->level){ 
     return false; 
    } 
   } 
   //右 
   c=col+1; 
   r=row; 
   if(c!=-1&&c!=4){ 
    if(tables->level==tables->level){ 
     return false; 
    } 
   } 
   //下 
   c=col; 
   r=row-1; 
   if(r!=-1&&r!=4){ 
    if(tables->level==tables->level){ 
     return false; 
    } 
   } 
  } 
 } 
 return true; 
} 

```
 

最后就是结束时收尾的工作,首先判断当前分数是否比最高分大,如果大就把当前分数保存下来,然后显示游戏结束的界面,这个界面就不说了,肯定分分钟就搞定了 
 
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())); 
} 

```
 
吼吼~终于写完了,第一次写,写的不太好,有什么不对和不足的地方希望大家提出来哈,希望大家早日吃透cocos2dx,写出一个很牛叉的游戏,冲击TOP10,哈哈~ 
最后奉上源代码和用到的素材,素材都是自己画的,丑是丑了点,凑合看吧 :882: 
代码和素材.rar (224 KB)

谢谢,没有完整的工程吗?

顶你一个,顺便带走。。。楼主真好。

DAY DAY UP! :2: .

还是来请教一下楼主吧。
编译的时候总是不过,undefined reference to `GameLayer::GameLayer()’
CMakeLists.txt文件里面GAME_SRC也添加了Classes/GameLayer.cpp
编译仍然不过,想不出什么办法了,麻烦楼主指点一下
谢谢!!

mark mark!!!

mark!!:2::2::2::2:

我的锤子,第一次,还写这么多,还写的这么好,这是在装么?!:882:

额 这个我也不清楚啊 要不你把邮箱给我我把那几个文件发给你你再看看?:967:

木有木有 我这么诚实的孩子怎么能在装呢 哈哈 总之很感谢大家的支持 要是觉得写得还不错 那以后有时间再写一个 嘿嘿:882:

资源是不是少?感谢楼至分享

年轻人加油啊。明天靠你了:2::2::2:

编译的时候总是不过:

1>g:\project\2048\classes\appdelegate.cpp(18): error C2653: “GLView”: 不是类或命名空间名称
1>g:\project\2048\classes\appdelegate.cpp(18): error C3861: “create”: 找不到标识符
1> LoadingScene.cpp
1>g:\project\2048\classes\helloworldscene.h(16): error C2039: “Ref”: 不是“cocos2d”的成员 (…\Classes\HelloWorldScene.cpp)
1>g:\project\2048\classes\helloworldscene.h(16): error C2061: 语法错误: 标识符“Ref” (…\Classes\HelloWorldScene.cpp)
1>g:\project\2048\classes\helloworldscene.cpp(79): error C2065: “Ref”: 未声明的标识符
1>g:\project\2048\classes\helloworldscene.cpp(79): error C2065: “pSender”: 未声明的标识符
1>g:\project\2048\classes\helloworldscene.cpp(80): error C2448: “HelloWorld::menuCloseCallback”: 函数样式初始值设定项类似函数定义

新手,不知道错误原因,请指教。

你用的是cocos2dx 3.0么?

资源应该没少吧,你少哪个?我发给你

是啊,cocos2dx3.0beta,

谢谢阿,363508133@qq.com

你把Ref换成Object就行了

发过去了~~~~~~~~~~~~

亲 你做cocos多久了 ? 向你学习