cocos2d-x 3.1 flappy bird 简单学习制作

原文地址  http://www.is17.com/p181.html

学习cocos2dx3.1中,网上教程不算特别多,参照各种教程,慢慢总结编写,跟引用了一些优秀类,总算做出来了个超简单的像素鸟游戏。
现在讲解一下核心内容跟,我遇到的一些问题的处理方法。

一、新建项目:
其实网上各种各样的环境配置眼花缭乱的也不一定配置成功,我们要做的仅仅是建一个可以显示helloworld的东西。
我这里有个超简单的方法。
原材料:vs2012,cocos2dx3.1.1  没错,只需要这些。
打开某英文(中文将导致编译失败)目录下的D:\cocos2d-x-3.1.1\build\cocos2d-win32.vc2012.sln
右键解决方案,生成解决方案,等十几分钟吧大概。i3i5表示都是100%cpu,卡的一比。
完了之后,右键cpp-empty-test->设为启动项目。---->F5。不出意外的话出现了helloworld,这么简单都失败的话请百度。
我们的新项目就是改这个东西,我们仅仅是学习,现在不用看那些各种Python的东西来生成新项目====。

二、分析游戏:
其实我写的时候是俩眼一抹黑,先做核心,然后在添加各种功能,然后代码各种改,对于一个没有学过c++的人,各种问题各种出。
真正做东西我们要理清思路,设计类什么的更好一些。思路清晰方能事半功倍。
小鸟,背景,水管,地面各做一个类,我写的仓促,地面并没有写成类。
小鸟类核心代码:Player

[cpp]  view plain copy
  1. bool Player::init()  
  2. {  
  3.  //当时为了显高端引用了别人的一个大图类,所有资源打包的那种,其实并不好用,而且我并不知道他的包怎么打,但是思路挺好,真正的官方用法是引用cocostudio打包  
  4.  playerSp = Sprite::createWithSpriteFrame(AtlasLoader::getInstance()->getSpriteFrameByName("bird0_0"));  
  5.  // 注释掉这一句跟上句相同效果。  
  6.  // playerSp = Sprite::create("bird0_0.png");  
  7.  this->addChild(playerSp);   
  8.  //小鸟飞行动画  
  9.  auto animation = Animation::create();  
  10.  char szName[100] = {0};  
  11.  //将小鸟的三张图片做成动画,  
  12.     forint i=0;i<3;i++)  
  13.     {  
  14.         sprintf(szName, "bird0_%d.png", i);  
  15.         animation->addSpriteFrameWithFile(szName);  
  16.     }  
  17.     animation->setDelayPerUnit(1.8f / 14.0f);  
  18.     auto action = Animate::create(animation);  
  19.  // 小鸟循环飞行  
  20.     playerSp->runAction(RepeatForever::create(action));  
  21.   
  22.  // 添加刚体,这个类中小鸟自动附加到刚体上  
  23.     auto body = PhysicsBody::createCircle(playerSp->getContentSize().width * 0.3f);  
  24.     body->getShape(0)->setFriction(0);  
  25.     body->getShape(0)->setRestitution(1.0f);  
  26.  //以下三行设定可以碰撞,具体参数不懂,反正这样就触发碰撞了。  
  27.     body->setCategoryBitmask(1);    // 0001  
  28.     body->setCollisionBitmask(1);   // 0001  
  29.     body->setContactTestBitmask(1); // 0001  
  30.     this->setPhysicsBody(body);     
  31.     return true;  
  32. }  
  33. //小鸟死亡,仅作了停止播放动画  
  34. void Player::die(){  
  35.   playerSp->stopAllActions();  
  36. }  

 背景类:bggroundlayer

[cpp]  view plain copy
  1.  Size visibleSize = Director::getInstance()->getVisibleSize();  
  2.   
  3.     /* 生成两张图片放在0和游戏宽度位置 */  
  4.     m_bg1= Sprite::createWithSpriteFrame(AtlasLoader::getInstance()->getSpriteFrameByName("bg_day"));  
  5.     m_bg1->setPosition(Point(0, 0));  
  6.  m_bg1->setAnchorPoint(Point::ZERO);  
  7.  //设置抗锯齿修正拼图缝隙(这个很有用不加会有一个显示bug可自己测试)  
  8.  m_bg1->getTexture()->setAliasTexParameters();  
  9.     this->addChild(m_bg1);  
  10.  m_bg2= Sprite::createWithSpriteFrame(AtlasLoader::getInstance()->getSpriteFrameByName("bg_day"));  
  11.     m_bg2->setPosition(Point(visibleSize.width, 0));  
  12.  m_bg2->setAnchorPoint(Point::ZERO);  
  13.  m_bg1->getTexture()->setAliasTexParameters();  
  14.     this->addChild(m_bg2);  
  15. //这个是根据帧频率触发的一个公共方法,来实现地图的横向滚动  
  16. void BackgroundLayer::logic(float dt)  
  17. {  
  18.     int posX1 = m_bg1->getPositionX(); // 背景地图1的X坐标  
  19.     int posX2 = m_bg2->getPositionX(); // 背景地图2的X坐标  
  20.   
  21.     int iSpeed = 1;    // 地图滚动速度,其实这个可以提出来的  
  22.   
  23.     /* 两张地图向上滚动(两张地图是相邻的,所以要一起滚动,否则会出现空隙) */  
  24.     posX1 -= iSpeed;  
  25.     posX2 -= iSpeed;  
  26.   
  27.     /* 屏幕宽 */  
  28.     int iVisibleWidth = Director::getInstance()->getVisibleSize().width;  
  29.     /* 当第1个地图完全离开屏幕时,让第2个地图完全出现在屏幕上,同时让第1个地图紧贴在第2个地图后面 */  
  30.     if (posX1 < -iVisibleWidth) {  
  31.         posX2 = 0;  
  32.         posX1 = iVisibleWidth;  
  33.     }  
  34.     /* 同理,当第2个地图完全离开屏幕时,让第1个地图完全出现在屏幕上,同时让第2个地图紧贴在第1个地图后面 */  
  35.     if (posX2 < -iVisibleWidth) {  
  36.         posX1 = 0;  
  37.         posX2 = iVisibleWidth;  
  38.     }  
  39.     m_bg1->setPositionX(posX1);  
  40.     m_bg2->setPositionX(posX2);  
  41. }  

管道类:Conduit

写这个遇到了很大问题,我并没有接触过cocos,只是找例子来参考做,使用了跟小鸟一样的生成方法,要么只能生成一个刚体,要么生成之后变成了类似于单例的样子,无法生成多个。后来终于看到了这个方法解决了问题。

[cpp]  view plain copy
  1. int iVisibleWidth = Director::getInstance()->getVisibleSize().width;  
  2. int jianHeight = 100;  
  3. auto pipeUpSp = Sprite::createWithSpriteFrame(AtlasLoader::getInstance()->getSpriteFrameByName("pipe_up"));  
  4. pipeUpSp->setPosition(Point(0, 0));  
  5. this->addChild(pipeUpSp);   
  6.   
  7. auto pipeDownSp = Sprite::createWithSpriteFrame(AtlasLoader::getInstance()->getSpriteFrameByName("pipe_down"));  
  8. pipeDownSp->setPosition(Point(0, pipeUpSp->getContentSize().height+jianHeight));  
  9. this->addChild(pipeDownSp);   
  10.   
  11. // 添加刚体  
  12. auto body = PhysicsBody::create();  
  13. body->setDynamic(false);  
  14. //我们生成了一个刚体,向这里放入两个元素。这样我们就生成了一堆可操作的管道了。  
  15. body->addShape(PhysicsShapeBox::create(pipeUpSp->getContentSize(),PHYSICSSHAPE_MATERIAL_DEFAULT, Point(0, 0)));  
  16. body->addShape(PhysicsShapeBox::create(pipeDownSp->getContentSize(),PHYSICSSHAPE_MATERIAL_DEFAULT, Point(0, pipeUpSp->getContentSize().height+jianHeight)));  
  17.    body->setCategoryBitmask(1);    // 0001  
  18.    body->setCollisionBitmask(1);   // 0001  
  19.    body->setContactTestBitmask(1); // 0001  
  20.    this->setPhysicsBody(body);  

地面我并没有写到类里边,加载地面也并没有新技术,就是创建一个Sprite,一个Body。其移动方法如下

[cpp]  view plain copy
  1. ground->setPositionX(ground->getPositionX() - 1);  
  2. if(ground->getPositionX() < ground->getContentSize().width/2-24) {  
  3.  ground->setPositionX(ground->getContentSize().width/2-1);  
  4. }  

 然后我们将这些东西添加到场景类中,在这里之前我们要修改自带的类,使之能跳转到我们的场景类。

我们修改AppDeleGate.cpp-->applicationDidFinishLaunching使其加载loadscene,

[cpp]  view plain copy
  1.   director->setOpenGLView(glview);  
  2. * 设置Win32屏幕大小为288X512, */  
  3.   glview->setFrameSize(288, 512);  
  4.   /* 简单的屏幕适配,按比例拉伸,可能有黑边 */  
  5.   glview->setDesignResolutionSize(288, 512, ResolutionPolicy::SHOW_ALL);   
  6.   // turn on display FPS  
  7.   director->setDisplayStats(true);  
  8.   // set FPS. the default value is 1.0/60 if you don't call this  
  9.   director->setAnimationInterval(1.0 / 60);  
  10.   // 这里添加load类  
  11. //  auto scene = TollgateScene::scene();  
  12. uto scene = LoadingScene::create();  
  13.   // run  
  14.   director->runWithScene(scene);  

loadscene类:loadscene中实现了一个开头切换场景动画,也可用作实现load进度条之类。

[cpp]  view plain copy
  1. void LoadingScene::onEnter(){  
  2.  // add background to current scene  
  3.   auto fileUtils = FileUtils::getInstance();  
  4.     std::vector<std::string> searchPaths;  
  5.      
  6.     searchPaths.push_back("image");      
  7.     fileUtils->setSearchPaths(searchPaths);  
  8.   
  9.  Sprite *background = Sprite::create("splash.png");  
  10.  Size visibleSize = Director::getInstance()->getVisibleSize();  
  11.     Point origin = Director::getInstance()->getVisibleOrigin();  
  12.  background->setPosition(origin.x + visibleSize.width/2, origin.y + visibleSize.height/2);  
  13.  this->addChild(background);  
  14.  //加载1024的一张大图  
  15.  Director::getInstance()->getTextureCache()->addImageAsync("atlas.png", CC_CALLBACK_1(LoadingScene::loadingCallBack, this));  
  16. }  
  17.   
  18. void LoadingScene::loadingCallBack(Texture2D *texture){  
  19.  //配合atlastxt  
  20.  AtlasLoader::getInstance()->loadAtlas("atlas.txt", texture);  
  21.   
  22.  //切换场景,TollgateScene便是我们的主要游戏场景类  
  23.  auto scene = TollgateScene::scene();  
  24.  TransitionScene *transition = TransitionFade::create(1, scene);  
  25.  Director::getInstance()->replaceScene(transition);  
  26. }  

然后就是最重要的TollgateScene类了,这里我们实现了游戏逻辑。
首先我们添加重力环境,当时我顺手把背景也写在了这里边,背景不用重复加载也就没改。

[cpp]  view plain copy
  1. Scene* TollgateScene::scene()  
  2. {  
  3.     auto scene = Scene::createWithPhysics();  
  4.  /**设置重力*/  
  5.     Vect gravity(0, -300.1f);  
  6.     scene->getPhysicsWorld()->setGravity(gravity);  
  7.   
  8.     /* 开启测试模式  这将会给所有刚体加一个红框 */  
  9.    // scene->getPhysicsWorld()->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL);   
  10.  auto backgroundLayer = BackgroundLayer::create();  
  11.     //addChild方法添加到场景,第二个参数是层级,越大层级越高越靠前  
  12.     scene->addChild(backgroundLayer, 0);  
  13.   
  14.     auto layer = TollgateScene::create();  
  15.     scene->addChild(layer, 10);  
  16.    
  17.  layer->m_backgroundLayer = backgroundLayer;  
  18.     return scene;  
  19. }  
  20. [/cc]  
  21. 然后我们在init函数里边添加一些初始显示且不怎么变动的东西。  
  22. [cc lang="c++"]  
  23. bool TollgateScene::init()  
  24. {  
  25.     if (!Layer::init())  
  26.     {  
  27.         return false;  
  28.     }  
  29. //场景尺寸  
  30.  visibleSize = Director::getInstance()->getVisibleSize();  
  31. //初始化一个个性字体类  
  32.  Number::getInstance()->loadNumber(NUMBER_FONT.c_str(), "font_0%02d", 48);  
  33. //添加开始按钮  
  34.  this->initGame();   
  35. //添加地面  
  36.  this->setGround();  
  37.     return true;  
  38. }  
  39. /**添加开始按钮 按钮点击会触发游戏开始事件。*/  
  40. void TollgateScene::initGame(){  
  41.  auto closeItem = MenuItemImage::create( "play_0.png""play_1.png", CC_CALLBACK_1(TollgateScene::startGame,this));  
  42.     closeItem->setPosition(Point(visibleSize.width / 2, visibleSize.height /2 ));  
  43.     // create menu, it's an autorelease object  
  44.     menu = Menu::create(closeItem, NULL);  
  45.     menu->setPosition(Vec2::ZERO);  
  46.     this->addChild(menu, 1);  
  47. }  

最最重点的游戏开始了:(其实并不复杂,我们设定了初始等级,隐藏开始按钮,添加小鸟与管道,添加随帧事件,添加碰撞事件,逻辑很清晰)

[cpp]  view plain copy
  1. void TollgateScene::startGame(Ref* sender){  
  2.  level = 0;  
  3.  showLevel(level);  
  4.  menu->setVisible(false);  
  5.   
  6.  this->setConduit();  
  7.  this->setBird();  
  8.    
  9.  this->schedule(schedule_selector(TollgateScene::logic));  
  10.  //监听事件  
  11.  auto listener = EventListenerTouchOneByOne::create();  
  12.  listener->setSwallowTouches(true);  
  13.  listener->onTouchBegan = CC_CALLBACK_2(TollgateScene::onTouchBegan, this);  
  14.  _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);  
  15.   
  16.  auto contactListener = EventListenerPhysicsContact::create();  
  17.  contactListener->onContactBegin = CC_CALLBACK_1(TollgateScene::onContactBegin,this);  
  18.  _eventDispatcher->addEventListenerWithSceneGraphPriority(contactListener, this);  
  19. }  
  20. //加载小鸟  
  21. void TollgateScene::setBird(){  
  22.  player = Player::create();  
  23.  player->setPosition(Point(visibleSize.width / 2, visibleSize.height /2 +100));  
  24.     this->addChild(player,5);   
  25. }  
  26. //加载管道,我们把管道添加到一个数组里边,以遍来播放管道动画,其实我们只用到了两组管道来重复播放。  
  27. void TollgateScene::setConduit(){  
  28.  for (int i = 0; i < 2; i++) {   
  29.   conduit = Conduit::create();   
  30.   conduit->setPosition(Point(visibleSize.width + i*PIP_INTERVAL + PIP_WIDTH, this->getRandomHeight()));  
  31.   this->addChild(conduit,4);  
  32.         this->pips.push_back(conduit);  
  33.  }  
  34. }  
  35. //给管道一个随机高度  
  36. int TollgateScene::getRandomHeight() {  
  37.     Size visibleSize = Director::getInstance()->getVisibleSize();  
  38.     return rand()%(int)(2*PIP_HEIGHT + PIP_DISTANCE - visibleSize.height);  
  39. }  
  40. //点击屏幕  简单的给小鸟添加一个向上的力量  
  41. bool TollgateScene::onTouchBegan(Touch* touch, Event* event) {  
  42.  player->getPhysicsBody()->setVelocity(Vect(0, 180));  
  43.  return true;  
  44. }  
  45. //碰撞触发游戏结束事件,显示按钮,游戏结束,小鸟死亡。(好吧这个函数里并没写)  
  46. bool TollgateScene::onContactBegin(const PhysicsContact& contact) {  
  47.  menu->setVisible(true);  
  48.  this->gameOver();  
  49.  return true;  
  50. }  
  51. //游戏结束  停止帧事件,结束侦听事件,清空数组,移除显示元素。  
  52. void TollgateScene::gameOver(){  
  53.  this->unschedule(schedule_selector(TollgateScene::logic));  
  54.  _eventDispatcher->removeAllEventListeners();  
  55.    
  56.  for (auto singlePip : this->pips) {  
  57.   this->removeChild(singlePip);  
  58.     }   
  59.  pips.clear();  
  60.   
  61.  player->die();  
  62.  this->removeChild(player);  
  63.  this->removeChild(menu);  
  64.  this->removeChild(scoreSprite);  
  65.  this->initGame();  
  66. }  

 还有一些动态的东西,并不多:

[cpp]  view plain copy
  1. [cc lang="c++"]  
  2. //背景移动  
  3. void TollgateScene::logic(float dt)  
  4. //调用背景移动  
  5.     m_backgroundLayer->logic(dt);  
  6.  //这里好像介绍过了  
  7.  ground->setPositionX(ground->getPositionX() - 1);  
  8.  if(ground->getPositionX() < ground->getContentSize().width/2-24) {  
  9.   ground->setPositionX(ground->getContentSize().width/2-1);  
  10.  }  
  11.   
  12.  for (auto singlePip : this->pips) {  
  13. //判断过关,当管道x<小鸟x  
  14.         singlePip->setPositionX(singlePip->getPositionX() - 1);  
  15.   if(singlePip->getPositionX() == visibleSize.width/2-PIP_WIDTH) {  
  16.    level++;  
  17.    showLevel(level);  
  18.   }  
  19. //当管道从这个移出屏幕了再让他从另一边进来。  
  20.         if(singlePip->getPositionX() < -PIP_WIDTH) {  
  21.               
  22.             singlePip->setPositionX(visibleSize.width+PIP_WIDTH/2);  
  23.             singlePip->setPositionY(this->getRandomHeight());  
  24.         }  
  25.     }  
  26. }  
  27. //显示关卡等级 Number类会根据数字返回一个精灵,也就是图片版的数字,比较好看而已。当过关了就刷一下。  
  28. void TollgateScene::showLevel(int lev){  
  29.  this->removeChild(scoreSprite);  
  30.  scoreSprite = (Sprite* )Number::getInstance()->convert(NUMBER_FONT.c_str(), lev);  
  31.  scoreSprite->setPosition(Point(visibleSize.width/2-scoreSprite->getContentSize().width/2,visibleSize.height*7/8));  
  32.  this->addChild(scoreSprite,20);  
  33. }  

讲解跟流程写完了,可能还遗漏了一些东西大致流程就是这样了,请忽略排版,我的编辑器是最原始的。

最最最后,附上源码,跟素材。(好吧这才是最重要的)

----------------------------------------

源码

--------------------------------

资源

--------------------------------

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值