网上找了好多教程写2048,不过都没有实现卡片的移动动画,自己写了一个不太完美的带动画版。
开发步骤:
1,设计一个CardSprite类。
2,设计主游戏场景GameScene,实现游戏逻辑,添加动画逻辑。
4,添加声音等其他元素,专门弄了一个声音预加载的场景,主场景添加声音切换变量存储。
贴上主场景关键代码:
GameScene.h
- #pragmaonce
- #include"cocos2d.h"
- #include"cardSprite.h"
- #include"MenuLayer.h"
- classGameScene:publiccocos2d::Layer
- {
- public:
- staticcocos2d::Scene*createScene();
- virtualboolinit();
- CREATE_FUNC(GameScene);
- //触摸监听
- boolonTouchBegan(cocos2d::Touch*touch,cocos2d::Event*event);//注意这里要加命名空间作用域cocos2d
- virtualvoidonTouchEnded(cocos2d::Touch*touch,cocos2d::Event*event);
- //上下左右滑动动作
- boolmoveLeft();
- boolmoveRight();
- boolmoveUp();
- boolmoveDown();
- //创建4*4卡片矩阵
- voidcreateCardArr(Sizesize);
- voidrandomCreateCard();
- //判断游戏赢输
- voidcheckGameWin();
- voidcheckGameOver();
- voidrestart(Ref*sender);//重新开始游戏菜单项
- private:
- intscore;//当前分数
- intbestscore;//最好分数
- boolsound;//声音变量
- cocos2d::LabelTTF*scoreLabel;
- LabelTTF*restartBtn;//重新开始的按钮
- LabelTTF*isSoundBtn;//声音切换按钮
- CardSprite*cardArr[4][4];//数字卡片矩阵
- CardSprite*cardArrAction[4][4];//用于动画的临时数字卡片矩阵
- PointstartPt;//触摸开始点
- intoffsetX,offsetY;//触摸水平和竖直方向偏移量
- MenuLayer*menuLayer;//菜单层
- timevaltv;//当前时间
- };
copy
/*
*game:2048
*author:tashaxing
*time:2014/10/12
*/
#include"GameScene.h"
#include"SimpleAudioEngine.h"
usingnamespacecocos2d;
namespaceCocosDenshion;
Scene*GameScene::createScene()
{
autoscene=Scene::create();
autolayer=GameScene::create();
scene->addChild(layer);
returnscene;
}
boolGameScene::init()
if(!Layer::init())
returnfalse;
//获得屏幕尺寸和原点
SizevisibleSize=Director::getInstance()->getVisibleSize();
Vec2origin=Director::getInstance()->getVisibleOrigin();
//添加背景
autogameBkGround=LayerColor::create(Color4B(180,170,255));
this->addChild(gameBkGround);
//添加标题
autotitle=LabelTTF::create("My2048","Arial",60);
title->setColor(Color3B(255,153));
title->setPosition(Point(visibleSize.width/2,visibleSize.height-50));
this->addChild(title);
//加入restart按钮
restartBtn=LabelTTF::create("Restart",40);
restartBtn->setColor(Color3B(204,253));
restartBtn->setPosition(Point(visibleSize.width/2,visibleSize.height-110));
this->addChild(restartBtn);
//添加声音切换按钮
//初始化获取最好分数和声音变量,第一次启动应用的话xml里没有任何值,所以下面的会返回0和false
sound=UserDefault::getInstance()->getBoolForKey("SOUND");
if(sound)
isSoundBtn=LabelTTF::create("SoundOn",153); font-weight:bold; background-color:inherit">else
isSoundBtn=LabelTTF::create("SoundOff",108); list-style:decimal-leading-zero outside; color:inherit; line-height:18px; margin:0px!important; padding:0px 3px 0px 10px!important"> isSoundBtn->setColor(Color3B(204,248)"> isSoundBtn->setPosition(Point(visibleSize.width/2,50));
this->addChild(isSoundBtn);
//加入游戏分数
autoslabel=LabelTTF::create("Score",30);
slabel->setPosition(Point(visibleSize.width/5,visibleSize.height-150));
this->addChild(slabel);
score=0;
scoreLabel=LabelTTF::create("0",108); list-style:decimal-leading-zero outside; color:inherit; line-height:18px; margin:0px!important; padding:0px 3px 0px 10px!important"> scoreLabel->setColor(Color3B(0,37));
scoreLabel->setPosition(Point(visibleSize.width/2+30,visibleSize.height-150));
this->addChild(scoreLabel);
bestScore=UserDefault::getInstance()->getIntegerForKey("BEST");
//初始化卡片
createCardArr(visibleSize);
randomCreateCard();
randomCreateCard();
//添加触摸监听
autolistener=EventListenerTouchOneByOne::create();
listener->onTouchBegan=CC_CALLBACK_2(GameScene::onTouchBegan,this);
listener->onTouchEnded=CC_CALLBACK_2(GameScene::onTouchEnded,153); font-weight:bold; background-color:inherit">this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener,153); font-weight:bold; background-color:inherit">true;
voidGameScene::restart(Ref*sender)
//转场,重新开始游戏
Director::getInstance()->replaceScene(TransitionFade::create(0.7f,GameScene::createScene()));
boolGameScene::onTouchBegan(Touch*touch,Event*event)
gettimeofday(&tv,NULL);//记录当前时间
startPt=touch->getLocation();//保存开始触摸点
//判断如果触摸点在restart按钮区域内则重新开始
if(restartBtn->getBoundingBox().containsPoint(restartBtn->convertToNodeSpace(touch->getLocation())))
Director::getInstance()->replaceScene(TransitionFade::create(0.7f,GameScene::createScene()));
//声音开关
if(isSoundBtn->getBoundingBox().containsPoint(isSoundBtn->convertToNodeSpace(touch->getLocation())))
sound=!sound;
UserDefault::getInstance()->setBoolForKey("SOUND",sound);
if(sound)
isSoundBtn->setString("SoundOn");
else
isSoundBtn->setString("SoundOff");
voidGameScene::onTouchEnded(Touch*touch,248)"> timevaltv_end;
gettimeofday(&tv_end,NULL);
if(tv_end.tv_sec-tv.tv_sec>3)
//开个后门,用来测试游戏赢了
cardArr[0][3]->setNumber(2048);
checkGameWin();
}
autoendPt=touch->getLocation();//获得触摸结束点
offsetX=endPt.x-startPt.x;//计算偏移
offsetY=endPt.y-startPt.y;
boolisTouch=false;
if(abs(offsetX)>abs(offsetY))//判断为方向
if(offsetX<-5)
isTouch=moveLeft();
elseif(offsetX>5)
isTouch=moveRight();
if(offsetY>5)//注意这里的纵向坐标别弄反
isTouch=moveDown();
if(offsetY<-5)
isTouch=moveUp();
if(isTouch)//如果滑动成功则判断
scoreLabel->setString(String::createWithFormat("%d",score)->getCString());
//这三个的顺序不能乱
checkGameWin();
checkGameOver();
voidGameScene::createCardArr(Sizesize)
intspace=5;//卡片间的间隔
intcardSize=(size.width-4*space)/4;
//创建卡片矩阵
for(inti=0;i<4;i++)
intj=0;j<4;j++)
//最左边留白12,最下面留白size.height/6
//坐标从左下角算起,右为正,上为正
CardSprite*card=CardSprite::createCard(0,cardSize,cardSize*i+12,cardSize*j+12+size.height/6);
this->addChild(card);//一定要把card添加到子节点才能渲染出来
cardArr[i][j]=card;//存到卡片矩阵
//创建临时卡片矩阵,用于动画,每个动画卡片对应一个实际卡片的动画,这是个技巧,并且动画层在卡片层之上,所以后加入,也可以设置addchild层次
inti=0;i<4;i++)
intj=0;j<4;j++)
//最左边留白12,最下面留白size.height/6
this->addChild(card);
cardArrAction[i][j]=card;
//一开始把这层全部因此
autohide=Hide::create();
cardArrAction[i][j]->getCardLayer()->runAction(hide);
voidGameScene::randomCreateCard()
//在随机位置生成卡片
introw=CCRANDOM_0_1()*4;
intcol=CCRANDOM_0_1()*4;
if(cardArr[row][col]->getNumber()>0)//如果有数字,则递归调用
cardArr[row][col]->setNumber(CCRANDOM_0_1()*10<1?4:2);//有10%的几率生成4
//用动画效果生成
autoaction=Sequence::createWithTwoActions(ScaleTo::create(0,0),ScaleTo::create(0.3f,1));//在0.3秒内从小缩放到大
cardArr[row][col]->getCardLayer()->runAction(action);//用卡片的层而不是卡片精灵本身做动作是为了使用局部坐标缩放
//向左滑动游戏逻辑,其他方向类似
boolGameScene::moveLeft()
//是否有移动的逻辑变量,如果没有任何移动,则不需要随机生成卡片,也不检验赢输,这一点很关键,否则很容易出bug
boolmoved=//计算移动的步进间距
autocardSize=(Director::getInstance()->getVisibleSize().width-5*4)/4;
//y表示行标号,x表示列标号
inty=0;y<4;y++)//最外层的行遍历可以先不管
intx=0;x<4;x++)//内部的N^2复杂度的类似冒泡排序
intx1=x+1;x1<4;x1++)
if(cardArr[x1][y]->getNumber()>0)//x右边的卡片有数字才动作
if(cardArr[x][y]->getNumber()==0)
//专门弄一个动画层卡片实现定位、显现、移动、隐藏系列动画
autoplace=Place::create(Point(cardSize*x1+12,cardSize*y+12+Director::getInstance()->getVisibleSize().height/6));
cardArrAction[x1][y]->setNumber(cardArr[x1][y]->getNumber());//每次都重新把动画卡片重新定位到实际对应的卡片位置,并设置相同的数字
autoshow=Show::create();
automove=MoveBy::create(0.1f,Point(-cardSize*(x1-x),0));//注意移动的距离
autohide=Hide::create();
cardArrAction[x1][y]->getCardLayer()->runAction(Sequence::create(place,show,move,hide,NULL));
//如果x位置是空卡片,就把x1卡片移到x处,x1处变成空卡片
cardArr[x][y]->setNumber(cardArr[x1][y]->getNumber());
cardArr[x1][y]->setNumber(0);
x--;//再扫描一遍,确保所有结果正确
moved=true;
if(cardArr[x][y]->getNumber()==cardArr[x1][y]->getNumber())
cardArrAction[x1][y]->setNumber(cardArr[x1][y]->getNumber());
//如果x位置非空,且与x1处数字相同,则乘2
cardArr[x][y]->setNumber(cardArr[x][y]->getNumber()*2);
cardArr[x1][y]->setNumber(0);
//数字合并动画
automerge=Sequence::create(ScaleTo::create(0.1f,1.2f),ScaleTo::create(0.1f,1.0f),NULL);
cardArr[x][y]->getCardLayer()->runAction(merge);
score+=cardArr[x][y]->getNumber();
//播放得分声音
SimpleAudioEngine::getInstance()->playEffect("get.mp3");
moved=break;//此处break防止出现连续乘2的bug
returnmoved;
boolGameScene::moveRight()
intx=3;x>=0;x--)intx1=x-1;x1>=0;x1--)
//x左边的卡片有数字才动作
autoplace=Place::create(Point(cardSize*x1+12,cardSize*y+12+Director::getInstance()->getVisibleSize().height/6));
cardArrAction[x1][y]->setNumber(cardArr[x1][y]->getNumber());
autoshow=Show::create();
automove=MoveBy::create(0.1f,0); background-color:inherit">//注意移动的距离
cardArrAction[x1][y]->getCardLayer()->runAction(Sequence::create(place,NULL));
x++;
//注意移动的距离,此处算出来为正
automerge=Sequence::create(ScaleTo::create(0.1f,248)"> cardArr[x][y]->getCardLayer()->runAction(merge);
score+=cardArr[x][y]->getNumber();
SimpleAudioEngine::getInstance()->playEffect("get.mp3");
boolGameScene::moveUp()//这里的“上”是逻辑上往坐标值小的方向,在屏幕上实际是往下动
//最外层的列遍历可以先不管
inty1=y+1;y1<4;y1++)
if(cardArr[x][y1]->getNumber()>0)//x下边的卡片有数字才动作
autoplace=Place::create(Point(cardSize*x+12,cardSize*y1+12+Director::getInstance()->getVisibleSize().height/6));
cardArrAction[x][y1]->setNumber(cardArr[x][y1]->getNumber());
cardArrAction[x][y1]->getCardLayer()->runAction(Sequence::create(place,108); list-style:decimal-leading-zero outside; color:inherit; line-height:18px; margin:0px!important; padding:0px 3px 0px 10px!important"> cardArr[x][y]->setNumber(cardArr[x][y1]->getNumber());
cardArr[x][y1]->setNumber(0);
y--;
if(cardArr[x][y]->getNumber()==cardArr[x][y1]->getNumber())
autoplace=Place::create(Point(cardSize*x+12,cardSize*y1+12+Director::getInstance()->getVisibleSize().height/6));
cardArrAction[x][y1]->setNumber(cardArr[x][y1]->getNumber());
cardArrAction[x][y1]->getCardLayer()->runAction(Sequence::create(place,108); list-style:decimal-leading-zero outside; color:inherit; line-height:18px; margin:0px!important; padding:0px 3px 0px 10px!important"> cardArr[x][y1]->setNumber(0);
boolGameScene::moveDown()//这里的“下”是逻辑上往坐标值小的方向,在屏幕上实际是往上动
inty=3;y>=0;y--)inty1=y-1;y1>=0;y1--)
//x上边的卡片有数字才动作
y++;
voidGameScene::checkGameWin()
boolisWin=if(2048==cardArr[i][j]->getNumber())
isWin=if(isWin)
//播放音效
SimpleAudioEngine::getInstance()->playEffect("gamewin.mp3");
//有一个2048游戏就是赢了
/*初始化菜单层*/
menuLayer=MenuLayer::create(Color4B(0,100));
this->addChild(menuLayer);
automenuSize=menuLayer->getContentSize();
automenuTitle=LabelTTF::create("YOUWIN",30);
menuTitle->setPosition(menuSize.width/2,menuSize.height/2+50);
menuLayer->addChild(menuTitle);
//添加当前分数
automenuscoreLabel=LabelTTF::create(String::createWithFormat("current:%d",score)->getCString(),20);
menuscoreLabel->setPosition(menuSize.width/2,menuSize.height/2);
menuLayer->addChild(menuscoreLabel);
//添加最好分数
bestScore=UserDefault::getInstance()->getIntegerForKey("BEST");
if(score>bestScore)
bestScore=score;
UserDefault::getInstance()->setIntegerForKey("BEST",bestScore);
automenuBestscoreLabel=LabelTTF::create(String::createWithFormat("best:%d",bestScore)->getCString(),248)"> menuBestscoreLabel->setPosition(menuSize.width/2,menuSize.height/2-30);
menuLayer->addChild(menuBestscoreLabel);
MenuItemFont::setFontName("Arial");
MenuItemFont::setFontSize(25);
automenuItemRestart=MenuItemFont::create("Restart",this,menu_selector(GameScene::restart));
menuItemRestart->setColor(Color3B(255,0));
automenu=Menu::create(menuItemRestart,108); list-style:decimal-leading-zero outside; color:inherit; line-height:18px; margin:0px!important; padding:0px 3px 0px 10px!important"> menuLayer->addChild(menu);
menu->setPosition(Point(menuSize.width/2,menuSize.height/2-80));
voidGameScene::checkGameOver()
boolisGameOver=//以下情况则游戏继续
if((cardArr[i][j]->getNumber()==0)||
(i>0&&cardArr[i][j]->getNumber()==cardArr[i-1][j]->getNumber())||
(i<3&&cardArr[i][j]->getNumber()==cardArr[i+1][j]->getNumber())||
(j>0&&cardArr[i][j]->getNumber()==cardArr[i][j-1]->getNumber())||
(j<3&&cardArr[i][j]->getNumber()==cardArr[i][j+1]->getNumber()))
isGameOver=//否则游戏结束
if(isGameOver)
SimpleAudioEngine::getInstance()->playEffect("gameover.mp3");
/*初始化菜单层*/
menuLayer=MenuLayer::create(Color4B(0,100));
this->addChild(menuLayer);
automenuSize=menuLayer->getContentSize();
//添加标题
automenuTitle=LabelTTF::create("GAMEOVER",108); list-style:decimal-leading-zero outside; color:inherit; line-height:18px; margin:0px!important; padding:0px 3px 0px 10px!important"> menuTitle->setPosition(menuSize.width/2,menuSize.height/2+50);
menuLayer->addChild(menuTitle);
//添加当前分数
automenuscoreLabel=LabelTTF::create(String::createWithFormat("current:%d",20);
menuscoreLabel->setPosition(menuSize.width/2,menuSize.height/2);
menuLayer->addChild(menuscoreLabel);
//添加最好分数
if(score>bestScore)
bestScore=score;
UserDefault::getInstance()->setIntegerForKey("BEST",bestScore);
automenuBestscoreLabel=LabelTTF::create(String::createWithFormat("best:%d",108); list-style:decimal-leading-zero outside; color:inherit; line-height:18px; margin:0px!important; padding:0px 3px 0px 10px!important"> menuBestscoreLabel->setPosition(menuSize.width/2,menuSize.height/2-30);
menuLayer->addChild(menuBestscoreLabel);
MenuItemFont::setFontName("Arial");
MenuItemFont::setFontSize(25);
automenuItemRestart=MenuItemFont::create("Restart",menu_selector(GameScene::restart));
menuItemRestart->setColor(Color3B(255,0));
automenu=Menu::create(menuItemRestart,248)"> menuLayer->addChild(menu);
menu->setPosition(Point(menuSize.width/2,menuSize.height/2-80));
}
关键点:
1,专门弄了一个4*4的卡片临时矩阵做为动画层,也就是有16个卡片专门负责对应卡片的动画(卡片生成的动画是实际卡片,这个是例外)。
2,关于游戏逻辑中每次移动后面都有一个break
如果不加这个break就会出现:
- 本来是 2 2 4 2
向左滑动应该是 4 4 2 0
结果是: 8 2 0 0
游戏截图:
源代码
csdn下载:2048源码
github下载:2048源码
FROM:http://blog.csdn.net/u012234115/article/details/40061303?utm_source=tuicool&utm_medium=referral