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空间,需要的同学可以加群来取。