展会信息港展会大全

cocos2dx实例开发之flappybird(入门版)
来源:互联网   发布日期:2015-09-27 15:28:57   浏览:4063次  

导读: cocos2dx社区里有个系列博客完整地复制原版flappybird的所有特性,不过那个代码写得比较复杂,新手学习起来有点捉摸不透,这里我写了个简单的版本。演示如下: vcyotcS/yda0...

cocos2dx社区里有个系列博客完整地复制原版flappybird的所有特性,不过那个代码写得比较复杂,新手学习起来有点捉摸不透,这里我写了个简单的版本。演示如下:

\vcyotcS/yda00NDOxLz+oaMK1+7W1bXEz+7Ev7mks8y94bm5yOfPwqO6CjxpbWcgc3JjPQ=="http://www.2cto.com/uploadfile/Collfiles/20141027/2014102708283222.png" alt="\">

很简单,只有三个类,预加载类,游戏主场景类,应用代理类,新手刚入门喜欢将很多东西都写在尽量少的类里面。

游戏设计

游戏结构如下,游戏包含预加载场景和主场景,主场景中包含背景、小鸟、管道和各种UI界面。

\开发步骤

1,素材收集

从apk文件里提取出来一些图片和音频,并用TexturePatcher拼成大图,导出plist文件。

\2,预加载场景

新建一个LoadingScene,在里面添加一张启动图片,通过异步加载纹理并回调的方式把所有图片素材、小鸟帧动画以及音频文件都加入到缓存,加载完毕后跳转到游戏主场景。

//添加加载回调函数,用异步加载纹理

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

void LoadingScene::loadingCallBack(Texture2D *texture)

{

//预加载帧缓存纹理

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

//预加载帧动画

auto birdAnimation = Animation::create();

birdAnimation->setDelayPerUnit(0.2f);

birdAnimation->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("bird1.png"));

birdAnimation->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("bird2.png"));

birdAnimation->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("bird3.png"));

AnimationCache::getInstance()->addAnimation(birdAnimation, "birdAnimation"); //将小鸟动画添加到动画缓存

//预加载音效

SimpleAudioEngine::getInstance()->preloadEffect("die.mp3");

SimpleAudioEngine::getInstance()->preloadEffect("hit.mp3");

SimpleAudioEngine::getInstance()->preloadEffect("point.mp3");

SimpleAudioEngine::getInstance()->preloadEffect("swooshing.mp3");

SimpleAudioEngine::getInstance()->preloadEffect("wing.mp3");

//加载完毕跳转到游戏场景

auto gameScene = GameScene::createScene();

TransitionScene *transition = TransitionFade::create(0.5f, gameScene);

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

}

3,游戏主场景

3.1,背景和logo

用图片精灵即可

//添加游戏背景

Sprite *backGround = Sprite::createWithSpriteFrameName("bg.png");

backGround->setPosition(visibleOrigin.x + visibleSize.width / 2, visibleOrigin.y + visibleSize.height / 2);

this->addChild(backGround);

//logo

auto gameLogo = Sprite::createWithSpriteFrameName("bird_logo.png");

gameLogo->setPosition(visibleOrigin.x + visibleSize.width / 2, visibleOrigin.y + visibleSize.height / 2+100);

gameLogo->setName("logo");

this->addChild(gameLogo);logo在游戏开始后要隐藏掉。

3.2,小鸟//小鸟

birdSprite = Sprite::create();

birdSprite->setPosition(visibleOrigin.x + visibleSize.width / 3, visibleOrigin.y + visibleSize.height / 2);

this->addChild(birdSprite);

auto birdAnim = Animate::create(AnimationCache::getInstance()->animationByName("birdAnimation"));

birdSprite->runAction(RepeatForever::create(birdAnim)); //挥翅动画

auto up = MoveBy::create(0.4f, Point(0, 8));

auto upBack = up->reverse();

if (gameStatus == GAME_READY)

{

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

birdSprite->runAction(swingAction); //上下晃动动画

}在准备界面下除了有扇翅膀的动作,还有上下浮动的动作。

3.3,地板

地板的左移是用两张错位的地板图片循环左移实现的。需要用到自定义调度器,注意调节移动速度。

//添加两个land

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

land1->setAnchorPoint(Point::ZERO);

land1->setPosition(Point::ZERO);

this->addChild(land1, 10); //置于最顶层

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

land2->setAnchorPoint(Point::ZERO);

land2->setPosition(Point::ZERO);

this->addChild(land2, 10);

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

//两个图片循环移动

land1->setPositionX(land1->getPositionX() - 1.0f);

land2->setPositionX(land1->getPositionX() + land1->getContentSize().width - 2.0f);

if (land2->getPositionX() setPosition(Point::ZERO);3.4,水管

一组水管由上下2半根组成,用Node包起来,弄个vector容器添加两组管道,每次出现在屏幕中的管子只有两组,当一组消失在屏幕范围内则重设置其横坐标,需要提前计算好各种间距或者高度。

//同屏幕出现的只有两根管子,放到容器里面,上下绑定为一根

for (int i = 0; i getVisibleSize();

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

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

Node *singlePipe = Node::create();

//给上管绑定刚体

auto pipeUpBody = PhysicsBody::createBox(pipeUp->getContentSize());

pipeUpBody->setDynamic(false);

pipeUpBody->setContactTestBitmask(1);

pipeUp->setAnchorPoint(Vec2::ANCHOR_MIDDLE);

pipeUp->setPhysicsBody(pipeUpBody);

//给两个管子分开设置刚体,可以留出中间的空隙使得小鸟通过

//给下管绑定刚体

auto pipeDownBody = PhysicsBody::createBox(pipeDown->getContentSize());

pipeDownBody->setDynamic(false);

pipeDownBody->setContactTestBitmask(1);

pipeDown->setAnchorPoint(Vec2::ANCHOR_MIDDLE);

pipeDown->setPhysicsBody(pipeDownBody);

pipeUp->setPosition(0, PIPE_HEIGHT + PIPE_SPACE);

singlePipe->addChild(pipeUp);

singlePipe->addChild(pipeDown); //pipeDown默认加到(0,0),上下合并,此时singlePipe以下面的管子中心为锚点

singlePipe->setPosition(i*PIPE_INTERVAL + WAIT_DISTANCE, getRandomHeight() ); //设置初始高度

singlePipe->setName("newPipe");

this->addChild(singlePipe); //把两个管子都加入到层

pipes.pushBack(singlePipe); //两个管子先后添加到容器

}

//管子滚动

for (auto &singlePipe : pipes)

{

singlePipe->setPositionX(singlePipe->getPositionX() - 1.0f);

if (singlePipe->getPositionX() setPositionX(visibleSize.width+PIPE_WIDTH/2);

singlePipe->setPositionY(getRandomHeight());

singlePipe->setName("newPipe"); //每次重设一根管子,标为new

}

}3.5,加入物理世界

cocos2dx 3.0后引入了自带的物理引擎,用法和box2D等差不多。

物理世界初始化

gameScene->getPhysicsWorld()->setGravity(Vec2(0, -900)); //设置重力场,重力加速度可以根据手感改小点

gameLayer->setPhysicWorld(gameScene->getPhysicsWorld()); //绑定物理世界小鸟绑定刚体

//小鸟绑定刚体

auto birdBody = PhysicsBody::createCircle(BIRD_RADIUS); //将小鸟当成一个圆,懒得弄精确的轮廓线了

birdBody->setDynamic(true); //设置为可以被物理场所作用而动作

birdBody->setContactTestBitmask(1); //必须设置这项为1才能检测到不同的物体碰撞

birdBody->setGravityEnable(false); //设置是否被重力影响,准备画面中不受重力影响

birdSprite->setPhysicsBody(birdBody); //为小鸟设置刚体地板绑定刚体

//设置地板刚体

Node *groundNode = Node::create();

auto groundBody = PhysicsBody::createBox(Size(visibleSize.width, land1->getContentSize().height));

groundBody->setDynamic(false);

groundBody->setContactTestBitmask(1);

groundNode->setAnchorPoint(Vec2::ANCHOR_MIDDLE); //物理引擎中的刚体只允许结点锚点设置为中心

groundNode->setPhysicsBody(groundBody);

groundNode->setPosition(visibleOrigin.x+visibleSize.width/2,land1->getContentSize().height/2);

this->addChild(groundNode);管道设置刚体,上下半根分别设置,留出中间的缝隙

//给上管绑定刚体

auto pipeUpBody = PhysicsBody::createBox(pipeUp->getContentSize());

pipeUpBody->setDynamic(false);

pipeUpBody->setContactTestBitmask(1);

pipeUp->setAnchorPoint(Vec2::ANCHOR_MIDDLE);

pipeUp->setPhysicsBody(pipeUpBody);

//给两个管子分开设置刚体,可以留出中间的空隙使得小鸟通过

//给下管绑定刚体

auto pipeDownBody = PhysicsBody::createBox(pipeDown->getContentSize());

pipeDownBody->setDynamic(false);

pipeDownBody->setContactTestBitmask(1);

pipeDown->setAnchorPoint(Vec2::ANCHOR_MIDDLE);

pipeDown->setPhysicsBody(pipeDownBody);碰撞检测

现在层的init里面的事件分发器中加入碰撞侦听

//添加碰撞监测

auto contactListener = EventListenerPhysicsContact::create();

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

_eventDispatcher->addEventListenerWithSceneGraphPriority(contactListener, this);

//碰撞监测

bool GameScene::onContactBegin(const PhysicsContact& contact)

{

if (gameStatus == GAME_OVER) //当游戏结束后不再监控碰撞

return false;

gameOver();

return true;

}3.6,触摸检测

//触摸监听

bool GameScene::onTouchBegan(Touch *touch, Event *event)3.7,控制小鸟

由准备模式变到游戏开始模式后,触摸屏幕会给小鸟一个向上的速度,写在触摸检测里面

birdSprite->getPhysicsBody()->setVelocity(Vec2(0, 250)); //给一个向上的初速度

小鸟的旋转角度与纵向速度有关,写在update()里

//小鸟的旋转

auto curVelocity = birdSprite->getPhysicsBody()->getVelocity();

birdSprite->setRotation(-curVelocity.y*0.1 - 20); //根据竖直方向的速度算出旋转角度,逆时针为负

3.8,游戏开始

开始后启动各种定时器

//游戏开始

void GameScene::gameStart()

{

gameStatus = GAME_START;

score = 0;//重置分数

scoreLabel->setString(String::createWithFormat("%d", score)->getCString());

this->getChildByName("logo")->setVisible(false); //logo消失

scoreLabel->setVisible(true); //计分开始

this->scheduleUpdate();//启动默认更新

this->schedule(schedule_selector(GameScene::scrollLand), 0.01f); //启动管子和地板滚动

birdSprite->stopAction(swingAction); //游戏开始后停止上下浮动

birdSprite->getPhysicsBody()->setGravityEnable(true); //开始受重力作用

}3.9,计分和数据存储

在默认的update()函数里对得分进行判断和更新,通过默认xml存储历史分数

//当游戏开始时,判断得分,这个其实也可以写在其他地方,比如管子滚动的更新函数里面或者触摸监测里面

if (gameStatus == GAME_START)

{

for (auto &pipe : pipes)

{

if (pipe->getName() == "newPipe") //新来一根管子就判断

{

if (pipe->getPositionX() getPositionX())

{

score++;

scoreLabel->setString(String::createWithFormat("%d", score)->getCString());

SimpleAudioEngine::getInstance()->playEffect("point.mp3");

pipe->setName("passed"); //标记已经过掉的管子

}

}

}

}4.0,游戏结束//游戏结束

void GameScene::gameOver()

{

gameStatus = GAME_OVER;

//获取历史数据

bestScore = UserDefault::getInstance()->getIntegerForKey("BEST");

if (score > bestScore)

{

bestScore = score; //更新最好分数

UserDefault::getInstance()->setIntegerForKey("BEST", bestScore);

}

SimpleAudioEngine::getInstance()->playEffect("hit.mp3");

//游戏结束后停止地板和管道的滚动

this->unschedule(schedule_selector(GameScene::scrollLand));

}结束后比较当前分数和历史分数,以便更新。

4.1,音频

音效文件已经加入到缓存,在适当的地方加上全局音频控制器播放音效即可

SimpleAudioEngine::getInstance()->playEffect("hit.mp3");4.2,记分板

游戏结束后滑入记分板,并显示重玩按钮。

//加入记分板和重玩菜单

void GameScene::gamePanelAppear()

{

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

Vec2 origin = Director::getInstance()->getVisibleOrigin();

//用node将gameoverlogo和记分板绑在一起

Node *gameOverPanelNode = Node::create();

auto gameOverLabel = Sprite::createWithSpriteFrameName("gameover.png");

gameOverPanelNode->addChild(gameOverLabel);

auto panel = Sprite::createWithSpriteFrameName("board.PNG");//注意这里是大写PNG,原图片用什么后缀这里就用什么,区分大小写

gameOverLabel->setPositionY(panel->getContentSize().height); //设置一下坐标

gameOverPanelNode->addChild(panel);

//记分板上添加两个分数

auto curScoreTTF = LabelTTF::create(String::createWithFormat("%d", score)->getCString(), "Arial", 20);

curScoreTTF->setPosition(panel->getContentSize().width-40, panel->getContentSize().height-45);

curScoreTTF->setColor(Color3B(255, 0, 0));

panel->addChild(curScoreTTF);

auto bestScoreTTF = LabelTTF::create(String::createWithFormat("%d", bestScore)->getCString(), "Arial", 20);

bestScoreTTF->setPosition(panel->getContentSize().width - 40, panel->getContentSize().height - 90);

bestScoreTTF->setColor(Color3B(0, 255, 0));

panel->addChild(bestScoreTTF);

this->addChild(gameOverPanelNode);

gameOverPanelNode->setPosition(origin.x + size.width / 2, origin.y + size.height );

//滑入动画

gameOverPanelNode->runAction(MoveTo::create(0.5f, Vec2(origin.x + size.width / 2, origin.y + size.height / 2)));

SimpleAudioEngine::getInstance()->playEffect("swooshing.mp3");

//添加菜单

MenuItemImage *restartItem = MenuItemImage::create("start_btn.png", "start_btn_pressed.png", this,menu_selector(GameScene::gameRetart));

auto menu = CCMenu::createWithItem(restartItem);

menu->setPosition(origin.x + size.width / 2, 150);

this->addChild(menu);

}

//游戏重新开始

void GameScene::gameRetart(Ref *sender)

{

//重新回到初始画面

auto gameScene = GameScene::createScene();

Director::getInstance()->replaceScene(gameScene); //这里懒得加特效了,直接转场

}

效果图:

\\\

源代码:MyFlappyBird

还有很多要完善的地方,比如没有加入图片数字以及社交分享等等。

赞助本站

人工智能实验室
相关内容
AiLab云推荐
推荐内容
展开

热门栏目HotCates

Copyright © 2010-2024 AiLab Team. 人工智能实验室 版权所有    关于我们 | 联系我们 | 广告服务 | 公司动态 | 免责声明 | 隐私条款 | 工作机会 | 展会港