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

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


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());
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值