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


    在 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,表示两者不匹配,如图所看到的


    有了检測匹配函数,就能够推断是否成功完毕游戏。在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);
    然后执行游戏,能够看到如图所看到的


    使用艺术字体

    StopWatchView * StopWatchView::initView(){
    	StopWatchView * p = (StopWatchView*)(CCLabelAtlas::create("11","fonts/tuffy_bold_italic-charmap.plist"));
    	return p;
    }
    载入艺术字体后。如图所看到的



    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();
    如图所看到的,游戏開始时就进行倒计时


    当然,游戏结束时别忘了要停止计时。在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完整版的代码,欢迎大家拍砖。






  • 相关阅读:
    Fixed Function Shader
    sqlserver 2014 数据库作业 通过脚本创建注意事项
    块存储、文件存储、对象存储意义及差异
    程序员如何成为架构师
    那些编程水平很高的程序员是怎么练成的?
    在ASP.NET Core调用WebService
    .net core 调用webservice同步方法
    Sqlserver中如何创建链接服务器
    JWT实现鉴权
    JWT原理实现代码
  • 原文地址:https://www.cnblogs.com/zhchoutai/p/7262497.html
Copyright © 2020-2023  润新知