在 cocos2d-x之道~制作第一款文字游戏(一)中,使用cocos2d-x把主界面显示出来。分别有每一个级别提供的初始短语TileView,和目标短语TargetView。初步接触了cocos2d-x的基本概念和基础使用方法。这篇博客将会基本实现游戏的逻辑,完毕游戏的主体部分。採用下面步骤:

使TileView可拖动

捕获TileView停止移动的事件

分析TileView是否放在正确的位置上

创建与原来Layer区分的层,放置button、菜单和分数等等。

加入计时和分数

如今開始。继续cocos2d-x之道!

1) 拖放TileView

在TileView的initWithLetter函数中,事实上另一部分工作没完毕。如今要实现拖放效果。就得让TileView处理手势事件,在函数的最后加入以下代码

CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(tile,0,true);

通过CCDirector类。加入TileView为手势处理代理。要处理手势事件,TileView还须要继承CCTargetedTouchDelegate,如代码所看到的

class TileView: public CCNode, public CCTargetedTouchDelegate

然后在类里加入下面变量

	int xOffset;
int yOffset;
bool isControl;

以及重写CCTargetedTouchDelegate继承来的函数

        bool ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent);
void ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent);
void ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent);
void ccTouchCancelled(CCTouch *pTouch, CCEvent *pEvent);

ccTouchBegan是点击開始,ccTouchMoved是手指在屏幕上移动,ccTouchEnded是手指离开屏幕以及ccTouchCancelled是被其它情况打断

实现这几个重要函数前,先把isControl初始化。相同是在initWithLetter的最后加入

tile->isControl = false;

这个变量的作用在后面再解释,先把很重要的手势事件给实现

bool TileView::ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent){
if(!containTouchLocation(pTouch)){
return false;
} isControl = true;
this->setZOrder(999); CCPoint point = pTouch->getLocationInView();
CCPoint touchPoint = CCDirector::sharedDirector()->convertToGL(point);
this->xOffset = touchPoint.x - this->getPositionX();
this->yOffset = touchPoint.y - this->getPositionY(); return true;
} void TileView::ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent){
if(!isControl){
return;
} CCPoint point = pTouch->getLocationInView();
CCPoint touchPoint = CCDirector::sharedDirector()->convertToGL(point);
float x = touchPoint.x - this->xOffset;
float y = touchPoint.y - this->yOffset; this->setPosition(ccp(x,y));
} void TileView::ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent){
CCPoint point = pTouch->getLocationInView();
CCPoint touchPoint = CCDirector::sharedDirector()->convertToGL(point); if(isControl){
isControl = false;
}
} void TileView::ccTouchCancelled(CCTouch *pTouch, CCEvent *pEvent){
ccTouchEnded(pTouch, pEvent);
}

一步步来解释。在ccTouchBegan中,containTouchLocation是推断手指的落点是否在TileView内,仅仅有落点是在TileView内,才进行处理。否则直接返回。然后设置isControl为true,且把拖动的TileView置顶。使用xOffset和yOffset来记录触摸点坐标和TileView坐标的偏移量。在每一个手势事件函数里,都有convertToGL这个函数,它的作用是把触摸的坐标转化成游戏世界里的坐标(很重要)。

ccTouchMoved就是在拖动过程中。不断的改变TileView的坐标。记得把偏移量减掉。

ccTouchEnded就是手指离开屏幕了,把isControl又一次设为false。能够看出,isControl的作用就是控制在整个手势过程中,标记TileView是否被拖动着。当ccTouchEnded或者ccTouchCancelled后,就须要复原。

执行一下,能否够拖动了?奇妙吧。

2) 放下TileView

拖动TileView后。总会有放下的一刻。当放下TileView时,我们就要推断它是否被放在正确的目标TargetView上。这些推断须要MainScene去处理,所以得建立一个TileDropDelegate

class TileDropDelegate{
public:
virtual void dropToPoint(TileView * tile,cocos2d::CCPoint point) = 0;
};

用MainScene继承这个类

class MainScene : public cocos2d::CCLayer, public TileDropDelegate

实现dropToPoint方法

void MainScene::dropToPoint(TileView * tile,CCPoint point){
TargetView * target = NULL;
CCObject * tv;
CCARRAY_FOREACH(pTargets,tv){
TargetView* pTarget = (TargetView*) tv;
if(pTarget->containPoint(point)){
target = pTarget;
break;
}
}
}

遍历存放TargetView的pTargets,检查是否点point在TargetView的frame里。假设发现TileView最后是落在TargetView上,则能够进行结果推断,继续在dropToPoint函数的尾部加入

if(target != NULL){
if(target->getLetter() == tile->getLetter()){
// 1 单词匹配
}else{
// 2 单词不匹配
}
}

1.检查拖动的TileView所代表的字母与TargetView的字母(不可见)是匹配

2.两个字母是不匹配的,须要进行兴许工作

实现了回调函数,还得设置进去给TileView。在dealRandomAnagram中的 tile->randomize(); 这句后面加入

tile->setTileDropDelegate(this);

记得在TileView类里加入setTileDropDelegate函数。

然后。就进行字母匹配后处理。须要将TileView覆盖到TargetView上。

void MainScene::placeTile(TileView * tile,TargetView * target){
tile->setMatch(true);
target->setMatch(true); tile->removeTouchDelegate(); CCActionInterval * actionTo = CCMoveTo::create(0.35,ccp(target->getPositionX(),target->getPositionY()));
tile->runAction(actionTo);
tile->setRotation(0);
target->setVisible(false);
}

代码中把tile和target设置成已匹配,去掉tile的拖放代理,使用CCMoveTo把tile移动到target位置上,并摆正

在1 单词匹配后加入下面语句

placeTile(tile,target);

在2 单词不匹配后加入

			tile->randomize();

			CCActionInterval * actionTo = CCMoveTo::create(0.35,
ccp(tile->getPositionX() + Common::random(-20,20),
tile->getPositionY() + Common::random(-20,30)));
tile->runAction(actionTo);

主要作用是把tile偏离target,表示两者不匹配,如图所看到的

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMTc4MTgzNA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

有了检測匹配函数,就能够推断是否成功完毕游戏。在MainScene中加入

void MainScene::checkForSuccess(){
CCObject * tv;
CCARRAY_FOREACH(pTargets,tv){
TargetView* pTarget = (TargetView*) tv;
if(pTarget->getIsMatch() == false){
return;
}
} CCLog("Game Over!"); }

遍历target,假设所有都匹配了,表示游戏成功。在dropToPoint最后加入

checkForSuccess();

执行游戏。就会发现当全部字母都匹配了,就能打印出Game Over消息

3) 创建HUD层

这是一种游戏设计理念,就是把游戏一些界面上的东西分离到HUD层上。这层的内容主要作用是辅助游戏内容。比方倒计时,button,结束界面等等

首先。我们来创建倒计时,类名为StopWatchView,头文件例如以下

class StopWatchView : public CCLabelAtlas{
public:
StopWatchView(){} static StopWatchView * initView();
void setSecond(int seconds); CREATE_FUNC(StopWatchView);
};

主体部分为setSecond是把秒数拆分成mm:ss形式

void StopWatchView::setSecond(int second){
char strTime[50];
float minutes = second / 60;
minutes = (minutes > 0.0) ? floor(minutes + 0.5) : ceil(minutes - 0.5);
int minu = (int)minutes;
int sec = second % 60;
sprintf(strTime," %02d:%02d",minu,sec);
this->setString(strTime);
}

以下创建HUDView,头文件例如以下

class HUDView : public CCLayer{
public:
// Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone
virtual bool init(); // implement the "static node()" method manually
CREATE_FUNC(HUDView); private:
StopWatchView * pWatch;
CounterPointView * pPointView;
CCMenuItemImage * pButton;
CCMenu * pMenu;
CCLabelTTF * pLabel;
public:
friend class MainScene;
};

当中,CounterPointView, CCmenuItemImage, CCMenu, CCLabelTTF这些是以后使用。friend class MainScene是让MainScene能够訪问HUDView的成员变量。

HUDView.cpp 内仅仅有一个函数,如

bool HUDView::init(){
if(! CCLayer::init()){
return false;
} pWatch = StopWatchView::initView();
pWatch->setSecond(0);
pWatch->setPosition(ccp(Common::getCameraWith()*0.5,
Common::getCameraHeight() * 0.95));
pWatch->setScale(0.7);
this->addChild(pWatch); return true;
}

把StopWatchView加入上去。

然后须要把HUDView加入到MainScene中,所以在MainScene.h里加入

HUDView * pHUD;

加入这个成员变量后,就在MainScene的init函数里,this->addChild(bg)这句后面加入

pHUD = HUDView::create();
this->addChild(pHUD);

然后执行游戏,能够看到如图所看到的

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMTc4MTgzNA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

使用艺术字体

StopWatchView * StopWatchView::initView(){
StopWatchView * p = (StopWatchView*)(CCLabelAtlas::create("11","fonts/tuffy_bold_italic-charmap.plist"));
return p;
}

载入艺术字体后。如图所看到的

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMTc4MTgzNA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">



4) 加入Level计时器

在MainScene.h中加入

int mTimeLeft;

然后在MainScene 中 加入启动计时器,如

void MainScene::startStopWatch(){
mTimeLeft = pLevel->mTimeToSovle;
pHUD->pWatch->setSecond(mTimeLeft); schedule(schedule_selector(MainScene::downTime),1.0f);
}

使用schedule方法,每隔一秒运行一次downTime。有启动计时。就得停止计时

void MainScene::stopStopWatch(){
unschedule(schedule_selector(MainScene::downTime));
}

unschedule后就不会再计时,以下实现downTime函数

void MainScene::downTime(float dt){
mTimeLeft --;
pHUD->pWatch->setSecond(mTimeLeft); if(mTimeLeft == 0){
stopStopWatch();
}
}

内容比較简单,就是实现mTimeLeft的递减和设置给TimeWatchView。而且在归0时停止计时

启动计时的地方在dealRandomAnagram函数中,在最后处加入

startStopWatch();

如图所看到的,游戏開始时就进行倒计时

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMTc4MTgzNA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

当然,游戏结束时别忘了要停止计时。在checkForSuccess的最后加入

stopStopWatch();

5)加入计分

游戏中计分是非常重要的设定。有了计分,才有成就感。以下就開始为游戏加入计分功能

新增Data类。用来存放分数

class Data : public CCObject{
public:
Data();
virtual ~Data(){} void setPoint(int point);
int getPoint();
void addPoint(int point);
void subtractPoint(int point); private:
int mPoints;
};

函数的实现例如以下

Data::Data(){
mPoints = 0;
} void Data::setPoint(int point){
if(point > 0){
mPoints = point;
}else{
mPoints = 0;
}
} int Data::getPoint(){
return mPoints;
} void Data::addPoint(int point){
mPoints += point;
} void Data::subtractPoint(int point){
mPoints -= point;
}

当玩家拖放TileView,放到正确的位置上后,就加入分数,假设放错,则扣分。

在MainScene.h中加入变量

Data * pData;

在MainScene的init函数中。申请变量

pData = new Data();

在dropToPoint中。假设匹配成功,则

pData->addPoint(pLevel->mPointPerTile);

假设匹配失败,则减一半的分

pData->subtractPoint(pLevel->mPointPerTile / 2);

6)在HUD层加入分数显示

分数应该实时显示给玩家看,须要显示则须要使用Label,所以新增CounterPointView

class CounterPointView: public CCLabelAtlas{
public:
static CounterPointView* initWithValue(int v); void countTo(int p, float duration);
void updateValueBy(float dt); void setValue(int v); CREATE_FUNC(CounterPointView); private:
CounterPointView(){}; int value;
float mDelta;
int mEndValue;
float mValueDelta;
};

value是指当前的分数值。 mEndValue是新的分数值,以下先来初始化

CounterPointView* CounterPointView::initWithValue(int v){
CounterPointView * p = (CounterPointView*)(CCLabelAtlas::create("11","fonts/tuffy_bold_italic-charmap.plist"));
char strPoint[10];
sprintf(strPoint,"%d",v);
p->setString(strPoint);
p->value = v;
p->setScale(0.5);
return p;
}

CCLabelAtlas类是能高速渲染的类。用来做变化的数字最合适(FPS的数字就是使用了CCLabelAtlas)

void CounterPointView::setValue(int v){
this->value = v;
char strPoint[10];
sprintf(strPoint,"%d",v);
setString(strPoint);
}

设置分数

void CounterPointView::updateValueBy(float dt){
this->value += (int)mValueDelta; if((int)mValueDelta > 0){
if(this->value > mEndValue){
this->value = mEndValue;
return;
}
}else{
if(this->value < mEndValue){
this->value = mEndValue;
return;
}
} setValue(value); schedule(schedule_selector(CounterPointView::updateValueBy),mDelta);
}

updateValueBy是递增(或递减)分数,每次都加上(或减去)mValueDelta。直到mEndValue,不然的话,每次都运行schedule函数

void CounterPointView::countTo(int to, float duration){
this->mDelta = duration / (abs(to - value) + 1);
if(mDelta < 0.05) mDelta = (float)0.05; mEndValue = to; unscheduleAllSelectors(); if(to - this->value > 0){
mValueDelta = 1;
updateValueBy(1);
}else{
mValueDelta = -1;
updateValueBy(-1);
}
}

countTo函数是对外的接口,目的是在duration时间内达到to值。首先是计算间隔mDelta,然后是unscheduleAllSelectors,接着就開始递增(或递减)了。

在HUDView中加入变量CounterPointView后,在init函数中加入

pPointView = CounterPointView::initWithValue(0);
pPointView->setColor(ccc3(97,25,9));
pPointView->setPosition(ccp(Common::getCameraWith()*0.05,Common::getCameraHeight() * 0.9));
this->addChild(pPointView);

则能够如图所看到的。看到分数

回到MainScene.cpp中,在 pData->addPoint(pLevel->mPointPerTile); 这句后面加入

pHUD->pPointView->countTo(pData->getPoint(),1.5);

在 pData->subtractPoint(pLevel->mPointPerTile / 2); 这句后面加入

pHUD->pPointView->countTo(pData->getPoint(),0.75);

如图所看到的。当摆放正确后。分数递增,摆放错误则分数递减

7)结语

第二篇就到此结束了,由于时间实在是不够用,所以写得非常慢并且非常懒散,差点儿是了了几句(掩面)。从文字上看,这实在不是一篇好教程。只是我认为从中能够学习的是制作游戏过程的思路。

在开发过程中,不断的往主框架里加入新东西,然后让整个游戏变得充实丰满。基本的游戏逻辑在这篇已经完毕了,剩下的就是一些优化方面的东西,还有就是加入一些炫的效果等。

使用cocos2d-x开发会非常方便,也非常有code的感觉,可是C++实在是门不easy驾驭的语言,要非常注意指针和内存方面的使用。只是也会收益匪浅,起码在code方面会提升。最后,放上AnagramPuzzle完整版的代码,欢迎大家拍砖。

cocos2d-x之道~制作第一款文字游戏(二)的更多相关文章

  1. Cocos2D:塔防游戏制作之旅(十二)

    以上代码块相当直观 - 但是它分解的有些细致了. 首先,敌人通过传递HelloWorldLayer对象的引用而初始化.在init方法里,少数重要的变量被设置: maxHP:定义敌人有多经打(Tough ...

  2. 纯CSS3制作九款可爱复古相机

    前言 掐指一算,快两个月没写博客分享了.好吧,我就只是在准备校招而已.现在已经有满意的offer了,所以我就回来啦!这两个月过得挺煎熬也挺充实的.具体细说估计得长篇大论,我就不闲扯了.总之呢,越努力, ...

  3. 我的第一款windows phone软件

    我的第一个windows phone应用发布成功了,大家支持下,名字叫吕氏春秋,发布人是我的英文名xmfdsh http://www.windowsphone.com/zh-cn/store/app/ ...

  4. 用Html5结合Qt制作一款本地化EXE游戏-太空大战(Space War)

    本次来说一说如何利用lufylegend.js引擎制作一款html5游戏后将其通过Qt转换成EXE程序.步骤其实非常简单,接下来就一步步地做一下解释和说明. 首先我们来开发一个有点类似于太空大战的游戏 ...

  5. 如何制作一款HTML5 RPG游戏引擎——第五篇,人物&人物特效

    上一次,我们实现了对话类,今天就来做一个游戏中必不可少的——人物类. 当然,你完全是可以自己写一个人物类,但是为了方便起见,还是决定把人物类封装到这个引擎里. 为了使这个类更有意义,我还给人物类加了几 ...

  6. 如何制作一款HTML5 RPG游戏引擎——第四篇,情景对话

    今天我们来实现情景对话.这是一个重要的功能,没有它,游戏将变得索然无味.所以我们不得不来完成它. 但是要知道,使用对话可不是一件简单的事,因为它内部的东西很多,比如说人物头像,人物名称,对话内容... ...

  7. 如何制作一款HTML5 RPG游戏引擎——第三篇,利用幕布切换场景

    开言: 在RPG游戏中,如果有地图切换的地方,通常就会使用幕布效果.所谓的幕布其实就是将两个矩形合拢,直到把屏幕遮住,然后再展开直到两个矩形全部移出屏幕. 为了大家做游戏方便,于是我给这个引擎加了这么 ...

  8. 如何制作一款HTML5 RPG游戏引擎——第二篇,烟雨+飞雪效果

    今天我们来实现烟雨+飞雪效果.首先来说,一款经典的RPG游戏难免需要加入天气的变化.那么为了使我们的RPG游戏引擎更完美,我们就只好慢慢地实现它. 本文为该系列文章的第二篇,如果想了解以前的文章可以看 ...

  9. 毕业两年半,入手人生第一款macbook pro

    当程序员入手第一款macbook 大家好,我是灰大狼,你们可以叫我灰狼.大狼.甚至是小灰灰. 接下来我主要跟大家分享下作为程序员的我,刚入手一款mac的使用心得. 背景 做程序员三年了,一直用的都是w ...

随机推荐

  1. 文件上传流式处理commons-fileupload

    1. 从请求中获取MultipartFile @RequestMapping(value="/upload", method=RequestMethod.POST) public ...

  2. jquery点击完一个按钮,并且触发另一个按钮

    $a.click(function(){ $b.trigger('click'); });

  3. jmeter连接mysql数据库配置

    用jmeter连接mysql数据库,在配置的过程中遇到了几个坑,跟大家分享一下,避免人人踩坑~~ 关于驱动包:大部分时候是需要下载与服务器的mysql相同版本的jar包~~ 关于驱动包路径:不是所有的 ...

  4. robotframework Selenium2+RFS自动化测试

    支持浏览器版本:Google Chrome (64位) 52.0.2743.82 正式版 52.0.2743.6_chrome_installer 64位 下载地址:http://www.online ...

  5. SQLite基础学习

    SQLite是一款轻量级数据库,集成于android中,以下从分享一下自己学习的. 在查阅资料时有一些好的说明就直接用了: 主要的curd语句 以下SQL语句获取5条记录,跳过前面3条记录 selec ...

  6. centos7.0查看IP

    原文:centos7.0查看IP 输入ip查询命名 ip addr  也可以输入 ifconfig(centOs7没有ifconfig命令)查看ip,但此命令会出现3个条目,centos的ip地址是e ...

  7. ViewPage第二课为ViewPage加入标题

    在第一课 学前准备:掌握ViewPage第一课http://blog.csdn.net/wei_chong_chong/article/details/50468832 为ViewPage加入标题: ...

  8. Android JavaMail介绍及发送一封简单邮件

    本文来自:高爽|Coder,原文地址:http://blog.csdn.net/ghsau/article/details/17839983,转载请注明.       JavaMail是SUN提供给开 ...

  9. WebApi自定义返回类型和命名空间实现

    1.自定义ContentNegotiator /// <summary> /// 返回json的ContentNegotiator /// </summary> public ...

  10. 28、应用调试之strace命令来跟踪系统调用

    strace是个工具,在使用时需要先按照,见韦东山书籍: 1.tar xjf starce-4.5.15.tar.bz2 2.cd strace-4.5.15/ 3.patch -p1 < .. ...