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

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMjIzNDExNQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

创建项目

VS2013+cocos2dx 3.2创建win32项目。因为仅仅是学习,所以没有编译为安卓、ios或者WP平台的可运行文件。
终于的项目project结构例如以下:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMjIzNDExNQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

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

游戏设计

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

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMjIzNDExNQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">


开发步骤

1,素材收集

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



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

  1. //加入载入回调函数,用异步载入纹理
  2. Director::getInstance()->getTextureCache()->addImageAsync("game.png", CC_CALLBACK_1(LoadingScene::loadingCallBack, this));
  1. void LoadingScene::loadingCallBack(Texture2D *texture)
  2. {
  3. //预载入帧缓存纹理
  4. SpriteFrameCache::getInstance()->addSpriteFramesWithFile("game.plist", texture);
  5. //预载入帧动画
  6. auto birdAnimation = Animation::create();
  7. birdAnimation->setDelayPerUnit(0.2f);
  8. birdAnimation->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("bird1.png"));
  9. birdAnimation->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("bird2.png"));
  10. birdAnimation->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("bird3.png"));
  11. AnimationCache::getInstance()->addAnimation(birdAnimation, "birdAnimation"); //将小鸟动画加入到动画缓存
  12. //预载入音效
  13. SimpleAudioEngine::getInstance()->preloadEffect("die.mp3");
  14. SimpleAudioEngine::getInstance()->preloadEffect("hit.mp3");
  15. SimpleAudioEngine::getInstance()->preloadEffect("point.mp3");
  16. SimpleAudioEngine::getInstance()->preloadEffect("swooshing.mp3");
  17. SimpleAudioEngine::getInstance()->preloadEffect("wing.mp3");
  18.  
  19. //载入完成跳转到游戏场景
  20. auto gameScene = GameScene::createScene();
  21. TransitionScene *transition = TransitionFade::create(0.5f, gameScene);
  22. Director::getInstance()->replaceScene(transition);
  23. }

3,游戏主场景

3.1。背景和logo
用图片精灵就可以
  1. //加入游戏背景
  2. Sprite *backGround = Sprite::createWithSpriteFrameName("bg.png");
  3. backGround->setPosition(visibleOrigin.x + visibleSize.width / 2, visibleOrigin.y + visibleSize.height / 2);
  4. this->addChild(backGround);
  5. //logo
  6. auto gameLogo = Sprite::createWithSpriteFrameName("bird_logo.png");
  7. gameLogo->setPosition(visibleOrigin.x + visibleSize.width / 2, visibleOrigin.y + visibleSize.height / 2+100);
  8. gameLogo->setName("logo");
  9. this->addChild(gameLogo);

logo在游戏開始后要隐藏掉。

3.2。小鸟

  1. //小鸟
  2. birdSprite = Sprite::create();
  3. birdSprite->setPosition(visibleOrigin.x + visibleSize.width / 3, visibleOrigin.y + visibleSize.height / 2);
  4. this->addChild(birdSprite);
  5. auto birdAnim = Animate::create(AnimationCache::getInstance()->animationByName("birdAnimation"));
  6. birdSprite->runAction(RepeatForever::create(birdAnim)); //挥翅动画
  7. auto up = MoveBy::create(0.4f, Point(0, 8));
  8. auto upBack = up->reverse();
  9. if (gameStatus == GAME_READY)
  10. {
  11. swingAction = RepeatForever::create(Sequence::create(up, upBack, NULL));
  12. birdSprite->runAction(swingAction); //上下晃动动画
  13. }

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

3.3,地板

地板的左移是用两张错位的地板图片循环左移实现的。

须要用到自己定义调度器,注意调节移动速度。

  1. //加入两个land
  2. land1 = Sprite::createWithSpriteFrameName("land.png");
  3. land1->setAnchorPoint(Point::ZERO);
  4. land1->setPosition(Point::ZERO);
  5. this->addChild(land1, 10); //置于最顶层
  6. land2 = Sprite::createWithSpriteFrameName("land.png");
  7. land2->setAnchorPoint(Point::ZERO);
  8. land2->setPosition(Point::ZERO);
  9. this->addChild(land2, 10);
  1. Size visibleSize = Director::getInstance()->getVisibleSize();
  2. //两个图片循环移动
  3. land1->setPositionX(land1->getPositionX() - 1.0f);
  4. land2->setPositionX(land1->getPositionX() + land1->getContentSize().width - 2.0f);
  5. if (land2->getPositionX() <= 0)
  6. land1->setPosition(Point::ZERO);

3.4,水管

一组水管由上下2半根组成,用Node包起来,弄个vector容器加入两组管道。每次出如今屏幕中的管子仅仅有两组,当一组消失在屏幕范围内则重设置其横坐标,须要提前计算好各种间距或者高度。
  1. //同屏幕出现的仅仅有两根管子。放到容器里面,上下绑定为一根
  2. for (int i = 0; i < 2; i++)
  3. {
  4. auto visibleSize = Director::getInstance()->getVisibleSize();
  5. Sprite *pipeUp = Sprite::createWithSpriteFrameName("pipe_up.png");
  6. Sprite *pipeDown = Sprite::createWithSpriteFrameName("pipe_down.png");
  7. Node *singlePipe = Node::create();
  8. //给上管绑定刚体
  9. auto pipeUpBody = PhysicsBody::createBox(pipeUp->getContentSize());
  10. pipeUpBody->setDynamic(false);
  11. pipeUpBody->setContactTestBitmask(1);
  12. pipeUp->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
  13. pipeUp->setPhysicsBody(pipeUpBody);
  14. //给两个管子分开设置刚体。能够留出中间的空隙使得小鸟通过
  15. //给下管绑定刚体
  16. auto pipeDownBody = PhysicsBody::createBox(pipeDown->getContentSize());
  17. pipeDownBody->setDynamic(false);
  18. pipeDownBody->setContactTestBitmask(1);
  19. pipeDown->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
  20. pipeDown->setPhysicsBody(pipeDownBody);
  21.  
  22. pipeUp->setPosition(0, PIPE_HEIGHT + PIPE_SPACE);
  23. singlePipe->addChild(pipeUp);
  24. singlePipe->addChild(pipeDown); //pipeDown默认加到(0,0),上下合并,此时singlePipe以以下的管子中心为锚点
  25. singlePipe->setPosition(i*PIPE_INTERVAL + WAIT_DISTANCE, getRandomHeight() ); //设置初始高度
  26. singlePipe->setName("newPipe");
  27. this->addChild(singlePipe); //把两个管子都增加到层
  28. pipes.pushBack(singlePipe); //两个管子先后增加到容器
  29. }
  1. //管子滚动
  2. for (auto &singlePipe : pipes)
  3. {
  4. singlePipe->setPositionX(singlePipe->getPositionX() - 1.0f);
  5. if (singlePipe->getPositionX() < -PIPE_WIDTH/2)
  6. {
  7. singlePipe->setPositionX(visibleSize.width+PIPE_WIDTH/2);
  8. singlePipe->setPositionY(getRandomHeight());
  9. singlePipe->setName("newPipe"); //每次重设一根管子,标为new
  10. }
  11. }

3.5。增加物理世界

cocos2dx 3.0后引入了自带的物理引擎,使用方法和box2D等差点儿相同。
物理世界初始化
  1. gameScene->getPhysicsWorld()->setGravity(Vec2(0, -900)); //设置重力场,重力加速度能够依据手感改小点
  1. gameLayer->setPhysicWorld(gameScene->getPhysicsWorld()); //绑定物理世界

小鸟绑定刚体

  1. //小鸟绑定刚体
  2. auto birdBody = PhysicsBody::createCircle(BIRD_RADIUS); //将小鸟当成一个圆,懒得弄精确的轮廓线了
  3. birdBody->setDynamic(true); //设置为能够被物理场所作用而动作
  4. birdBody->setContactTestBitmask(1); //必须设置这项为1才干检測到不同的物体碰撞
  5. birdBody->setGravityEnable(false); //设置是否被重力影响,准备画面中不受重力影响
  6. birdSprite->setPhysicsBody(birdBody); //为小鸟设置刚体

地板绑定刚体

  1. //设置地板刚体
  2. Node *groundNode = Node::create();
  3. auto groundBody = PhysicsBody::createBox(Size(visibleSize.width, land1->getContentSize().height));
  4. groundBody->setDynamic(false);
  5. groundBody->setContactTestBitmask(1);
  6. groundNode->setAnchorPoint(Vec2::ANCHOR_MIDDLE); //物理引擎中的刚体仅仅同意结点锚点设置为中心
  7. groundNode->setPhysicsBody(groundBody);
  8. groundNode->setPosition(visibleOrigin.x+visibleSize.width/2,land1->getContentSize().height/2);
  9. this->addChild(groundNode);

管道设置刚体,上下半根分别设置,留出中间的缝隙

  1. //给上管绑定刚体
  2. auto pipeUpBody = PhysicsBody::createBox(pipeUp->getContentSize());
  3. pipeUpBody->setDynamic(false);
  4. pipeUpBody->setContactTestBitmask(1);
  5. pipeUp->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
  6. pipeUp->setPhysicsBody(pipeUpBody);
  7. //给两个管子分开设置刚体,能够留出中间的空隙使得小鸟通过
  8. //给下管绑定刚体
  9. auto pipeDownBody = PhysicsBody::createBox(pipeDown->getContentSize());
  10. pipeDownBody->setDynamic(false);
  11. pipeDownBody->setContactTestBitmask(1);
  12. pipeDown->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
  13. pipeDown->setPhysicsBody(pipeDownBody);

碰撞检測

如今层的init里面的事件分发器中增加碰撞侦听
  1. //加入碰撞监測
  2. auto contactListener = EventListenerPhysicsContact::create();
  3. contactListener->onContactBegin = CC_CALLBACK_1(GameScene::onContactBegin, this);
  4. _eventDispatcher->addEventListenerWithSceneGraphPriority(contactListener, this);
  1. //碰撞监測
  2. bool GameScene::onContactBegin(const PhysicsContact& contact)
  3. {
  4. if (gameStatus == GAME_OVER) //当游戏结束后不再监控碰撞
  5. return false;
  6.  
  7. gameOver();
  8. return true;
  9. }

3.6,触摸检測

  1. //触摸监听
  2. bool GameScene::onTouchBegan(Touch *touch, Event *event)

3.7。控制小鸟

由准备模式变到游戏開始模式后,触摸屏幕会给小鸟一个向上的速度。写在触摸检測里面
  1. birdSprite->getPhysicsBody()->setVelocity(Vec2(0, 250)); //给一个向上的初速度
小鸟的旋转角度与纵向速度有关,写在update()里
  1. //小鸟的旋转
  2. auto curVelocity = birdSprite->getPhysicsBody()->getVelocity();
  3. birdSprite->setRotation(-curVelocity.y*0.1 - 20); //依据竖直方向的速度算出旋转角度。逆时针为负

3.8,游戏開始

開始后启动各种定时器
  1. //游戏開始
  2. void GameScene::gameStart()
  3. {
  4. gameStatus = GAME_START;
  5. score = 0;//重置分数
  6. scoreLabel->setString(String::createWithFormat("%d", score)->getCString());
  7. this->getChildByName("logo")->setVisible(false); //logo消失
  8. scoreLabel->setVisible(true); //计分開始
  9. this->scheduleUpdate();//启动默认更新
  10. this->schedule(schedule_selector(GameScene::scrollLand), 0.01f); //启动管子和地板滚动
  11. birdSprite->stopAction(swingAction); //游戏開始后停止上下浮动
  12. birdSprite->getPhysicsBody()->setGravityEnable(true); //開始受重力作用
  13. }

3.9,计分和数据存储

在默认的update()函数里对得分进行推断和更新,通过默认xml存储历史分数
  1. //当游戏開始时,推断得分,这个事实上也能够写在其它地方。比方管子滚动的更新函数里面或者触摸监測里面
  2. if (gameStatus == GAME_START)
  3. {
  4. for (auto &pipe : pipes)
  5. {
  6. if (pipe->getName() == "newPipe") //新来一根管子就推断
  7. {
  8. if (pipe->getPositionX() < birdSprite->getPositionX())
  9. {
  10. score++;
  11. scoreLabel->setString(String::createWithFormat("%d", score)->getCString());
  12. SimpleAudioEngine::getInstance()->playEffect("point.mp3");
  13. pipe->setName("passed"); //标记已经过掉的管子
  14. }
  15. }
  16. }
  17. }

4.0。游戏结束

  1. //游戏结束
  2. void GameScene::gameOver()
  3. {
  4. gameStatus = GAME_OVER;
  5. //获取历史数据
  6. bestScore = UserDefault::getInstance()->getIntegerForKey("BEST");
  7. if (score > bestScore)
  8. {
  9. bestScore = score; //更新最好分数
  10. UserDefault::getInstance()->setIntegerForKey("BEST", bestScore);
  11. }
  12.  
  13. SimpleAudioEngine::getInstance()->playEffect("hit.mp3");
  14. //游戏结束后停止地板和管道的滚动
  15. this->unschedule(schedule_selector(GameScene::scrollLand));
  16. }

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

4.1,音频
音效文件已经增加到缓存,在适当的地方加上全局音频控制器播放音效就可以
  1. SimpleAudioEngine::getInstance()->playEffect("hit.mp3");

4.2,记分板

游戏结束后滑入记分板,并显示重玩button。
  1. //加入记分板和重玩菜单
  2. void GameScene::gamePanelAppear()
  3. {
  4. Size size = Director::getInstance()->getVisibleSize();
  5. Vec2 origin = Director::getInstance()->getVisibleOrigin();
  6. //用node将gameoverlogo和记分板绑在一起
  7. Node *gameOverPanelNode = Node::create();
  8. auto gameOverLabel = Sprite::createWithSpriteFrameName("gameover.png");
  9. gameOverPanelNode->addChild(gameOverLabel);
  10. auto panel = Sprite::createWithSpriteFrameName("board.PNG");//注意这里是大写PNG。原图片用什么后缀这里就用什么,区分大写和小写
  11. gameOverLabel->setPositionY(panel->getContentSize().height); //设置一下坐标
  12. gameOverPanelNode->addChild(panel);
  13. //记分板上加入两个分数
  14. auto curScoreTTF = LabelTTF::create(String::createWithFormat("%d", score)->getCString(), "Arial", 20);
  15. curScoreTTF->setPosition(panel->getContentSize().width-40, panel->getContentSize().height-45);
  16. curScoreTTF->setColor(Color3B(255, 0, 0));
  17. panel->addChild(curScoreTTF);
  18. auto bestScoreTTF = LabelTTF::create(String::createWithFormat("%d", bestScore)->getCString(), "Arial", 20);
  19. bestScoreTTF->setPosition(panel->getContentSize().width - 40, panel->getContentSize().height - 90);
  20. bestScoreTTF->setColor(Color3B(0, 255, 0));
  21. panel->addChild(bestScoreTTF);
  22. this->addChild(gameOverPanelNode);
  23. gameOverPanelNode->setPosition(origin.x + size.width / 2, origin.y + size.height );
  24. //滑入动画
  25. gameOverPanelNode->runAction(MoveTo::create(0.5f, Vec2(origin.x + size.width / 2, origin.y + size.height / 2)));
  26. SimpleAudioEngine::getInstance()->playEffect("swooshing.mp3");
  27. //加入菜单
  28. MenuItemImage *restartItem = MenuItemImage::create("start_btn.png", "start_btn_pressed.png", this,menu_selector(GameScene::gameRetart));
  29. auto menu = CCMenu::createWithItem(restartItem);
  30. menu->setPosition(origin.x + size.width / 2, 150);
  31. this->addChild(menu);
  32. }
  33. //游戏又一次開始
  34. void GameScene::gameRetart(Ref *sender)
  35. {
  36. //又一次回到初始画面
  37. auto gameScene = GameScene::createScene();
  38. Director::getInstance()->replaceScene(gameScene); //这里懒得加特效了,直接转场
  39. }

效果图:

 

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMjIzNDExNQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">


watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMjIzNDExNQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt=""> 



源码

csdn下载:MyFlappyBird
github下载:MyFlappyBird

还有非常多要完好的地方,比方没有增加图片数字以及社交分享等等。



cocos2dx实例开发之flappybird(入门版)的更多相关文章

  1. Cocos2d-X游戏开发之Windows7+VS2010环境搭建(亲测)

    Cocos2d—X游戏引擎,提供Mac系统下的Xcode开发和Windows系统的VS开发,应该是比较常用的2种. 使用Mac以后,就会发现使用Xcode开发实在是福分啊.VS开发步骤繁琐,调试效率低 ...

  2. Cocos2d-x游戏开发之lua编辑器 Sublime 搭建,集成cocos2dLuaApi和自有类

    版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/wisdom605768292/article/details/34085969 Sublime Te ...

  3. 前端开发之CSS入门篇

    一.CSS介绍和语法 二.CSS引入方式 三.基本选择器 四.高级选择器 五.伪类选择器 六.伪元素选择器 1️⃣  CSS介绍和语法 1. CSS的介绍 (1)为什么需要CSS? 使用css的目的就 ...

  4. Cocos2d-x游戏开发之luaproject创建

    操作系统:OS X 10.85 Cocos2d-x 版本号: 2.2.1 使用Cocos2d-x 能够创建luaproject,已经使用cpp创建的project也能够继承lua进行开发,可是lua并 ...

  5. 安卓开发之ListView入门

    <!--这个地方最好用match_parent 这样效率高--> <ListView android:layout_width="match_parent" an ...

  6. Cocos2d-x 3.x游戏开发之旅

    Cocos2d-x 3.x游戏开发之旅 钟迪龙 著   ISBN 978-7-121-24276-2 2014年10月出版 定价:79.00元 516页 16开 内容提要 <Cocos2d-x ...

  7. UWP开发之Template10实践:本地文件与照相机文件操作的MVVM实例(图文付原代码)

    前面[UWP开发之Mvvmlight实践五:SuspensionManager中断挂起以及复原处理]章节已经提到过Template10,为了认识MvvmLight的区别特做了此实例. 原代码地址:ht ...

  8. Android驱动开发之Hello实例

    Android驱动开发之Hello实例:   驱动部分 modified:   kernel/arch/arm/configs/msm8909-1gb_w100_hd720p-perf_defconf ...

  9. python开发之路:python数据类型(老王版)

    python开发之路:python数据类型 你辞职当了某类似微博的社交网站的底层python开发主管,官还算高. 一次老板让你编写一个登陆的程序.咔嚓,编出来了.执行一看,我的妈,报错? 这次你又让媳 ...

随机推荐

  1. 数据结构(12) -- 图的邻接矩阵的DFS和BFS

    //////////////////////////////////////////////////////// //图的邻接矩阵的DFS和BFS ////////////////////////// ...

  2. linux3.0.4编译LDD中的scull全过程

    按照惯例,我是应该先写一些本章的收获的,不过太晚了. 在看完第三章之后开始编译,错误一堆,几乎崩溃,幸亏经过不断的百度,总算解决了问题,我发现 我遇到问题比较多,算是集中七个龙珠了吧,感谢先行的大神们 ...

  3. windows分屏

    一.准备 主机.显示屏A.显示屏B.DVI连接线2根 二.操作步骤 1.使用DVI连接线将显示屏A连接到主机上,开机进入windows系统(演示用的是win 7)(若已连接,请跳到第2步.基本上这一步 ...

  4. openstack kilo 流量

  5. 2016 ACM/ICPC 沈阳站 小结

    铜铜铜…… 人呐真奇怪 铁牌水平总想着运气好拿个铜 铜牌水平总想着运气好拿个银 估计银牌的聚聚们一定也不满意 想拿个金吧 这次比赛挺不爽的 AB两道SB题,十分钟基本全场都过了 不知道出这种题有什么意 ...

  6. rdlc 分页操作

    工具箱中拖一个列表过来,设置 列表-->行组-->组属性常规-->组表达式=Int((RowNumber(Nothing)-1)/10)分页符-->勾选在组的结尾

  7. js中Number数字数值运算后值不对

    问题: 37.5*5.5=206.08 (JS算出来是这样的一个结果,我四舍五入取两位小数) 我先怀疑是四舍五入的问题,就直接用JS算了一个结果为:206.08499999999998 怎么会这样,两 ...

  8. Window服务初级教程以及log4net配置文件初始化

    Window服务初级教程:http://www.jb51.net/article/48987.htm 另外,配置log4net这个日志功能的时候需要初始化,不然会报没有初始化的错误,而且初始化的节点应 ...

  9. iOS单例 宏定义

    #define singleton_interface(className) \ + (className *)shared##className; // @implementation #defin ...

  10. 结构类模式(六):享元(Flyweight)

    定义 运用共享技术有效的支持大量细粒度的对象. 两个状态 内蕴状态存储在享元内部,不会随环境的改变而有所不同,是可以共享的. 外蕴状态是不可以共享的,它随环境的改变而改变的,因此外蕴状态是由客户端来保 ...