由于本人最近在学习cocos2d-x,一时手痒,写一个游戏练练手,也是对cocos2d-x进一步的巩固,于是敲了两天,就写了这个拼图游戏,还挺有成就感的,:-),先把我的成果展示如下:
由于cocos2d的跨平台性,此游戏已移植到win、android平台。
言归正传,首先准备工具:
1.一张用来分割的整张大图片;
2.退出按钮的正常和按下两张图片;
3.重新开始按钮的正常和按下两张图片;
4.背景音乐素材,:-)我在这个东西上吃过亏,本来找了一个wma格式的音乐,结果在win上没问题,但linux和android上都放不出声音,后来干脆转换成了mp3,大就大点吧,先忍了。
5.没了,下面开工吧
创建工程就不必说了,就拿cocos2d的HelloCPP工程就行,把HelloWorld类中的init中的除了
if ( !CCLayer::init() )
{
return false;
}
和
return true;
保留,其余全部删除,我们的代码全部写在这两部分的中间。
首先修改头文件中如下:
#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__
#include "cocos2d.h"
class HelloWorld : public cocos2d::CCLayer
{
public:
// 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();
// a selector
callback
void
menuCloseCallback(CCObject* pSender);
void
menuRefreshCallback(CCObject *pSender);
// implement the "static
node()" method manually
CREATE_FUNC(HelloWorld);
static const int rows =
4; // 定义将图片分成多少行
static const int cols =
5; // 定义将图片分成多少列
static const int
picFadeOutTime = 5; // 图片展示3秒,消失的过程2秒,共5秒
static const int
fragsFadeInTime = 1; // 切片出现所需时间1秒
static const int
shuffleFragsTime = 1; // 乱序排列时间1秒
private:
cocos2d::CCSprite *pic;
// 一整张图片
cocos2d::CCArray *frags;
// 保存切片的数组
cocos2d::CCSprite
*selectedFrag; // 手指触摸处的切片的副本
cocos2d::CCLabelTTF
*winLabel; // 显示YOU WIN!!!标签
cocos2d::CCMenuItemImage
*closeItem; // 退出按钮
cocos2d::CCMenuItemImage
*refreshItem; // 重排按钮
int fragWidth; //
切片的宽
int fragHeight; //
切片的高
cocos2d::CCSize winSize;
// 窗体的尺寸
void prepareFrags(); //
准备切片
void displayPic(); //
显示整张图片,然后消失
void displayFrags(); //
显示所有的切片
void shuffleFrags(); //
打乱切片的顺序
int
currIndexOnPos(cocos2d::CCPoint &point); // 当前点所在的位置序号
bool didGameWin(); //
判断游戏是否完成
cocos2d::CCPoint
posAtIndex(int index); // 某个位置序号的坐标
int fragIndexArray[rows
* cols]; // 切片的顺序数组,当数组中的数字是按从小到大依次排列时,则游戏结束
int selectedFragIndex;
// 拖动的切片序号,与位置的序号不是一个概念
protected:
void onEnter();
void
ccTouchesBegan(cocos2d::CCSet *pTouches, cocos2d::CCEvent
*pEvent);
void
ccTouchesMoved(cocos2d::CCSet *pTouches, cocos2d::CCEvent
*pEvent);
void
ccTouchesEnded(cocos2d::CCSet *pTouches, cocos2d::CCEvent
*pEvent);
};
首先是init的实现,首先创建一个CCSprite,显示一整张图片:
pic = CCSprite::create(pathToPicture);
要说明一点的是,因为图片与屏幕的分辨率是不一样的,直接贴到屏幕上会导致只又一部分显示出来或者有黑边的情况,本人就遇到这样的问题,一开始还搜索如何缩放CCImage来达到与屏幕适配的效果,果断google了一番,终于知道,最好的方法其实是设置屏幕的分辨率与图片一致,这样的话,对以后换图片比较方便,具体如下:
CCDirector::sharedDirector()->getOpenGLView()->setDesignResolutionSize(pic->getContentSize().width,
pic->getContentSize().height, kResolutionExactFit);
将OpenGLView的分辨率设置为跟图片一样就行,前两个分别石宽和高,第三个是设置分辨率的策略,一共有6种,在这里我直接使用kResolutionExactFit,表示精确匹配,这样图片才能够完全填满屏幕,至于其他的几种,有兴趣的可以自己去研究一番
enum ResolutionPolicy
{
kResolutionExactFit,
kResolutionNoBorder,
kResolutionShowAll,
kResolutionFixedHeight,
kResolutionFixedWidth,
kResolutionUnKnown,
};
然后就是给pic定位了:
winSize = CCDirector::sharedDirector()->getWinSize();
pic->setPosition(ccp(winSize.width / 2, winSize.height /
2));
this->addChild(pic, 0);
把图片放到屏幕中间
至于两个按钮如何创建和定位,这里就省略了,在下确实比较懒一点,当然也是为了博文不要又臭又长。
接下来就要调用prepareFrags()来准备切片了
具体如下:
void HelloWorld::prepareFrags()
{
frags = CCArray::create();
char name[10];
for(int j = 0; j < rows; ++ j) {
for(int i = 0; i < cols; ++ i) {
sprintf(name, "fragd", i + j * cols);
CCSpriteFrame *fragFrame =
CCSpriteFrame::create(picture,
CCRectMake(i * fragWidth, winSize.height - (j + 1) *
fragHeight, fragWidth, fragHeight));
CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFrame(fragFrame,
name);
CCSprite *frag =
CCSprite::createWithSpriteFrame(fragFrame);
frag->setOpacity(0);
frag->setPosition(posAtIndex(i + j * cols));
frags->addObject(frag);
this->addChild(frag, 1);
}
}
frags->retain();
}
切片的方法使用的是static CCSpriteFrame* create(const char* filename,
const CCRect&
rect);根据rect指定的区域不同,切除图片的不同部分,这里要强调的石CCSpriteFrame使用的坐标左上角为原点,x向又增长,y向下增长,这与屏幕的坐标系是不一样的,刚开始我不知道,在这个上面走了点弯路,晕死了,太坑人了,基本把坐标系的问题解决后就没什么问题了。
OK,前期工作已搞定,下面就是见证奇迹的时刻。
在显示当前场景是,cocos2d会调用onEnter虚函数,因此需要重新实现此函数。
因为刚刚学习了cocos2d的延时动作和即时动作,这里全部使用动作动作来实现调用相应的函数
void HelloWorld::onEnter()
{
CCLayer::onEnter();
setTouchEnabled(true);
CCActionInterval *delayForDisplayPic =
CCDelayTime::create(3);
CCActionInterval *delayForDisplayFrags =
CCDelayTime::create(picFadeOutTime);
CCActionInterval *delayForShuffleFrags =
CCDelayTime::create(picFadeOutTime + fragsFadeInTime);
CCActionInstant *displayPicCallback = CCCallFunc::create(this,
callfunc_selector(HelloWorld::displayPic));
CCActionInstant *displayFragsCallback =
CCCallFunc::create(this,
callfunc_selector(HelloWorld::displayFrags));
CCActionInstant *displayShuffleFragsCallback =
CCCallFunc::create(this,
callfunc_selector(HelloWorld::shuffleFrags));
this->runAction(CCSequence::create(delayForDisplayPic,
displayPicCallback, 0));
this->runAction(CCSequence::create(delayForDisplayFrags,
displayFragsCallback, 0));
this->runAction(CCSequence::create(delayForShuffleFrags,
displayShuffleFragsCallback, 0));
CocosDenshion::SimpleAudioEngine::sharedEngine()>playBackgroundMusic("Resources/bgsound.mp3",
true);
}
拿DisplayPic来说,此动作我设计的是先让整张图片显示3秒,然后图片消失,消失时间为两秒,因此首先定义一个CCActionInterval
*delayForDisplayPic = CCDelayTime::create(3);
再定义一个及时动作:
CCActionInstant *displayPicCallback =
CCCallFunc::create(this,callfunc_selector(HelloWorld::displayPic));
this->runAction(CCSequence::create(delayForDisplayPic,
displayPicCallback, 0));
的后,首先前三秒内没有任何动作,3秒钟后再调用即时动作,来调用HelloWorld::displayPic函数,基本也没什么难的。
所有动作的执行顺序是图片显示3秒,用2秒消失,然后所有的切片用1秒的时间来显示出来,最后是用1秒的时间来打乱切片的顺序,因此三个CCDelayTime的时间要算好。
最后在玩游戏的时候为了让人更双一点,可以边玩边听音乐(其实也未必好,我只是觉得好玩:-))
CocosDenshion::SimpleAudioEngine::sharedEngine()>playBackgroundMusic("Resources/bgsound.mp3",
true);
OK,这个函数就这么完成了,下面要来实现三个动作了,首先是整张图片用2秒时间消失:
void HelloWorld::displayPic()
{
CCActionInterval *fadeOut =
CCFadeOut::create(picFadeOutTime - 3);
pic->runAction(fadeOut);
}
然后是所有的切片显示出来:
void HelloWorld::displayFrags()
{
for(unsigned int i = 0; i < frags->count(); ++ i)
{
CCActionInterval *fadeIn =
CCFadeIn::create(fragsFadeInTime);
CCSprite *frag =
dynamic_cast(frags->objectAtIndex(i));
frag->runAction(fadeIn);
}
}
当所有的切片显示出来是,好像是整张图片又显示出来了,这是因为一开始创建切片的时候,所有的切片都放在了正确的位置上,没关系,因为切片显示出来后就会把它们全部大乱:
void HelloWorld::shuffleFrags()
{
shuffle(fragIndexArray, rows * cols);
for(int i = 0; i < rows * cols; ++ i) {
CCSprite *sprite =
dynamic_cast(frags->objectAtIndex(fragIndexArray[i]));
CCPoint dest = posAtIndex(i);
CCActionInterval *moveToDest =
CCMoveTo::create(shuffleFragsTime, dest);
sprite->runAction(moveToDest);
}
}
首先定义一个打乱位置数组的函数,然后每张切片根据位置信息移动到相应的位置上去。
我把shuffle定义成了一个全局函数
void shuffle(int *array, int size)
{
int p1, p2, tmp;
for(int i = 0; i < size * size; ++ i) {
p1 = rand() % size;
p2 = rand() % size;
tmp = array[p1];
array[p1] = array[p2];
array[p2] = tmp;
}
}
有必要说一下我的思路:
屏幕上的位置是按从小到大的顺序排列的,位置数组中则是乱的:
位置: 0 1 2 3 4
5 6 7
8 9
切片: 5 3 6 7 1
2 4 0
8 9
则切片数组中的第5个切片移到第0号位置上,第3个切片一道第1号位置上,第6个切片一道第二号位置上,依次类推,因此还要一个函数计算某个位置序号的所对应的坐标:
CCPoint HelloWorld::posAtIndex(int index)
{
int x = index % cols;
int y = index / cols;
x = x * fragWidth + fragWidth / 2;
y = y * fragHeight + fragHeight / 2;
return ccp(x, y);
}
屏幕上的位置编号为:
16 17 18
19 20
11 12 13
14 15
6 7 8 9 10
0 1 2 3 4 5
又一个部分完成了,下面就是到了互动环节了,:-),说了这么多,总算开始游戏了,好累啊,休息一下。。。。。。
交互的部分主要是三个虚函数的实现:
void HelloWorld::ccTouchesBegan(CCSet *pTouches, CCEvent
*pEvent); //
手指按下时调用
void HelloWorld::ccTouchesMoved(cocos2d::CCSet *pTouches,
cocos2d::CCEvent *pEvent); // 手指移动时调用
void HelloWorld::ccTouchesEnded(cocos2d::CCSet *pTouches,
cocos2d::CCEvent *pEvent); // 手指放开时调用
首先是手指按下,此时要判断当前按下的是拿一张切片,先要得到按下的是哪一个点,需要将这个点转化为gl的坐标:
CCLayer::ccTouchesBegan(pTouches, pEvent);
CCSetIterator iter = pTouches->begin();
CCTouch *touch = dynamic_cast(*iter);
CCPoint touchLocation = touch->getLocationInView();
touchLocation =
CCDirector::sharedDirector()->convertToGL(touchLocation);
再定义一个函数,用来判断当前按下的点属于哪一个位置:
int HelloWorld::currIndexOnPos(CCPoint &point)
{
int x = point.x / fragWidth;
int y = point.y / fragHeight;
return x + y * cols;
}
然后在切片位置数组中找到当前是哪一个切片,这里使用了CCSpriteFrameCache,还记得创建切片的方法吗:
char name[10];
sprintf(name, "fragd", fragIndexArray[spriteIndex]);
selectedFragIndex = spriteIndex;
selectedFrag =
CCSprite::createWithSpriteFrameName(name);
selectedFrag->setScale(0.9);
selectedFrag->setOpacity(200);
this->addChild(selectedFrag, 2);
当手指按下一个切片是,将按下的切片复制一份,但稍微小一点,而且也不是不透明的,这个切片会跟着手指在屏幕上移动,当手指松开是,切片消失,那么下面就涉及到了移动的函数了:
void HelloWorld::ccTouchesMoved(CCSet *pTouches, CCEvent
*pEvent)
{
CCSetIterator iter = pTouches->begin();
CCTouch *touch = dynamic_cast(*iter);
CCPoint touchLocation = touch->getLocationInView();
touchLocation =
CCDirector::sharedDirector()->convertToGL(touchLocation);
if(selectedFrag) {
selectedFrag->setPosition(touchLocation);
}
}
基本不用过多的解释,一看便知。
当手指松开是,最初选择的切片需要与松开处的切片调换位置:
void HelloWorld::ccTouchesEnded(CCSet *pTouches, CCEvent
*pEvent)
{
if(selectedFrag) {
this->removeChild(selectedFrag);
selectedFrag = 0;
CCSetIterator iter = pTouches->begin();
CCTouch *touch = dynamic_cast(*iter);
CCPoint touchLocation = touch->getLocationInView();
touchLocation =
CCDirector::sharedDirector()->convertToGL(touchLocation);
int destFragIndex = currIndexOnPos(touchLocation);
if(destFragIndex >= 0) {
CCSprite *selected =
dynamic_cast(frags->objectAtIndex(fragIndexArray[selectedFragIndex]));
CCSprite *dest =
dynamic_cast(frags->objectAtIndex(fragIndexArray[destFragIndex]));
CCActionInterval *selectedMovetoDest = CCMoveTo::create(0.3,
dest->getPosition());
CCActionInterval *destMovetoSelected = CCMoveTo::create(0.3,
selected->getPosition());
selected->runAction(selectedMovetoDest);
dest->runAction(destMovetoSelected);
int tmp = fragIndexArray[selectedFragIndex];
fragIndexArray[selectedFragIndex] =
fragIndexArray[destFragIndex];
fragIndexArray[destFragIndex] = tmp;
if(didGameWin()) {
winLabel->setVisible(true);
CCBlink *blink = CCBlink::create(1, 5);
winLabel->runAction(blink);
closeItem->setVisible(true);
refreshItem->setVisible(true);
}
}
}
}
调换位置使用了又延时动作,然后将切片在位置数组中的对应关系也调换一下:
每调换一次就要检查当前是否完成了游戏,如果完成了游戏,则显示YOU WIN,并将退出按钮和重玩按钮显示出来:
bool HelloWorld::didGameWin()
{
for(int i = 0; i < cols * rows; ++ i) {
if(fragIndexArray[i] != i)
return false;
}
return true;
}
到这里,一个小小的游戏就初步完成了,还是挺有成就感的,不过玩了两把就不想再玩了,毕竟是拿来练手的,没多大意思,但成就感主要在编写游戏的思考过程中,顺便也确实感受到了cocos2d的强大。
因为我是才开始学cocos2d不久,所以这个游戏肯定还有不足指出,以后随着学习的深入,会不断的完善,不过现在我还是来炫耀一把,把他一直到手机上玩玩,过过瘾: