上一篇博客实现了基本框架的搭建和手势类型的判断,但是卡片还不能动,现在我们就让它动起来吧!这个移动的函数需要写在卡片管理器中,将卡片从卡片管理器中取出来,根据手势的类型判断需要移动到什么地方,这个判断就是关键。我的逻辑是这样的,以手势向左为例,如果卡片的左边没有卡片,那么它一定是移动到了最左边,如果有一张卡片的话,它就会移动到这张卡片的右边,坐标是它左边卡片的数量和它原先的y坐标,下面具体看看代码中如何实现的吧。

//参数是手势的类型,函数调用完毕以后返回true
bool CubeManager::actionOfCube(int type)
{
	//设置手势的类型
	//this->m_type = type;

	//数组遍历的时候用到
	CCObject * obj;

	//设置卡片的状态为移动状态
	//this->m_bRun = true;

	//重置坐标管理器中的坐标
	//this->m_pointManager->initPointArray();

	//遍历数组,对数组中的卡片执行移动的动作
	CCARRAY_FOREACH(this->m_cubeArray,obj)
	{
		CCSprite * cube = (CCSprite *)obj;
		//getPosition获得的是节点坐标,这里是相对于棋盘图片的坐标
		CCPoint cubePoint = cube->getPosition();
		//转化为自己设置的逻辑坐标
		CCPoint logicalPoint = m_dealPoint.convertToLogicalPosition(cubePoint);
		//获得卡片左边的卡片数量,为移动做准备
		int numOfCube = this->getNumOfCube(logicalPoint,type);

		//根据不同的手势产生不同的移动动作
		CCMoveTo * move = NULL;
		//卡片移动完毕调用的动作
		//CCCallFunc * callFunc = CCCallFunc::create(this,callfunc_selector(CubeManager::call));

		//保存卡片将要移动到的逻辑位置
		CCPoint movePoint;

		//根据手势的类型,做不同的处理,关于处理代码的说明请参照case 2的注释
		switch(type)
		{
			//手势是上
		case 0:
			{
			move = CCMoveTo::create(0.1f,
			m_dealPoint.convertToActualPosition(ccp(logicalPoint.x,3-numOfCube)));
			movePoint = ccp(logicalPoint.x,3-numOfCube);
			break;
			}
			//down
		case 1:
			{
			move = CCMoveTo::create(0.1f,
			m_dealPoint.convertToActualPosition(ccp(logicalPoint.x,numOfCube)));
			movePoint = ccp(logicalPoint.x,numOfCube);
			break;
			}
			//left
		case 2:
			{
			//如果左边的卡片数为0,就移动到(0,原y坐标),也就是ccp(numOfCube,logicalPoint.y)
			//注意是移动到,其他的情况请类推
			move = CCMoveTo::create(0.1f,
			m_dealPoint.convertToActualPosition(ccp(numOfCube,logicalPoint.y)));
			movePoint = ccp(numOfCube,logicalPoint.y);
			break;
			}
			//right
		case 3:
			{
			move = CCMoveTo::create(0.1f,
			m_dealPoint.convertToActualPosition(ccp(3-numOfCube,logicalPoint.y)));
			movePoint = ccp(3-numOfCube,logicalPoint.y);
			break;
			}
		}
		//this->removePoint(movePoint);
		//动作系列
		//CCSequence * sequ = CCSequence::create(move,callFunc,NULL);
		//执行移动的动作
		cube->runAction(move);

	}
	return true;
}

//判断卡片的左边有几张卡片,传入的是逻辑坐标
int CubeManager::getNumOfCube(CCPoint point,int type)
{
	//初始化为没有卡片
	int numOfCube = 0;

	CCObject * obj;
	//再次遍历数组
	CCARRAY_FOREACH(this->m_cubeArray,obj)
	{
		CCSprite * cube = (CCSprite *)obj;
		CCPoint cubePoint = cube->getPosition();
		//转化为自己设置的逻辑坐标
		CCPoint logicalPoint = m_dealPoint.convertToLogicalPosition(cubePoint);
		//代码逻辑含义参照case 2
		switch(type)
		{
		case 0:
			if(logicalPoint.x == point.x && logicalPoint.y > point.y)
				numOfCube++;
			break;
		case 1:
			if(logicalPoint.x == point.x && logicalPoint.y < point.y)
				numOfCube++;
			break;
		case 2:
			//符合if的条件就证明精灵的左边有精灵,这个时候计数值加1
			if(logicalPoint.y == point.y && logicalPoint.x < point.x)
				numOfCube++;
			break;
		case 3:
			if(logicalPoint.y == point.y && logicalPoint.x > point.x)
				numOfCube++;
			break;
		}
	}

	return numOfCube;
}

小塔1024——卡片移动及随机位置添加卡片在chess的ccTouchEnded函数中完成手势判断以后记得调用这个动作啊,运行程序,可以看到效果了。我们如果把移动的速度设慢会发现有bug,当我们在卡片还没有结束移动动作的时候再次滑动屏幕发现卡片的位置发生了偏差,所以我们得想个办法来解决这个bug。我的做法是在所有的卡片结束移动动作之前用户滑动屏幕无效,需要在chess中设置一个变量,保存卡片是否移动的情况,就叫m_bRun吧,并且实现这个变量的get函数,方便在其他的类中调用,然后在ccTouchBegan中判断这个变量的值,决定是否传递消息。

//如果卡片正在移动,则不再进行移动,防止用户点击屏幕过快,导致坐标错误
	if(this->m_cubeManager->getCubeRun() == true)
	{
		return false;
	}

那么我们什么时候改变这个值呢?当然是所有的卡片动作执行完毕以后了,所以我们需要卡片执行完毕动作以后调用一个函数,在这个函数中记录卡片移动完成的个数,如果都完成了移动动作就改变m_bRun这个变量,函数实现如下。

//当棋盘格子中的卡片完成了自己的动作以后调用该函数,当num和卡片数量想同的时候代表可以接受下一次用户的触摸
//如果在卡片没有完成自己的动作的时候就接受触摸消息,会产生bug,导致坐标混乱
void CubeManager::call()
{
	static int num = 0;
	num++;
	//当所有的卡片移动完毕以后以下条件才会满足
	if(num == this->m_cubeArray->count())
	{
		//当所有的卡片都移动完毕以后设置卡片的移动状态为false
		this->m_bRun = false;
		num = 0;
	}
}

调低卡片移动的速率,再次运行程序,发现bug消失了。这样我们就可以移动卡片了,接下来需要做的是每次移动卡片,在随机的位置上就产生一个新的2的卡片。要完成这个功能我们需要对位置进行管理,因为卡片的位置是随机出现的,而有些位置被其他的卡片占用了,占用了的位置是不允许出现卡片的,所以我们就需要将没有被占用的位置保持起来,放到数组中,然后产生新的卡片的时候从这个数组中随机的获取一个位置信息,然后添加卡片到棋盘中。好了,现在第一步就是保存没有被占用的坐标到数组CCArray中,大家可以试一下,发现是不可以的,因为CCPoint没有继承自CCObject,所以不可以添加进去的,解决的方法是可以使用c++的容器vector,或者将CCPoint放到CCNode中,然后添加CCNode到CCArray中,这里我用的是第二种方法,封装CCPoint到一个类GridPoint中。这里我们还需要一个类,这个类是管理这些坐标信息的,就叫坐标管理器吧!好了,现在我们具体的实现一下这俩个类吧!

//GridPoint.h
#ifndef _GRID_POINT_H_
#define _GRID_POINT_H_
#include "cocos2d.h"

using namespace cocos2d;

class GridPoint : public CCNode
{
public:
	//把坐标信息保存到这个变量中
	CCPoint m_point;
	CREATE_FUNC(GridPoint);
	bool init();
};

#endif
#include "GridPoint.h"

bool GridPoint::init()
{
	if(!CCNode::init())
	{
		return false;
	}

	this->m_point = ccp(0,0);
	return true;
}
//坐标管理器是对棋盘坐标的管理,它保存了没有分配给卡片的坐标
#ifndef _POINT_MANAGER_H_
#define _POINT_MANAGER_H_
#include "cocos2d.h"
//格子坐标
#include "GridPoint.h"

using namespace cocos2d;

class PointManager : public CCObject
{
public:
	bool init();
	CREATE_FUNC(PointManager);
	~PointManager(){m_pointArray->release();};
	GridPoint * getObject(CCPoint point);
private:
	//声明一个数组,用来存放没有被占用的坐标
	CC_SYNTHESIZE(CCArray *,m_pointArray,PointArray);
	//初始化坐标数组,在数组中存放所有的格子坐标
	void initPointArray();
};

#endif
#include "PointManager.h"

bool PointManager::init()
{
	//创建一个坐标数组,用来存放棋盘的坐标
	m_pointArray = CCArray::create();
	m_pointArray->retain();
	//初始化这个数组,把所有的格子坐标都放进去
	this->initPointArray();

	return true;
}

//初始化坐标数组,在数组中存放所有的格子坐标
void PointManager::initPointArray()
{
	//如果坐标数组中还存在坐标,则把所有的坐标都清除
	if(m_pointArray->count() != 0)
	{
		m_pointArray->removeAllObjects();
	}
	//将逻辑坐标存入到数组中
	for(int i=0;i<4;i++)
	{
		for(int j=0;j<4;j++)
		{
			GridPoint * gridPoint = GridPoint::create();
			gridPoint->m_point = ccp(i,j);
			m_pointArray->addObject(gridPoint);
		}
	}
}

//这个函数的功能是获得坐标数组中的坐标对象,传入的是逻辑坐标,当坐标值相等的时候就返回对象
GridPoint * PointManager::getObject(CCPoint point)
{
	CCObject * obj;
	CCARRAY_FOREACH(this->m_pointArray,obj)
	{
		GridPoint * gridPoint = (GridPoint *)obj;
		if(point.x == gridPoint->m_point.x && point.y == gridPoint->m_point.y)
		{
			return gridPoint;
		}
	}
	return NULL;
}

其他几个函数的功能不需要说明,这个getObject函数需要说一下。当我们从数组中移除一个元素的时候,使用removeObject()的时候需要传入一个CCObject的对象,这个CCObject必须是和数组中元素的指针相同,如果不相同是不可以移除的,所以我们就不能自己新建一个对象,将他的变量设置的和数组中的某一个对象完全相同,然后调用remove函数,这样做是不行的,所以我们要根据这个对象的成员变量找到数组中和它有相同成员变量的对象,然后返回数组中的一个对象指针,传入到remove函数中,这样才可以移除,这就是getObject()函数的作用了。接下来的一个问题就是如何操作这些坐标了,我们需要在卡片执行move动作的函数中,将卡片将要占用的坐标从坐标管理器中移除出去,这样在坐标数组中剩下的就是可以占用的坐标了,然后添加卡片的时候从这个坐标数组中随机的取出一个坐标,这样就完成了功能,看看代码如何修改吧!

//添加2的卡片到棋盘中,位置是随机的
CCSprite * CubeManager::addCubeToChess()
{
	//添加一个2的格子到棋盘的随机位置中
	CCSprite * randSprite_1 = CCSprite::createWithSpriteFrameName("cube_1.png");
	//将它的数字设置为2,方便消除动作
	randSprite_1->setTag(2);
	//randomObject返回随机的一个坐标
	GridPoint * gridPoint = (GridPoint *)m_pointManager->getPointArray()->randomObject();
	//如果为空代表无法获得有效的位置,游戏结束
	if(gridPoint == NULL)
	{
		CCLog("end!");
		return NULL;
	}
	//设置坐标
	randSprite_1->setPosition(m_dealPoint.convertToActualPosition(gridPoint->m_point));
	//添加到格子管理器中
	m_cubeArray->addObject(randSprite_1);

	return randSprite_1;
}
//参数是手势的类型,函数调用完毕以后返回true
bool CubeManager::actionOfCube(int type)
{
	//设置手势的类型
	this->m_type = type;

	//数组遍历的时候用到
	CCObject * obj;

	//设置卡片的状态为移动状态
	this->m_bRun = true;

	//重置坐标管理器中的坐标
	this->m_pointManager->initPointArray();

	//遍历数组,对数组中的卡片执行移动的动作
	CCARRAY_FOREACH(this->m_cubeArray,obj)
	{
		CCSprite * cube = (CCSprite *)obj;
		//getPosition获得的是节点坐标,这里是相对于棋盘图片的坐标
		CCPoint cubePoint = cube->getPosition();
		//转化为自己设置的逻辑坐标
		CCPoint logicalPoint = m_dealPoint.convertToLogicalPosition(cubePoint);
		//获得卡片左边的卡片数量,为移动做准备
		int numOfCube = this->getNumOfCube(logicalPoint,type);

		//根据不同的手势产生不同的移动动作
		CCMoveTo * move = NULL;
		//卡片移动完毕调用的动作
		CCCallFunc * callFunc = CCCallFunc::create(this,callfunc_selector(CubeManager::call));

		//保存卡片将要移动到的逻辑位置
		CCPoint movePoint;

		//根据手势的类型,做不同的处理,关于处理代码的说明请参照case 2的注释
		switch(type)
		{
			//手势是上
		case 0:
			{
			move = CCMoveTo::create(0.1f,
			m_dealPoint.convertToActualPosition(ccp(logicalPoint.x,3-numOfCube)));
			movePoint = ccp(logicalPoint.x,3-numOfCube);
			break;
			}
			//down
		case 1:
			{
			move = CCMoveTo::create(0.1f,
			m_dealPoint.convertToActualPosition(ccp(logicalPoint.x,numOfCube)));
			movePoint = ccp(logicalPoint.x,numOfCube);
			break;
			}
			//left
		case 2:
			{
			//如果左边的卡片数为0,就移动到(0,原y坐标),也就是ccp(numOfCube,logicalPoint.y)
			//注意是移动到,其他的情况请类推
			move = CCMoveTo::create(0.1f,
			m_dealPoint.convertToActualPosition(ccp(numOfCube,logicalPoint.y)));
			movePoint = ccp(numOfCube,logicalPoint.y);
			break;
			}
			//right
		case 3:
			{
			move = CCMoveTo::create(0.1f,
			m_dealPoint.convertToActualPosition(ccp(3-numOfCube,logicalPoint.y)));
			movePoint = ccp(3-numOfCube,logicalPoint.y);
			break;
			}
		}
		//将卡片将要占用的位置从位置管理器中移除
		this->removePoint(movePoint);
		//动作系列
		CCSequence * sequ = CCSequence::create(move,callFunc,NULL);
		//执行移动的动作
		cube->runAction(sequ);

	}
	return true;
}

//移除坐标管理器中的坐标
void CubeManager::removePoint(CCPoint logicalPoint)
{
	m_pointManager->getPointArray()->removeObject(m_pointManager->getObject(logicalPoint));
}
//当棋盘格子中的卡片完成了自己的动作以后调用该函数,当num和卡片数量想同的时候代表可以接受下一次用户的触摸
//如果在卡片没有完成自己的动作的时候就接受触摸消息,会产生bug,导致坐标混乱
void CubeManager::call()
{
	static int num = 0;
	num++;
	//当所有的卡片移动完毕以后以下条件才会满足
	if(num == this->m_cubeArray->count())
	{
		//当所有的卡片都移动完毕以后设置卡片的移动状态为false
		this->m_bRun = false;
		num = 0;
		//向chess发送添加卡片的消息
		CCNotificationCenter::sharedNotificationCenter()->postNotification(MESSAGE_TYPE_ADD_CUBE,
			this->addCubeToChess());
	}
}

大家会发现我在call函数中加了几句话,这句话的作用是向chess类发送消息,消息的类型定义在了头文件message.h中,发送过去的是一个对象,这个对象就是调用addCubeToChess返回的对象,为什么要发送消息呢?这是因为我们要向chess中添加卡片,然而在卡片管理类中我们是无法获得chess对象的,当然了你可以new一个,但是这样的话就不好了,或许你还有其他的方法向chess中添加这个新建的卡片,但是我这里采用的就是事件监听的机制,所以在chess的init函数中,需要监听一下这个消息,然后写个函数来处理一下这个消息。

//添加事件监听,监听CubeManager发送过来的添加卡片的消息
	CCNotificationCenter::sharedNotificationCenter()->addObserver(this,
		callfuncO_selector(Chess::addCube),MESSAGE_TYPE_ADD_CUBE,NULL);
//事件监听的调用函数
void Chess::addCube(CCObject * obj)
{
	CCSprite * sprite = (CCSprite *)obj;
	this->m_chess->addChild(sprite);
	//添加抖动的效果
	CCJumpTo * jump = CCJumpTo::create(0.2f,sprite->getPosition(),30.0f,1);
	sprite->runAction(jump);
}

小塔1024——卡片移动及随机位置添加卡片好了运行程序我们就看到了如图所示的效果了,我这里说的都是程序的主要思路,代码也是主要代码,在头文件中的一些函数声明什么的都没有说,还有其他的一些细节也没有讲到,不过大家照着我的思路一定可以完成的,最后我会共享源代码的。好了,本篇博客又完成了一个功能,卡片在随机位置的出现,我们离成功不远了!