好久没有更新博客了,博主最近被各种事情忙,然而看着自己的网站访问量日增,留言不断,实在不忍心自己的网站成为一个水货网站,有负各位网友们的支持,本网站的副标题叫做cocos2d-x技术实战,然而各位点击项目实战出来的不过是个忍者飞镖的例子,真是让各位耻笑了,实战的网站怎么能不出几个实战的例子呢?因而博主痛下决心,抽出几个晚上的时间,写写最近比较火的那个游戏1024(也就是2048),一来更新一下网站的博客,给各位网友点干货,一来串联一遍cocos2d-x的技术知识。于是写了三个晚上,今天刚好写起了游戏主要的逻辑,实现了主要的功能,赶紧把这几天的辛苦成果分享出来,本系列博客将讲解cocos2d-x各种功能的实现技巧,把各个功能的实现分开作为一篇篇博客,最后本人水平有限,可能某种实现方式不是最好的,到时候欢迎大家拍砖吧!废话不多说了,现在上货吧!

小塔1024

上边的图是最后游戏主要逻辑完成的效果图,要做到这一步,需要我们一步步的来完成。我们先来完成对游戏框架的搭建,然后来处理游戏的坐标。看看我们用到了哪些资源吧。

小塔1024——对坐标的处理和实现手势的判断

好了先就这四个资源吧,background.png是背景图片,chess.png是我截取出来的棋盘图片,我打算将卡片放到这张图片上,而这个棋盘图片再放到背景图片上,那个.plist文件和cube.png就是用到的卡片了。我们先创建一个游戏主场景,叫做MainGameScene,然后写场景的相关代码,就是helloworld中的那些代码了。

#ifndef __MAIN_GAME_SCENE_H__
#define __MAIN_GAME_SCENE_H__

#include "cocos2d.h"
//添加棋盘类
//#include "Chess.h"

using namespace cocos2d;

class MainGameScene : public CCLayer
{
public:
    virtual bool init();
    static CCScene* scene();
    CREATE_FUNC(MainGameScene);
private:
	//设备大小
	CCSize m_size;
	//棋盘
	//Chess * m_chess;
};

#endif
#include "MainGameScene.h"

CCScene* MainGameScene::scene()
{
    CCScene *scene = CCScene::create();

    MainGameScene *layer = MainGameScene::create();

    scene->addChild(layer);

    return scene;
}

bool MainGameScene::init()
{
    if ( !CCLayer::init() )
    {
        return false;
    }

	m_size = CCDirector::sharedDirector()->getWinSize();

	//添加背景图片
	CCSprite * background = CCSprite::create("background.png");
	background->setPosition(ccp(m_size.width/2,m_size.height/2));
	this->addChild(background);

	//添加棋盘层
	//m_chess = Chess::create();
	//this->addChild(m_chess);

    return true;
}

上边就是游戏主场景的代码,在appdelegate中修改一下需要运行的场景,然后运行起来,怎么样,是不是看到了一部分背景图片啊,这就对了,因为我们的背景图片很大,放到当前的设备中,当然会出现问题,那么我们就来做一下适配吧。我们先来修改一下main.cpp中设备的大小,改为我们pc下可以看全的大小,然后再在appdelegate中添加代码,需要添加的代码如下。

//main.cpp文件
#include "main.h"
#include "AppDelegate.h"
#include "CCEGLView.h"

USING_NS_CC;

#define USE_WIN32_CONSOLE

int APIENTRY _tWinMain(HINSTANCE hInstance,
                       HINSTANCE hPrevInstance,
                       LPTSTR    lpCmdLine,
                       int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

#ifdef USE_WIN32_CONSOLE
    AllocConsole();
    freopen("CONIN$", "r", stdin);
    freopen("CONOUT$", "w", stdout);
    freopen("CONOUT$", "w", stderr);
#endif

    // create the application instance
    AppDelegate app;
    CCEGLView* eglView = CCEGLView::sharedOpenGLView();
    eglView->setViewName("xiaota1024");
	//这里改为(480,680),是为了在win32下编程可以看到整个游戏画面
    eglView->setFrameSize(480, 680);
    int ret = CCApplication::sharedApplication()->run();

#ifdef USE_WIN32_CONSOLE
    FreeConsole();
#endif

	return ret;
}
bool AppDelegate::applicationDidFinishLaunching() {

    CCDirector* pDirector = CCDirector::sharedDirector();
    CCEGLView* pEGLView = CCEGLView::sharedOpenGLView();

    pDirector->setOpenGLView(pEGLView);

    pDirector->setDisplayStats(true);

    pDirector->setAnimationInterval(1.0 / 60);

	//以下操作用来进行适配

	//getFrameSize()获得实际屏幕的大小
	 CCSize frameSize = pEGLView->getFrameSize();
	 //这填写的就是一般你作为背景图片的那张图片的大小
	 CCSize winSize=CCSize(768,1136);

	 //将宽和高做一个比,通过这个比,来具体的调整逻辑分辨率的大小
	 float widthRate = frameSize.width/winSize.width;
	 float heightRate = frameSize.height/winSize.height;

	 //如果是if中的语句,说明逻辑的高度有点大了,就把逻辑的高缩小到和宽度一样的比率
    if (widthRate > heightRate)
	{
		//里边传入的前俩个参数就是逻辑分辨率的大小,也就是通过getWinSize()得到的大小
		pEGLView->setDesignResolutionSize(winSize.width,
			winSize.height*heightRate/widthRate, kResolutionNoBorder);
	}
    else
	{
		pEGLView->setDesignResolutionSize(winSize.width*widthRate/heightRate, winSize.height,
			kResolutionNoBorder);
	}

	//运行场景
    CCScene *pScene = MainGameScene::scene();

    pDirector->runWithScene(pScene);

    return true;
}

好了,现在让我们运行一下游戏吧。怎么样,大小还合适吧!

小塔1024——对坐标的处理和实现手势的判断

接着我们来创建一个棋盘类,命名为Chess,这个chess是用来处理触摸的,同时将卡片都添加到这个类的背景图片中,下面看下代码吧。

//chess.h
#ifndef __CHESS_H__
#define __CHESS_H__

#include "cocos2d.h"

using namespace cocos2d;

class Chess : public CCLayer
{
public:
    virtual bool init();
    CREATE_FUNC(Chess);
private:
	//设备大小
	CCSize m_size;
	//棋盘背景图片
	CCSprite * m_chess;
};

#endif
//chess.cpp
#include "Chess.h"

bool Chess::init()
{
    if ( !CCLayer::init() )
    {
        return false;
    }
	//获取设备大小
	m_size = CCDirector::sharedDirector()->getWinSize();

	//添加棋盘背景图片
	m_chess = CCSprite::create("chess.png");
	//棋盘图片的位置是经过测试合适以后得到的坐标位置
	m_chess->setPosition(ccp(m_size.width/2,m_size.height*0.46));
	this->addChild(m_chess);

    return true;
}

然后去掉在MainGameScene头文件和cpp文件中注释掉的那几行代码运行程序,怎么样,棋盘的位置放到不错吧,和原来刚好重叠,这样我们就比较容易处理坐标了,我们可以将卡片放到这个棋盘的背景图片上,看看左下角的渲染次数,是2!代表添加了背景图片和棋盘。

小塔1024——对坐标的处理和实现手势的判断

接下来我们就向棋盘中添加卡片吧!刚开始的时候是添加2的卡片到棋盘的随机的俩个位置处,这里我要说明俩个问题。第一个是我对卡片坐标的处理,我采用逻辑坐标和实际坐标相结合的方式,一共是4*4的格子,左下角第一个格子的坐标是(0,0),接下来是(0,1),右上角的坐标是(3,3),这样便于对坐标的处理,而要添加到棋盘中我们就不能使用我们定义的这个坐标,需要转化为实际在棋盘中的坐标,所以我们使用坐标处理类dealPoint,用来处理逻辑坐标和实际坐标的转化。看看他的头文件和cpp文件怎么写吧。

#ifndef _DEAL_POINT_H_
#define _DEAL_POINT_H_
#include "cocos2d.h"

using namespace cocos2d;

//定义每个格子的宽度为100,高度为150
#define CHESS_WIDTH  100
#define CHESS_HEIGHT  150

class DealPoint
{
public:
	CCPoint convertToLogicalPosition(CCPoint);
	CCPoint convertToActualPosition(CCPoint);
};

#endif
#include "DealPoint.h"

//在本游戏中实际的坐标就是放在棋盘上的实际坐标位置,而逻辑坐标采用的是以右下角为原点,然后向右向上
//都增大的坐标,每个棋盘格子代表的是1,这些坐标分别是(0,0),(0,1),(0,2),(0,3),...(3,3)

//将实际坐标转化为逻辑坐标
CCPoint DealPoint::convertToLogicalPosition(CCPoint point)
{
	int x = point.x/CHESS_WIDTH;
	//采用上边的方法x方向上逻辑坐标的处理有些bug,就只好设为3
	if(x == 4)
		x = 3;
	int y = point.y/CHESS_HEIGHT;
	return ccp(x,y);
}

//将逻辑坐标转化为实际坐标
CCPoint DealPoint::convertToActualPosition(CCPoint point)
{
	CCPoint actualPoint = ccp(point.x*CHESS_WIDTH+CHESS_WIDTH/2+point.x*22,
		point.y*CHESS_HEIGHT+CHESS_HEIGHT/2+point.y*12);
	return actualPoint;
}

另一个问题就是对卡片的增删问题,我们不能在棋盘这个类中去处理,需要新建一个类,叫做卡片管理器类CubeManager,负责卡片的增加回收执行动作等等。而首先在这个类中我们需要做的就是添加卡片到随机的位置处,看看类中是怎么实现的吧。

#ifndef _CUBE_MANAGER_H_
#define _CUBE_MANAGER_H_

#include "cocos2d.h"
//坐标转换头文件
#include "DealPoint.h"

using namespace cocos2d;

class CubeManager : public CCObject
{
private:
	//坐标处理对象,用来进行逻辑坐标和实际坐标的转化
	DealPoint m_dealPoint;
	//声明一个数组用来存放卡片
	CC_SYNTHESIZE(CCArray * ,m_cubeArray,CubeArray);
public:
	CREATE_FUNC(CubeManager);
	bool init();
	//析构中release数组
	~CubeManager(){m_cubeArray->release();};
	//添加2的卡片到棋盘中,位置是随机的
	CCSprite * addCubeToChess();
};

#endif
#include "CubeManager.h"

bool CubeManager::init()
{
	//为卡片数组初始化
	this->m_cubeArray = CCArray::create();
	//将引用计数加1
	m_cubeArray->retain();

	//将用到的精灵通过.plist存入到精灵帧缓存中,可以提高效率
	CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile("cube.plist");

	//初始化坐标处理对象,用来进行逻辑坐标和实际坐标的转化
	m_dealPoint = DealPoint();

	//设置随机数种子
	cc_timeval tv;
	CCTime::gettimeofdayCocos2d(&tv,NULL);
	//都转化为毫秒
	unsigned long reed = tv.tv_sec*1000+tv.tv_usec/1000;
	//srand()中传入一个随机数种子
	srand(reed);

	return true;
}

//添加2的卡片到棋盘中,位置是随机的
CCSprite * CubeManager::addCubeToChess()
{
	//添加一个2的格子到棋盘的随机位置中
	CCSprite * randSprite_1 = CCSprite::createWithSpriteFrameName("cube_1.png");
	//将它的数字设置为2,方便消除动作
	randSprite_1->setTag(2);

	int x = CCRANDOM_0_1()*3;
	int y = CCRANDOM_0_1()*3;
	//设置坐标
	randSprite_1->setPosition(m_dealPoint.convertToActualPosition(ccp(x,y)));
	//添加到格子管理器中
	m_cubeArray->addObject(randSprite_1);

	return randSprite_1;
}

小塔1024——对坐标的处理和实现手势的判断

好了,现在运行程序就看到了上边的图片,这个位置是随机的,但是前提需要在init函数中产生一个随机数种子,多次运行程序可以看到卡片被添加到了不同的位置处,我们对坐标的处理先就这样,下次我们需要另作处理。接下来我们完成手势的识别工作。在chess中开启触摸,然后写触摸处理函数,具体实现如下。

#ifndef __CHESS_H__
#define __CHESS_H__

#include "cocos2d.h"
//坐标处理类
#include "DealPoint.h"
//卡片管理器
#include "CubeManager.h"

using namespace cocos2d;

//定义手势的方向
enum DIRECTION
{
	DIRECTION_TYPE_UP = 0,
	DIRECTION_TYPE_DOWN,
	DIRECTION_TYPE_LEFT,
	DIRECTION_TYPE_RIGHT,
	DIRECTION_TYPE_NO
};

class Chess : public CCLayer
{
public:
    virtual bool init();
    CREATE_FUNC(Chess);
	//注册触摸
	void Chess::registerWithTouchDispatcher();
	//触摸处理
	bool ccTouchBegan(CCTouch * touch,CCEvent * evt);
	void ccTouchEnded(CCTouch * touch,CCEvent * evt);
	//析构中记得release
	~Chess(){m_cubeManager->release();};
private:
	//设备大小
	CCSize m_size;
	//棋盘背景图片
	CCSprite * m_chess;
	//坐标处理
	DealPoint m_dealPoint;
	//手势的方向
	DIRECTION m_direction;
	//刚开始触摸棋盘的触摸点坐标,判断手势的方向的时候会用到
	CCPoint m_firstPoint;
	//卡片管理器,用来将棋盘上的卡片存放起来
	CubeManager * m_cubeManager;
};

#endif
void Chess::registerWithTouchDispatcher()
{
	CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this,0,true);
}

bool Chess::ccTouchBegan(CCTouch * touch,CCEvent * evt)
{

	CCPoint clickPoint = touch->getLocation();
	CCRect rect = this->m_chess->boundingBox();
	//判断点击点是否在棋盘上
	if(rect.containsPoint(clickPoint))
	{
		//将刚开始的触摸点保存下来,过会进行手势的判断
		this->m_firstPoint = clickPoint;
		return true;
	}

	return false;
}

//在ccTouchEnded中完成手势的判断
void Chess::ccTouchEnded(CCTouch * touch,CCEvent * evt)
{
	CCPoint clickPoint = touch->getLocation();
	//将手势的方向置为无
	this->m_direction = DIRECTION_TYPE_NO;

	//x方向上的增量
	int x_add = clickPoint.x-this->m_firstPoint.x;
	//y方向上的增量
	int y_add = clickPoint.y-this->m_firstPoint.y;

	//x方向上的增量大于y方向上的增量,往左或者是往右滑动
	if(std::abs(x_add) > std::abs(y_add))
	{
		//往左滑动
		if(x_add < 0)
		{
			this->m_direction = DIRECTION_TYPE_LEFT;
			CCLog("left");
		}
		//往右滑动
		else
		{
			this->m_direction = DIRECTION_TYPE_RIGHT;
			CCLog("right");
		}
	}
	//往上或者是下滑动
	else
	{
		//往下滑动
		if(y_add < 0)
		{
			this->m_direction = DIRECTION_TYPE_DOWN;
			CCLog("down");
		}
		//往上滑动
		else if(y_add > 0)
		{
			this->m_direction = DIRECTION_TYPE_UP;
			CCLog("up");
		}
	}

	//如果没有方向,则卡片不进行移动,不添加新的卡片元素
	if(m_direction == DIRECTION_TYPE_NO)
		return;

	//执行动作
	/*if(this->m_cubeManager->actionOfCube(m_direction))
	{

	}*/
}

小塔1024——对坐标的处理和实现手势的判断

好了,现在为止我们就搭起了我们的框架,还完成了手势的识别,下篇博客继续吧!