cocos2d-x-3.16 实例开发之FlappyBird

开发环境

开发环境:(1)windows 7 64位(2)vs2015(3)python2.7.10 

开发语言:c++ 

游戏引擎:cocos2d-x-3.16下载地址:http://download.cocos.com/Cocos2D-X/cocos2d-x-3.16.zip 

项目源码托管在Github:https://github.com/smiger/FlappyBird

 

一、项目创建

使用cocos new创建新项目,命令行模式下进入cocos2d-x-3.16\tools\cocos2d-console\bin目录下,使用cocos new FlappyBird -p com.game.flappybird -l cpp -d E:\Projects命令创建Flappybird项目

-p:包名

-l :编程语言

-d:项目存储路径

 

二、用vs2015打开FlappyBird项目

进入E:\Projects\FlappyBird\proj.win32目录,打开FlappyBird.sln

先不急着码代码,生成该解决方案,这个过程会编译cocos引擎的代码,时间会久一点

生成解决方案成功后,运行项目

 

加载资源

一、用TexturePacker做成flappy_packer.png合图

TexturePacker工具:链接:https://pan.baidu.com/s/1htqSysG 密码:xvsi

flappy图片资源:链接:https://pan.baidu.com/s/1jM2_yMjkBBcNSl43i0f4Ig 密码:4h0v

flappy_packer.png

二、cocos2dx提供加载纹理的接口

1

Director::getInstance()->getTextureCache()->addImageAsync("flappy_packer.png", CC_CALLBACK_1(LoadingScene::loadingCallBack, this));

TextureCache这个类里面的addImageAsync的方法便是一个异步加载图片纹理的方法,第一个参数是图片的名字,第二个参数便是图片异步加载完成之后需要调用的回调函数

flappy_packer.png是放在image文件夹下的,在AppDelegate.cpp下有将image添加到搜索路径,所以直接写文件名就可以了

1

2

3

FileUtils::getInstance()->addSearchPath("fonts");

FileUtils::getInstance()->addSearchPath("image");

FileUtils::getInstance()->addSearchPath("sounds");

loadingCallback回调函数加载帧动画和声音文件后跳转到欢迎界面

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

void LoadingScene::loadingCallBack(Texture2D *texture){

    //预加载帧缓存纹理

    SpriteFrameCache::getInstance()->addSpriteFramesWithFile("flappy_packer.plist", texture);

 

    // After loading the texture , preload all the sound

    SimpleAudioEngine::getInstance()->preloadEffect("sfx_die.ogg");

    SimpleAudioEngine::getInstance()->preloadEffect("sfx_hit.ogg");

    SimpleAudioEngine::getInstance()->preloadEffect("sfx_point.ogg");

    SimpleAudioEngine::getInstance()->preloadEffect("sfx_swooshing.ogg");

    SimpleAudioEngine::getInstance()->preloadEffect("sfx_wing.ogg");

 

    // After load all the things, change the scene to new one

    //auto scene = HelloWorld::createScene();

    auto scene = WelcomeScene::create();

    TransitionScene *transition = TransitionFade::create(1, scene);

    Director::getInstance()->replaceScene(transition);

}

 

欢迎界面

一、欢迎界面比较简单,由背景、标题、鸟、开始按钮、地面构成

1、背景

1

2

3

4

5

6

7

8

9

10

11

12

13

14

    //通过时间判断是白天还是晚上加载不同的图片资源

    time_t t = time(NULL);

    tm* lt = localtime(&t);

    int hour = lt->tm_hour;

    //create the background image according to the current time;

    Sprite *background;

    if(hour >= 6 && hour <= 17){

         background = Sprite::createWithSpriteFrameName("bg_day.png");

    }else{

        background = Sprite::createWithSpriteFrameName("bg_night.png");

    }

    background->setAnchorPoint(Point::ZERO);

    background->setPosition(Point::ZERO);

    this->addChild(background);

2、开始按钮

1

2

3

4

5

6

7

8

9

10

    //add the start-menu to the current scene

    Sprite *startButton = Sprite::createWithSpriteFrameName("button_play.png");

    Sprite *activeStartButton = Sprite::createWithSpriteFrameName("button_play.png");

    activeStartButton->setPositionY(5);

    auto menuItem  = MenuItemSprite::create(startButton,activeStartButton,NULL,CC_CALLBACK_1(WelcomeLayer::menuStartCallback, this));

    menuItem->setPosition(Point(origin.x + visiableSize.width/2 ,origin.y + visiableSize.height*2/5));

    //将menuItem菜单项加入到Menu菜单中

    auto menu = Menu::create(menuItem,NULL);

    menu->setPosition(Point(origin.x ,origin.y));

    this->addChild(menu,1);

3、地面

地面的移动是通过两张图片拼接循环移动的效果

1

2

3

4

5

6

7

8

9

10

11

12

    // Add the land

    this->land1 = Sprite::createWithSpriteFrameName("land.png");

    this->land1->setAnchorPoint(Point::ZERO);

    this->land1->setPosition(Point::ZERO);

    this->addChild(this->land1);

 

    this->land2 = Sprite::createWithSpriteFrameName("land.png");

    this->land2->setAnchorPoint(Point::ZERO);

    this->land2->setPosition(this->land1->getContentSize().width - 2.0f, 0);

    this->addChild(this->land2);

 

    this->schedule(schedule_selector(WelcomeLayer::scrollLand), 0.01f);

4、挥动翅膀的小鸟

1

2

3

4

5

6

7

    //create a bird and set the position in the center of the screen

    this->bird = BirdSprite::getInstance();

    this->bird->createBird();

    this->bird->setTag(BIRD_SPRITE_TAG);

    this->bird->setPosition(Point(origin.x + visiableSize.width / 2,origin.y + visiableSize.height*3/5 - 10));

    this->bird->idle();

    this->addChild(this->bird);

小鸟作为单独的类会在下一节讲解

 

小鸟类的实现

一、小鸟的状态

1

2

3

4

5

typedef enum{

    ACTION_STATE_IDLE, //准备状态

    ACTION_STATE_FLY, //飞行状态

    ACTION_STATE_DIE //死亡状态

} ActionState;

 

二、小鸟相关操作

1、初始化随机创建一种颜色的鸟createBirdByRandom

2、小鸟状态的切换changeState

3、创建小鸟的动画createAnimation

 

三、3种不同状态的分析

初始化时创建了小鸟的2个动作

    翅膀的挥动idleAction

    小鸟的上下移动swingAction

 

1

2

3

4

5

6

7

8

9

// init idle status

//create the bird animation

Animation* animation = this->createAnimation(this->birdNameFormat.c_str(), 3, 10);

Animate* animate = Animate::create(animation);

this->idleAction = RepeatForever::create(animate);

// create the swing action

ActionInterval *up = CCMoveBy::create(0.4f,Point(0, 8));

ActionInterval *upBack= up->reverse();

this->swingAction = RepeatForever::create(Sequence::create(up, upBack, NULL));

1、idle空闲状态

1

2

3

4

5

6

void BirdSprite::idle() {

    if (changeState(ACTION_STATE_IDLE)) {

        this->runAction(idleAction);

        this->runAction(swingAction);

    }

}

2、fly飞行状态

点击开始按钮,游戏开始后,小鸟由idle状态切换到fly状态

由于游戏用到了box2d物理引擎,所以fly状态除了挥动翅膀还要受到重力的作用

1

2

3

4

5

6

void BirdSprite::fly() {

    if(changeState(ACTION_STATE_FLY)) {

        this->stopAction(swingAction);

        this->getPhysicsBody()->setGravityEnable(true);

    }

}

3、die死亡状态

当小鸟碰到水管后,进入死亡状态

1

2

3

4

5

void BirdSprite::die() {

    if(changeState(ACTION_STATE_DIE)) {

        this->stopAllActions();

    }

}

 

控制层与游戏层

一、控制层

在OptionLayer层中,我们实现了触摸事件的监听

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

bool OptionLayer::init()

{

    if(Layer::init()){

        auto dispatcher = Director::getInstance()->getEventDispatcher();

        auto listener = EventListenerTouchAllAtOnce::create();

        listener->onTouchesBegan = CC_CALLBACK_2(OptionLayer::onTouchesBegan, this);

        dispatcher->addEventListenerWithSceneGraphPriority(listener, this);

 

        return true;

    }else {

        return false;

    }

}

 

void OptionLayer::onTouchesBegan(const std::vector<Touch*>& touches, Event *event)

{

    this->delegator->onTouch();

}

在头文件OptionLayer.h中定义了delegator

1

CC_SYNTHESIZE(OptionDelegate*, delegator, Delegator);

还有OptionDelegate类

1

2

3

4

5

class OptionDelegate

{

public:

    virtual void onTouch() = 0;

};

二、控制层与游戏层的关系

游戏层GameLayer和控制层OptionLayer通过代理OptionDelegate来进行通讯

游戏层继承OptionDelegate实现onTouch()方法

1

2

3

class GameLayer : public Layer , public OptionDelegate{

    ...

};

在游戏主场景GameScene中将两者联系起来

1

2

3

4

5

6

// Add operation layer to control the game

auto optionLayer = OptionLayer::create();

if(optionLayer) {

    optionLayer->setDelegator(gameLayer);

    this->addChild(optionLayer);

}

三、游戏层

1、游戏状态

游戏有三种状态:准备、开始、结束

1

2

3

4

5

typedef enum _game_status {

GAME_STATUS_READY = 1,

GAME_STATUS_START,

GAME_STATUS_OVER

} GameStatus;

2、添加小鸟

在上一节中我们已经完成了小鸟类的封装,我们先在GameLayer.h中声明这只小鸟

1

BirdSprite *bird;

设置物理引擎的相关属性并添加到游戏层中

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

// Add the bird

this->bird = BirdSprite::getInstance();

this->bird->createBird();

PhysicsBody *body = PhysicsBody::create();

        body->addShape(PhysicsShapeCircle::create(BIRD_RADIUS));

        body->setCategoryBitmask(ColliderTypeBird);

        body->setCollisionBitmask(ColliderTypeLand & ColliderTypePip);

        body->setContactTestBitmask(ColliderTypeLand | ColliderTypePip);

        body->setDynamic(true);

body->setLinearDamping(0.0f);

body->setGravityEnable(false);

this->bird->setPhysicsBody(body);

this->bird->setPosition(origin.x + visiableSize.width*1/3 - 5,origin.y + visiableSize.height/2 + 5);

this->bird->idle();

this->addChild(this->bird);

游戏进行过程当中,每次点击屏莫的时候,给小鸟设置一个向上的初速度:

1

this->bird->getPhysicsBody()->setVelocity(Vect(0, 260)

 

3、水管的添加

水管的添加,主要是通过createPips()函数来生成水管

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

void GameLayer::createPips() {

    // Create the pips

    for (int i = 0; i < PIP_COUNT; i++) {

        Size visibleSize = Director::getInstance()->getVisibleSize();

        Sprite *pipUp = Sprite::createWithSpriteFrameName("pipe_up.png");

        Sprite *pipDown = Sprite::createWithSpriteFrameName("pipe_down.png");

        Node *singlePip = Node::create();

         

        // bind to pair

        pipDown->setPosition(0, PIP_HEIGHT + PIP_DISTANCE);

        singlePip->addChild(pipDown, 0, DOWN_PIP);

        singlePip->addChild(pipUp, 0, UP_PIP);

        singlePip->setPosition(visibleSize.width + i*PIP_INTERVAL + WAIT_DISTANCE, this->getRandomHeight());

        auto body = PhysicsBody::create();

        auto shapeBoxDown = PhysicsShapeBox::create(pipDown->getContentSize(),PHYSICSSHAPE_MATERIAL_DEFAULT, Point(0, PIP_HEIGHT + PIP_DISTANCE));

        body->addShape(shapeBoxDown);

        body->addShape(PhysicsShapeBox::create(pipUp->getContentSize()));

        body->setDynamic(false);

        body->setContactTestBitmask(true);

        singlePip->setPhysicsBody(body);

        singlePip->setTag(PIP_NEW);

 

        this->addChild(singlePip);

        this->pips.push_back(singlePip);

    }

}

一组水管由上下2半根组成,用vector容器添加两组管道,每次出现在屏幕中的管子只有两组,当一组消失在屏幕范围内则重设置其横坐标

 

1

2

3

4

5

6

7

8

9

10

    // move the pips

    for (auto singlePip : this->pips) {

        singlePip->setPositionX(singlePip->getPositionX() - 2);

        if(singlePip->getPositionX() < -PIP_WIDTH) {

            singlePip->setTag(PIP_NEW);

            Size visibleSize = Director::getInstance()->getVisibleSize();

            singlePip->setPositionX(visibleSize.width);

            singlePip->setPositionY(this->getRandomHeight());

        }

    }

4、碰撞检查

在GameLayer初始化的时候,我们创建了一个碰撞监听:

 

1

2

3

auto contactListener = EventListenerPhysicsContact::create();

contactListener->onContactBegin = CC_CALLBACK_1(GameLayer::onContactBegin, this);

this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(contactListener, this);


一旦出现碰撞,则进入游戏结束状态

 

1

2

3

4

bool GameLayer::onContactBegin(PhysicsContact& contact) {

this->gameOver();

return true;

}

 

游戏状态层

游戏状态层StatusLayer也是分为3种状态,在不同的阶段显示相应的画面

一、状态层的三种状态

1、游戏准备

image.png

2、游戏进行

image.png

3、游戏结束

image.png

二、不同状态的处理函数

1、OnGameStart

当用户在游戏准备状态点击屏幕后,则执行OnGameStart函数

1

2

3

void StatusLayer::onGameStart(){

this->showStartStatus();

}

2、onGamePlaying

游戏进行过程中,状态层主要负责显示实时得分

 

1

2

3

4

void StatusLayer::onGamePlaying(int score){

    auto scorestr = __String::createWithFormat("%d", score)->getCString();

    scoreSprite->setString(scorestr);

}

其中游戏过程中展示的分数采用TextAtlas控件,先将数字做成一张合图

num.png

然后使用ui::TextAtlas::create创建,参数1:展示字符,参数2:做成的合图,参数3:每个字符所占宽度,参数4:高度,参数5:开始字符

1

2

3

4

5

TextAtlas* TextAtlas::create(const std::string &stringValue,

                             const std::string &charMapFile,

                             int itemWidth,

                             int itemHeight,

                             const std::string &startCharMap)

1

scoreSprite = ui::TextAtlas::create("0""num.png",30, 44,"0");

3、onGameEnd
在游戏结束的时候,需要显示游戏结束画面,在这个过程当中,包括一些动画的显示,菜单的显示

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

void StatusLayer::onGameEnd(int curScore, int bestScore){

    this->showOverStatus(curScore,bestScore);

}

void StatusLayer::showOverStatus(int curScore, int bestScore) {

    this->currentScore = curScore;

    this->bestScore = bestScore;

    if(curScore > bestScore){

        this->bestScore = curScore;

        this->isNewRecord = true;

    }else{

        this->isNewRecord = false;

    }

    this->removeChild(scoreSprite);

    this->blinkFullScreen();

}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值