首页 > Cocos > cocos2d-x之道~制作第一款文字游戏(一)
2015
07-17

cocos2d-x之道~制作第一款文字游戏(一)

前言

        回想去年,也是在三、四月时开发出自己的第一款游戏,正是这款游戏,令我决定踏上独立开发者之路。一年过去了,第一款游戏达到它应有的盈利水平。然而这款游戏开发后的时间里,都没再取得另一款令自己满意的作品。直到今年的三、四月,我使用cocos2d-x开发出我的第一款文字游戏。

        自从使用cocos2d-x后,发现自己爱上了这个引擎。它或许还不够强大和完善,但是使用它,可以体验编码的乐趣,还可以为我重拾C++这门技术(到底对C++有多么的执着啊),还能增进NDK和JNI的学习。恰恰满足我各种各样的追求。

       这个第一款文字游戏,名字我取作Anagram Puzzle,其实这款游戏是参照RayWenderlich上的这篇教程来改写的,教程里使用的是iOS的UIKit编写,虽说原理相通,但是在改写过程中还是遇到不少折腾的地方。由于初次编写cocos2d-x游戏,所以错误难免百出,希望阅读者们高抬贵手,点到即止……废话不多说,马上开始coco2d-x之道~如何制作第一款文字游戏!

Anagram简介

Anagram是一种把单词或短语的字母顺序打乱,重新排列后变成一个新单词或短语的游戏。例如,单词cinema可以重新排列成iceman。游戏中就要求玩家进行你所提供单词或短语的重新排列。完成游戏的画面会如图所示:

cocos2d-x之道~制作第一款文字游戏(一) - 第1张  | FreemanApp

在开发这个游戏过程中,会接触到以下这些知识:

  • MVC型的游戏结构
  • 如何从文件配置加载级别
  • 加载第三方字体
  • 简单使用音乐音效
  • 分离HUD层与游戏层
  • 手势拖动及动画
  • Particle的效果
还有其他的一些cocos2d-x的基础知识,都会在开发过程中接触到。

初始化工程

首先最重要的当然是使用命令行来创建cocos2d-x工程,当然也有其他方法也可以创建工程,但是我认为掌握命令行来创建是必须的基本功。创建方法可以在这里找到。创建完成后各个平台的工程文件夹都有了。我们的主要工程文件夹是proj.android和proj.ios两个。整个开发过程,我使用Mac OS来开发,所以编码是在XCode上进行,而Android工程的编译则使用命令行,详细教程可以参考这里。 

建立好工程后,先把所需的资源文件拷贝到Resource文件夹中。打开Xcode工程,目前Resource文件夹下还是原来的资源文件,通过右击Resource文件夹 -> Add Files to …,把资源文件都加到工程里。编辑后工程会如图

cocos2d-x之道~制作第一款文字游戏(一) - 第2张  | FreemanApp

1)加载级别配置文件

打开level1.plist,可以看到里面内容

cocos2d-x之道~制作第一款文字游戏(一) - 第3张  | FreemanApp

有三个最顶端的key,分别是:

pointsPerTile: 每个单词填对后获得的分数。
timeToSolve: 解决这一关的时间(秒)。
anagrams: 是题目的列表,包含两个item,分别是原始的短语以及最后要拼出的短语。

level文件的介绍就到此为止,下面开始编写Level类,在Level.h中添加以下内容

class Level:public CCObject{
public:
	static Level * levelWithNum(int levelNum);
 
public:
	int mPointPerTile;
	int mTimeToSovle;
	CCArray * pAnagrams;
};

其中,三个变量对应level文件里的三个最顶端的item。还有一个初始化函数,是给外部调用初始化level文件。

现在,打开Level.cpp,实现levelWithNum函数

Level * Level::levelWithNum(int levelNum){
	char fileName[50];
	char fullPath[150];
	sprintf(fileName,"level%d.plist",levelNum);
	CCFileUtils::sharedFileUtils()->fullPathFromRelativeFile(fileName,fullPath);
	CCDictionary * pListDict = CCDictionary::createWithContentsOfFile(fileName);
 
	if(pListDict == NULL){
		CCLog("level config not found");
	}
 
	Level * l = new Level();
	CCString * tempStr;
	tempStr = dynamic_cast<CCString*>(pListDict->objectForKey("pointsPerTile"));
	l->mPointPerTile = tempStr->intValue();
	tempStr = dynamic_cast<CCString*>(pListDict->objectForKey("timeToSolve"));
	l->mTimeToSovle = tempStr->intValue();
	l->pAnagrams = dynamic_cast<CCArray*>(pListDict->objectForKey("anagrams"));
	l->pAnagrams->retain();
 
	return l;
}

这里首先用CCDictionary读出level文件中数值,然后就是读取出对应key里的值,并存储起来。

现在,打开主界面文件,默认是HelloWorldScene类,但是我改写成MainScene,其中MainScene.h是这样的

class MainScene : public cocos2d::CCLayer
{
public:
    ~MainScene();
    // Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone
    virtual bool init();  
 
    // there's no 'id' in cpp, so we recommend returning the class instance pointer
    static cocos2d::CCScene* scene();
 
    // implement the "static node()" method manually
    CREATE_FUNC(MainScene);
private:
	Level * pLevel;
};

可见,增加了Level变量。在MainScene.cpp中的init函数,编写

pLevel = Level::levelWithNum(1);

当然,这里可以通过CCLog来打印出pLevel里的内容来看看,会得到如图所示

cocos2d-x之道~制作第一款文字游戏(一) - 第4张  | FreemanApp

在MainScene里添加个新的函数

void dealRandomAnagram();

具体实现要这样

void MainScene::dealRandomAnagram(){
	Common::random(0, pLevel->pAnagrams->count() - 1);
	int randomIndex =  Common::random(0, pLevel->pAnagrams->count() - 1);
 
	CCAssert((randomIndex >= 0 && randomIndex < pLevel->pAnagrams->count()),"error random index!");
 
	CCArray * anagram = (CCArray*)pLevel->pAnagrams->objectAtIndex(randomIndex);
 
	CCString * ana1 = (CCString*)anagram->objectAtIndex(0);
	CCString * ana2 = (CCString*)anagram->objectAtIndex(1);
 
	int ana1len = ana1->length();
	int ana2len = ana2->length();
}


Common::random是我自己编写的,生成两个数值之间的随机数

int Common::random(int s,int e){
	float i = CCRANDOM_0_1()*(e-s+1)+s;
	return (int)i;
}

这样就把初始状态的短语和最终状态的短语获取到了,把dealRandomAnagram函数添加到MainScene的init函数里,

pLevel = Level::levelWithNum(1);
dealRandomAnagram();

2)创建单词的View

cocos2d-x之道~制作第一款文字游戏(一) - 第5张  | FreemanApp

在工程中新增一个继承于CCNode的类,名字叫做TileView,在TileView.h中添加以下代码

public:
	static TileView * initWithLetter(const char * l,float sideLen);
 
private:
	CCSprite * pSprite;
	char mLetter;
	bool mIsMatch;

其中initWithLetter是初始化函数,pSprite是显示的精灵,mLetter是对应的字母,mIsMatch表示结果是否已经配对上(就是找到字母所应该在的位置)。

在TileView.cpp里,添加以下代码

#include "TileView.h"
 
TileView * TileView::initWithLetter(const char * l, float sideLen){
	TileView * tile = new TileView();
	CCSprite * bg = CCSprite::create("tile.png");
	tile->addChild(bg);
	tile->pSprite = bg;
	float scale = sideLen / bg->getContentSize().width;
	bg->setScale(scale);
 
	char chLetter[2];
	sprintf(chLetter,"%c",l[0] - 32);
	CCLabelTTF * letter = CCLabelTTF::create(chLetter,"Arial",75 * scale);
	letter->setColor(ccWHITE);
	tile->addChild(letter);
 
	tile->mIsMatch = false;
	tile->mLetter = chLetter[0];
 
	return tile;
}

函数中,首先的是创建一个以tile.png为图案的精灵,然后创建图案上的字母

接下来就要在界面中显示出来了,在MainScene中添加

private:
	Level * pLevel;
	CCArray * pTiles;
	CCArray * pTargets;

pTiles是TileView的数组,pTargets是TargetView的数组,其中TileView是放在底部给出的短语的各个单词,TargetView是目标短语的各个单词。在MainScene.cpp的dealRandomAnagram函数中继续添加代码

int ana1len = ana1->length();
int ana2len = ana2->length();
 
float tileSide = ceilf( Common::getCameraWith()*0.9 / (float)std::max(ana1len, ana2len) ) - kTileMargin;
 
float xOffset = (Common::getCameraWith() - std::max(ana1len,ana2len) * (tileSide + kTileMargin)) / 2;
xOffset += tileSide/2;

此时,开始计算各个TileView的位置。首先,比较得出原始短语和目标短语中长度最长的,然后算出各个View所需的宽度tileSide,以及各个View之间的间隔xOffset

对了,不要忘了定义全局的空隙

#define kTileMargin 20

接着,就要创建我们的TileView了

<span style="white-space:pre">	</span>pTiles = CCArray::createWithCapacity(ana1len);
 
	const char * ana1Letter = ana1-&gt;getCString();
	for(int i = 0;i &lt; ana1len; i++){
		char letter[3];
		sprintf(letter,&quot;%c&quot;,ana1Letter[i]);
 
		if(letter[0] != ' '){
			TileView * tile = TileView::initWithLetter(letter,tileSide);
			tile-&gt;setPosition(ccp(xOffset + i * (tileSide + kTileMargin),Common::getCameraHeight() / 4));
			this-&gt;addChild(tile);
			pTiles-&gt;addObject(tile);
		}
	}
	pTiles-&gt;retain();

创建方法比较简单,但是要注意的是,原始短语中可以会有空字符,空字符的地方需要留空。如图所示

cocos2d-x之道~制作第一款文字游戏(一) - 第6张  | FreemanApp

3)单词View优化

方方正正的TileView看着有些拘谨,下面进行一些优化来让它们生动一些。在TileView中添加randomize函数

void TileView::randomize(){
	float rotation = Common::random(0,50) /(float)100 - 0.2;
	this-&gt;setRotation(rotation * 10);
 
	int yOffset = Common::random(0,10);
	this-&gt;setPositionY(this-&gt;getPositionY() + yOffset);
}

让TileView稍作旋转和偏移,然后在MainScene的dealRandomAnagram函数中的 this->addChild(tile); 语句后添加下面语句

tile-&gt;randomize();
 

cocos2d-x之道~制作第一款文字游戏(一) - 第7张  | FreemanApp4)添加TargetView

有了原始短语,下面就要开始创建目标短语的View了。对比TileView,TargetView要相对简单一些。因为它是固定位置及不需要显示字母。TargetView.h中添加以下代码:

class TargetView : public CCNode
{
public:
	TargetView(void);
	~TargetView(void);
 
	static TargetView * initWithLetter(const char * l,float sideLen);
 
private:
	CCSprite * pSprite;
	char mLetter;
	bool mIsMatch;
};

与TileView相类似的,一个初始化函数,三个私有变量。与TileView的是一一对应。

TargetView * TargetView::initWithLetter(const char * l, float sideLen){
	TargetView * tile = new TargetView();
	CCSprite * bg = CCSprite::create(&quot;slot.png&quot;);
	tile-&gt;addChild(bg);
	tile-&gt;pSprite = bg;
 
	float scale = sideLen / bg-&gt;getContentSize().width;
	bg-&gt;setScale(scale);
 
	char chLetter[2];
	sprintf(chLetter,&quot;%c&quot;,l[0] - 32);
	/*
	CCLabelTTF * letter = CCLabelTTF::create(chLetter,&quot;Arial&quot;,78 * scale);
	letter-&gt;setColor(ccWHITE);
	tile-&gt;addChild(letter);*/
 
	tile-&gt;mIsMatch = false;
	tile-&gt;mLetter = chLetter[0];
 
	return tile;
}

TargetView的initWithLetter函数中,注释的语句是为显示一下结果,但是在实际游戏中是不显示TargetView上的字母。

接下来,就要把TargetView显示到场景上了,找到MainScene的dealRandomAnagram方法,在末尾处添加上以下代码

<span style="white-space:pre">	</span>pTargets = CCArray::createWithCapacity(ana2len);
 
	const char * ana2Letter = ana2-&gt;getCString();
	for(int i = 0;i &lt; ana2len; i++){
		char letter[3];
		sprintf(letter,&quot;%c&quot;,ana2Letter[i]);
 
		if(letter[0] != ' '){
			TargetView * target = TargetView::initWithLetter(letter,tileSide);
			target-&gt;setPosition(ccp(xOffset + i * (tileSide + kTileMargin),Common::getCameraHeight() / 4 * 3));
			this-&gt;addChild(target);
			pTargets-&gt;addObject(target);
		}
	}
	pTargets-&gt;retain();

是否可看到TargetView也出来,这里,我们就完成了AnagramPuzzle的第一部分开发工作,最后上个截图

cocos2d-x之道~制作第一款文字游戏(一) - 第8张  | FreemanApp

感觉还不错吧,轻松的把所需要的游戏界面展示出来。这次接触到cocos2d-x知识还是比较少,主要是怎样往主场景中添加内容,怎样创建精灵。下一次,我们就要编写一些有点挑战性的东西了,比如怎么拖动精灵,怎么判断是否摆在正确的位置,怎么进行倒计时等等,真正的Code乐趣快要上场!请听下回分解。


 
 										                        
                        						                        
最后编辑:
作者:freeman
这个作者貌似有点懒,什么都没有留下。

留下一个回复

你的email不会被公开。

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据