Flappybird Demo版

Flappybird 作为风靡一时的游戏,其实其实现并不复杂。今天的目标就是利用cocos2dx 3.0和Box2D打造一款自己的Flappybird。 Go!


#ifndef __FLAPPY_BIRD_H__
#define __FLAPPY_BIRD_H__

#include "cocos2d.h"
#include "Box2D.h"
#include "VisibleRect.h"
#include "cocos-ext.h"
#include "Box2dHandler.h"
using namespace cocos2d;

enum {
	kTagTitle = 150,
	kTagHandler = 151,
	kTagMenuBird = 152,
	kTagStart = 153,
	kTagBird=200,
	kTagObstacle1 = 201,
	kTagObstacle2 = 202,
	kTagObstacle3 = 203,
	kTagScore = 204,
};



class Flappybird : public cocos2d::Layer
{
public:
	 static Flappybird* app;
        // there's no 'id' in cpp, so we recommend returning the class instance pointer
         static cocos2d::Scene* createScene();
         Flappybird();
	~Flappybird();

	// Touch process
	 bool onTouchBegan(Touch* touch, Event* pEvent);
	 void onTouchEnded(Touch* touch, Event* pEvent);

	 void bgChange(Node*);

	 void update(int birdx, int birdy, int birdw, int birdH);
	 void obstacle1ChangePosition(Node*);
	 void obstacle2ChangePosition(Node*);

	 void gotoGameStart();
	 void gotoGamePlay();
	 void gotoGameOver();
	 void restart();
	 void updateScore();

	 static bool obstacle_in_range;
         static bool obstacle2_in_range;
private:
	 int m_state;
	 int m_score;
};

#endif // __FLAPPY_BIRD_H__

m_state 是个状态机变量,用于控制游戏的不同状态。 m_score用于记录游戏的分数。


void gotoGameStart();
void gotoGamePlay();
void gotoGameOver();
处理三个不同状态的初始化及跳转代码。


void update(int birdx, int birdy, int birdw, int birdH);
这个方法主要用于碰撞检测。

void restart();
重新启动游戏方法。

void bgChange(Node*);
实现循环地图的方法。


Flappybird游戏主要包括:

1. 静态的背景【或者动态的背景】

2. 滚动的地图块

3. 移动的障碍【柱子】

4. 重力小鸟。

静态背景非常好实现。就是在Layer上加一个背景Sprite就行了。

滚动的地图我的实现是不断移动一张可以无缝连接的图片。具体实现参见下面的Flappybird.cpp代码。

移动的障碍,我使用了两个Sprite对象。然后给它们随机的位置。同时不断地往左边滚动。出屏后要重新设置位置。重设的代码在

void obstacle1ChangePosition(Node*);
void obstacle2ChangePosition(Node*);

重力小鸟

我使用的是Box2D刚体,由于重力场的作用,它会往下掉。然后再触摸方法中给它一个向上的冲量。这样如果你点击屏幕,小鸟就会往上运动了。

下面给出实现代码


#include "Flappybird.h"

#define PTM_RATIO 32
USING_NS_CC;

#define STATE_START 0
#define STATE_GAMEPLAY 1
#define STATE_GAMEOVER 2


bool Flappybird::obstacle_in_range = false;
bool Flappybird::obstacle2_in_range = false;
Flappybird* Flappybird::app = NULL;


Scene* Flappybird::createScene()
{
    auto scene = Scene::create();
    auto layer =  new Flappybird();
    scene->addChild(layer);
    layer->release();
    return scene;
}

Flappybird::Flappybird() 
{
    gotoGameStart();
    Sprite* start = Sprite::create("start.png");
    int height = start->getContentSize().height;
    start->setTag(kTagStart);
    start->setPosition(VisibleRect::getVisibleRect().getMaxX()/2, height/2 + 150);
    addChild(start);
    auto listener_to_start = EventListenerTouchOneByOne::create();
    listener_to_start->setSwallowTouches(true);
	
    listener_to_start->onTouchBegan = [](Touch* touch, Event* event){  
    auto target = static_cast<Sprite*>(event->getCurrentTarget()); 
    Point locationInNode = target->convertToNodeSpace(touch->getLocation());  
    Size s = target->getContentSize();  
    Rect rect = Rect(0, 0, s.width, s.height);   
    if (rect.containsPoint(locationInNode))
    {
	  target->setScale(1.2);
	  return true;  
     }
     else  
	  return false;  
     };  
  
     listener_to_start->onTouchMoved = [](Touch* touch, Event* event){  
	 auto target = static_cast<Sprite*>(event->getCurrentTarget());  
	 target->setScale(1.2);
     };  

     listener_to_start->onTouchEnded = [=](Touch* touch, Event* event){  
 	 auto target = static_cast<Sprite*>(event->getCurrentTarget()); 
	 target->setScale(1.0);
 	 this->getChildByTag(kTagTitle)->setVisible(false);
 	 this->getChildByTag(kTagMenuBird)->setVisible(false);
 	 start->setVisible(false);
	  gotoGamePlay();
	  _eventDispatcher->removeEventListener(listener_to_start);
		
      };  
      _eventDispatcher->addEventListenerWithSceneGraphPriority(listener_to_start, start);  
      if(app == NULL)
	 app = this;
}
  

void Flappybird::gotoGameStart()
{
 	m_score = 0;
	m_state = STATE_START;
 	auto dispatcher = Director::getInstance()->getEventDispatcher();
	auto touchListener = EventListenerTouchOneByOne::create();
	touchListener->onTouchBegan = CC_CALLBACK_2(Flappybird::onTouchBegan, this);
	touchListener->onTouchEnded = CC_CALLBACK_2(Flappybird::onTouchEnded, this);
	dispatcher->addEventListenerWithSceneGraphPriority(touchListener, this);

 	Sprite* bg = Sprite::create("flappy_bird_bg.png");
	addChild(bg,  -1);
	bg->setPosition(ccp( VisibleRect::center().x, VisibleRect::center().y));

	Box2dHandler * handler = Box2dHandler::handler();
	handler->setTag(kTagHandler);
 	this->addChild(handler);

   	Sprite* title = Sprite::create("title.png");
 	title->setPosition(VisibleRect::getVisibleRect().getMaxX()/2 - 60, VisibleRect::getVisibleRect().getMaxY() - 150);
  	title->setTag(kTagTitle);
	addChild(title);
        title->runAction(CCRepeatForever::create(CCSequence::create(CCMoveBy::create(0.5, Point(0, -15)), CCMoveBy::create(1, Point(0, 30)), CCMoveBy::create(0.5, Point(0, -15)), NULL)));
	

  	B2Sprite *menu_bird = B2Sprite::create("bird", 20);
 	menu_bird->setPosition(VisibleRect::getVisibleRect().getMaxX()/2 + 120, VisibleRect::getVisibleRect().getMaxY() - 150);
  	menu_bird->setTag(kTagMenuBird);
	Action* action = CCRepeatForever::create(CCSequence::create(CCMoveBy::create(0.5, Point(0, -15)), CCMoveBy::create(1, Point(0, 30)), CCMoveBy::create(0.5, Point(0, -15)), NULL));
	menu_bird->runAction(action);
	 addChild(menu_bird);

	Sprite* scrollbar = Sprite::create("scrollbar.png");
	int height = scrollbar->getContentSize().height;
	scrollbar->setPosition(VisibleRect::getVisibleRect().getMaxX()/2, height/2);

	Action* scroll = CCSequence::create(CCMoveBy::create(0.5, ccp(-88, 0)), CCCallFuncN::create(this, callfuncN_selector(Flappybird::bgChange)), NULL);
 	scrollbar->runAction(scroll);
	addChild(scrollbar, 0, 0);
}

void Flappybird::gotoGamePlay()
{
 	if(m_state == STATE_START)
	 {
	     auto score = LabelAtlas::create("0", "fonts/tuffy_bold_italic-charmap.plist");
             addChild(score, 0, kTagScore);
             score->setOpacity( 126 );
             score->setPosition(ccp( VisibleRect::center().x, VisibleRect::top().y-150));

	      m_state = STATE_GAMEPLAY;
	      B2Sprite* bird = B2Sprite::create("bird", 20);
	      bird->setPosition(VisibleRect::getVisibleRect().getMaxX()/2, VisibleRect::getVisibleRect().getMaxY() - 400);
	      bird->setTag(kTagBird);
	     ((Box2dHandler*)(this->getChildByTag(kTagHandler)))->addBodyForSprite(bird);
	      addChild(bird);

	      Sprite* obstace1 = Sprite::create("obstacle.png");
	      obstace1->setPosition(VisibleRect::getVisibleRect().getMaxX() *2 +  CCRANDOM_0_1() * 40, VisibleRect::getVisibleRect().getMaxY()/2 + CCRANDOM_0_1() * 120);
	      obstace1->setTag(kTagObstacle1);
	      Action* scroll1 = CCSequence::create(CCMoveTo::create(16, ccp(-50, obstace1->getPositionY())), CCCallFuncN::create(this, callfuncN_selector(Flappybird::obstacle1ChangePosition)), NULL);
	      obstace1->runAction(scroll1);
	      addChild(obstace1);

	      Sprite* obstace2 = Sprite::create("obstacle.png");
	      obstace2->setPosition(VisibleRect::getVisibleRect().getMaxX()*5/2 +  CCRANDOM_0_1() * 20, VisibleRect::getVisibleRect().getMaxY()/2 - CCRANDOM_0_1() * 120);
	      obstace2->setTag(kTagObstacle2);
	      Action* scroll2 = CCSequence::create(CCMoveTo::create(20, ccp(-50, obstace2->getPositionY())), CCCallFuncN::create(this, callfuncN_selector(Flappybird::obstacle2ChangePosition)), NULL);
	      obstace2->runAction(scroll2);
	      addChild(obstace2);
 	}
}

void Flappybird::gotoGameOver()
{
	if(m_state == STATE_GAMEPLAY)
	{
		this->getChildByTag(kTagObstacle1)->stopAllActions();
		this->getChildByTag(kTagObstacle2)->stopAllActions();
		m_state = STATE_GAMEOVER;
		Sprite* gameover = Sprite::create("gameover.png");
		 this->addChild(gameover);
		gameover->setPosition(VisibleRect::getVisibleRect().getMaxX()/2, VisibleRect::getVisibleRect().getMaxY() + 50);
		gameover->runAction(CCMoveTo::create(0.2, Point(VisibleRect::getVisibleRect().getMaxX()/2, VisibleRect::getVisibleRect().getMaxY() /2 + 20)));
		Sprite* ok = Sprite::create("ok.png");
		this->addChild(ok);
		ok->setPosition(VisibleRect::getVisibleRect().getMaxX()/2, -50);
		ok->runAction(CCMoveTo::create(0.2, Point(VisibleRect::getVisibleRect().getMaxX()/2,  40)));
		auto listener_to_ok = EventListenerTouchOneByOne::create();
		listener_to_ok->onTouchBegan = [](Touch* touch, Event* event){  
		auto target = static_cast<Sprite*>(event->getCurrentTarget()); 
 		Point locationInNode = target->convertToNodeSpace(touch->getLocation());  
		Size s = target->getContentSize();  
  		Rect rect = Rect(0, 0, s.width, s.height);   
  		if (rect.containsPoint(locationInNode))
		{
		    target->setScale(1.2);
		    return true;  
 		 }
		 else  
		    return false;  
	    };  
  
	  listener_to_ok->onTouchMoved = [](Touch* touch, Event* event){  
 		auto target = static_cast<Sprite*>(event->getCurrentTarget());  
                target->setScale(1.2);
	  };  

	 listener_to_ok->onTouchEnded = [=](Touch* touch, Event* event){  
	 auto target = static_cast<Sprite*>(event->getCurrentTarget()); 
	 target->setScale(1.0);
	 ok->removeFromParentAndCleanup(true);
	 gameover->removeFromParentAndCleanup(true);

	 restart();
 	 _eventDispatcher->removeEventListener(listener_to_ok);
		
 	};  
 	_eventDispatcher->addEventListenerWithSceneGraphPriority(listener_to_ok, ok);  

     }
}

void Flappybird::restart()
{
	 if( m_state == STATE_GAMEOVER)
	 {
		Sprite* start = (Sprite*) this->getChildByTag(kTagStart);
 		start->setVisible(true);
 		this->getChildByTag(kTagScore)->setVisible(false);
		this->getChildByTag(kTagTitle)->setVisible(true);
 		this->getChildByTag(kTagMenuBird)->setVisible(true);
		this->getChildByTag(kTagObstacle1)->setVisible(false);
 		this->getChildByTag(kTagObstacle2)->setVisible(false);
 		auto listener_to_start = EventListenerTouchOneByOne::create();
		listener_to_start->setSwallowTouches(true);
	
		listener_to_start->onTouchBegan = [](Touch* touch, Event* event){  
		auto target = static_cast<Sprite*>(event->getCurrentTarget()); 
	        Point locationInNode = target->convertToNodeSpace(touch->getLocation());  
		Size s = target->getContentSize();  
 		Rect rect = Rect(0, 0, s.width, s.height);   
		if (rect.containsPoint(locationInNode))
		{
 				target->setScale(1.2);
				return true;  
 		}
 		else  
				 return false;  
 		};  
  
		 listener_to_start->onTouchMoved = [](Touch* touch, Event* event){  
			auto target = static_cast<Sprite*>(event->getCurrentTarget());  
 			target->setScale(1.2);
 		};  

 		listener_to_start->onTouchEnded = [=](Touch* touch, Event* event){
 		m_score = 0;
		auto target = static_cast<Sprite*>(event->getCurrentTarget()); 
 		target->setScale(1.0);
 		this->getChildByTag(kTagTitle)->setVisible(false);
		this->getChildByTag(kTagMenuBird)->setVisible(false);


		B2Sprite* bird = B2Sprite::create("bird", 20);
		bird->setPosition(VisibleRect::getVisibleRect().getMaxX()/2, VisibleRect::getVisibleRect().getMaxY() - 400);
		bird->setTag(kTagBird);
		((Box2dHandler*)(this->getChildByTag(kTagHandler)))->addBodyForSprite(bird);
		addChild(bird);

 		Sprite* obstace1 =(Sprite*) this->getChildByTag(kTagObstacle1);
  		obstace1->resumeSchedulerAndActions();
 		obstace1->setVisible(true);
		obstace1->setPosition(VisibleRect::getVisibleRect().getMaxX() *2 +  CCRANDOM_0_1() * 40, VisibleRect::getVisibleRect().getMaxY()/2 + CCRANDOM_0_1() * 120);
 		Action* scroll1 = CCSequence::create(CCMoveTo::create(16, ccp(-50, obstace1->getPositionY())), CCCallFuncN::create(this, callfuncN_selector(Flappybird::obstacle1ChangePosition)), NULL);
 		obstace1->runAction(scroll1);
		

 		Sprite* obstace2 =(Sprite*) this->getChildByTag(kTagObstacle2);
 		obstace2->setVisible(true);
 		obstace2->resumeSchedulerAndActions();
		obstace2->setPosition(VisibleRect::getVisibleRect().getMaxX()*5/2 +  CCRANDOM_0_1() * 20, VisibleRect::getVisibleRect().getMaxY()/2 - CCRANDOM_0_1() * 120);
		Action* scroll2 = CCSequence::create(CCMoveTo::create(20, ccp(-50, obstace2->getPositionY())), CCCallFuncN::create(this, callfuncN_selector(Flappybird::obstacle2ChangePosition)), NULL);
		obstace2->runAction(scroll2);


		start->setVisible(false);
 		this->getChildByTag(kTagScore)->setVisible(true);
		updateScore();
		m_state = STATE_GAMEPLAY;
 		_eventDispatcher->removeEventListener(listener_to_start);
	    };  
	    _eventDispatcher->addEventListenerWithSceneGraphPriority(listener_to_start, start);  
	}
}

void Flappybird::bgChange(Node* node)
{
 	Sprite* bg = (Sprite*) this->getChildByTag(0);
	int height = bg->getContentSize().height;
 	bg->setPosition(ccp(VisibleRect::getVisibleRect().getMaxX()/2, height/2));
	bg->update(1/60);
	bg->runAction(CCSequence::create(CCMoveBy::create(0.5, ccp(-88, 0)), CCCallFuncN::create(this, callfuncN_selector(Flappybird::bgChange)), NULL));
}

void Flappybird::obstacle1ChangePosition(Node* node)
{
 	Sprite* obstacle1 = (Sprite*) this->getChildByTag(kTagObstacle1);
	int sign = CCRANDOM_0_1() >0.5? 1:-1;
 	obstacle1->setPosition(ccp(VisibleRect::getVisibleRect().getMaxX()+ 20,  VisibleRect::getVisibleRect().getMaxY()/2 + sign * CCRANDOM_0_1() * 120));
	obstacle1->runAction( CCSequence::create(CCMoveTo::create(8, ccp(-50, obstacle1->getPositionY())), CCCallFuncN::create(this, callfuncN_selector(Flappybird::obstacle1ChangePosition)), NULL));
}

void Flappybird::obstacle2ChangePosition(Node* node)
{
	Sprite* obstacle2 = (Sprite*) this->getChildByTag(kTagObstacle2);
  	int sign = CCRANDOM_0_1() >0.5? 1:-1;
	obstacle2->setPosition(ccp(VisibleRect::getVisibleRect().getMaxX()+ 20,  VisibleRect::getVisibleRect().getMaxY()/2 + sign * CCRANDOM_0_1() * 120));
 	obstacle2->runAction( CCSequence::create(CCMoveTo::create(8, ccp(-50, obstacle2->getPositionY())), CCCallFuncN::create(this, callfuncN_selector(Flappybird::obstacle2ChangePosition)), NULL));
}

void Flappybird::updateScore()
{
      auto score = (LabelAtlas*)(this->getChildByTag(kTagScore));
      char string[12] = {0};
      sprintf(string, "%d", m_score);
      score->setString(string);
}

void Flappybird::update(int birdx, int birdy, int birdw, int birdh)
{
	Sprite* obstace1 = (Sprite *)(this->getChildByTag(kTagObstacle1));
	Sprite* obstace2 = (Sprite *)(this->getChildByTag(kTagObstacle2));
 	B2Sprite* bird = (B2Sprite*)(this->getChildByTag(kTagBird));
 	if(m_state == STATE_GAMEPLAY)
	 {
		float width = obstace1->getContentSize().width;
 		float spaceH = 176;
		bool collide = false;
  		if(obstace1->getPositionX() + width/2 >=  birdx - birdw/2 && obstace1->getPositionX() < VisibleRect::getVisibleRect().getMaxX()-width)
		{
     			obstacle_in_range = true;
			 if(birdx + birdw/2 >= obstace1->getPositionX()-width/2
 			  && birdx - birdw/2 <= obstace1->getPositionX()+width/2
 			  &&( birdy + birdh/2 >= obstace1->getPositionY() + spaceH/2
			  || birdy - birdh/2 <= obstace1->getPositionY() - spaceH/2))
			 {
				  collide = true;
 			 }
 		}
		if(obstace1->getPositionX() + width/2 < birdx - birdw/2 && !collide && obstacle_in_range )
		{
			obstacle_in_range = false;
			++m_score;
			updateScore();
		}

		if(obstace2->getPositionX() + width/2 >= birdx - birdw/2 && obstace2->getPositionX() < VisibleRect::getVisibleRect().getMaxX()-width)
		{
		      obstacle2_in_range = true;
		      if(birdx + birdw/2 >= obstace2->getPositionX()-width/2
			&& birdx - birdw/2 <= obstace2->getPositionX()+width/2
			&&( birdy + birdh/2 >= obstace2->getPositionY() + spaceH/2
			|| birdy - birdh/2 <= obstace2->getPositionY() - spaceH/2))
			{
				 collide = true;
			}
                }
		if(obstace2->getPositionX() + width/2< birdx - birdw/2 && !collide && obstacle2_in_range)
		{
		    obstacle2_in_range = false;
		    ++m_score;
		    updateScore();
		}
		if(collide)
		{
		    bird->getB2Body()->ApplyLinearImpulse(b2Vec2(0, -1000), bird->getB2Body()->GetPosition(), true);
		    if(STATE_GAMEOVER != m_state)
		    {
			gotoGameOver();
		    }
		}
	}
}


Flappybird::~Flappybird()
{
	
}

bool Flappybird::onTouchBegan(Touch* touch, Event* pEvent)
 {
	 B2Sprite* bird = (B2Sprite*) this->getChildByTag(kTagBird);
	 if(bird != NULL)
	 {
	     bird->getB2Body()->ApplyLinearImpulse(b2Vec2(0, CCRANDOM_0_1() * 10 + 15), bird->getB2Body()->GetPosition(), true);
	 }
	 return true;
 }

 void Flappybird::onTouchEnded(Touch* touch, Event* pEvent)
 { 
 }

代码简单分析

void Flappybird::bgChange(Node* node)
{
 	Sprite* bg = (Sprite*) this->getChildByTag(0);
	int height = bg->getContentSize().height;
 	bg->setPosition(ccp(VisibleRect::getVisibleRect().getMaxX()/2, height/2));
	bg->update(1/60);
	bg->runAction(CCSequence::create(CCMoveBy::create(0.5, ccp(-88, 0)), CCCallFuncN::create(this, callfuncN_selector(Flappybird::bgChange)), NULL));
}
循环地图的实现在这里。可以看到,在出屏后,重新设置了位置,然后继续跑一个不断往左运动的动画。

void Flappybird::obstacle1ChangePosition(Node* node)
{
 	Sprite* obstacle1 = (Sprite*) this->getChildByTag(kTagObstacle1);
	int sign = CCRANDOM_0_1() >0.5? 1:-1;
 	obstacle1->setPosition(ccp(VisibleRect::getVisibleRect().getMaxX()+ 20,  VisibleRect::getVisibleRect().getMaxY()/2 + sign * CCRANDOM_0_1() * 120));
	obstacle1->runAction( CCSequence::create(CCMoveTo::create(8, ccp(-50, obstacle1->getPositionY())), CCCallFuncN::create(this, callfuncN_selector(Flappybird::obstacle1ChangePosition)), NULL));
}

void Flappybird::obstacle2ChangePosition(Node* node)
{
	Sprite* obstacle2 = (Sprite*) this->getChildByTag(kTagObstacle2);
  	int sign = CCRANDOM_0_1() >0.5? 1:-1;
	obstacle2->setPosition(ccp(VisibleRect::getVisibleRect().getMaxX()+ 20,  VisibleRect::getVisibleRect().getMaxY()/2 + sign * CCRANDOM_0_1() * 120));
 	obstacle2->runAction( CCSequence::create(CCMoveTo::create(8, ccp(-50, obstacle2->getPositionY())), CCCallFuncN::create(this, callfuncN_selector(Flappybird::obstacle2ChangePosition)), NULL));
}
两个障碍物的回调事件。在出屏后会重新设置障碍物的位置,然后跑一个不断往左运动的动画,然后跟一个Schedule事件,达到不断循环的目的。

bool Flappybird::onTouchBegan(Touch* touch, Event* pEvent)
 {
	 B2Sprite* bird = (B2Sprite*) this->getChildByTag(kTagBird);
	 if(bird != NULL)
	 {
	     bird->getB2Body()->ApplyLinearImpulse(b2Vec2(0, CCRANDOM_0_1() * 10 + 15), bird->getB2Body()->GetPosition(), true);
	 }
	 return true;
 }
在上面的方法中,如果我们触摸屏幕,就会给小鸟一个冲量,让它能够往上运动。

void Flappybird::update(int birdx, int birdy, int birdw, int birdh)

碰撞检测方法。由于障碍物的特性,它不能实现为刚体,所以碰撞检测要自己处理。如果发生碰撞,我会给小鸟一个向下的很大的冲量,把小鸟弹出屏幕。

基本的实现就是这些。传几张游戏截图。


【游戏开始】




【游戏中】



【游戏结束】

感谢阅读。源码已经上传到群216208142空间,需要的同学可以加群来取。



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值