cocos2dx3.3开发FlappyBird总结十六:游戏层实现

游戏有三种状态,准备开始、游戏中、游戏结束,定义一个枚举来表示:

/**
 * The status of game, it has three status.
 */
typedef enum tag_GameState {
  /** The game hasn't started, but ready to start */
  kGameStateReady = 1,
  /** The game has started, and the player is playing the game */
  kGameStateStarted,
  /** The game has over, it means that the player has lost */
  kGameStateOver
} GameState;

游戏层与控制层需要通信,因此要遵守代理协议:

class GameScene : public cocos2d::Layer, public OptionDelegate 

代理协议只有一个方法,就是点击屏幕事件监听回调:

  /**
   * The OptionDelegate method override
   */
  void onTouch();

游戏层还需要与状态层通信,因此需要接收一个代理:

  /**
   * The delegate of status.
   * When is setted,it will call StatusDelegate corresponding method on correct
   * time.
   */
  CC_SYNTHESIZE(GameStatusDelegate*, _statusDelegate, StatusDelegate);

把状态层对象作为代理传到游戏层,就可以二者通信了。

下面看下初始化方法,这个是很重要的,添加物理特性,并监听触碰事件:

bool GameScene::init() {
  if (!Layer::init()) {
    return false;
  }

  auto size = Director::getInstance()->getVisibleSize();
  auto origin = Director::getInstance()->getVisibleOrigin();

  // Add the bird
  auto bird = BirdSprite::getInstance();
  bird->createBird();
  auto body = PhysicsBody::create();
  body->addShape(PhysicsShapeCircle::create(kBirdRadius));
  body->setDynamic(true);
  body->setLinearDamping(0.0f);
  body->setGravityEnable(false);
  body->setCategoryBitmask(0x01);
  body->setCollisionBitmask(0x01);
  body->setContactTestBitmask(0x01);
  bird->setPhysicsBody(body);
  bird->setPosition(origin.x + size.width / 3 - 5, origin.y + size.height / 2 + 5);
  bird->setActionState(kActionStateIdle);
  this->addChild(bird);

  // Add the ground
  _groundNode = Node::create();
  auto groundBody = PhysicsBody::create();
  auto groundSize = Size(kDesignWidth, BackgroundLayer::getLandHeight());
  groundBody->addShape(PhysicsShapeBox::create(groundSize));
  groundBody->setDynamic(false);
  groundBody->setLinearDamping(0.0f);
  groundBody->setCategoryBitmask(0x01);
  groundBody->setContactTestBitmask(0x01);
  groundBody->setCollisionBitmask(0x01);
  _groundNode->setPhysicsBody(groundBody);
  _groundNode->setPosition(groundSize.width / 2, groundSize.height);
  this->addChild(_groundNode);

  // land
  // Add the land1 and land2
  _land1 = Sprite::createWithSpriteFrame(AtlasLoader::getInstance()->getSpriteFrame("land"));
  _land1->setAnchorPoint(Vec2::ZERO);
  _land1->setPosition(Vec2::ZERO);
  this->addChild(_land1, 30);

  _land2 = Sprite::createWithSpriteFrame(AtlasLoader::getInstance()->getSpriteFrame("land"));
  _land2->setAnchorPoint(Vec2::ZERO);
  _land2->setPosition(Vec2(_land1->getContentSize().width - 2.0f, 0));
  this->addChild(_land2, 30);

  // Add a timer to update the land
  _shiftLand = schedule_selector(GameScene::scrollLand);
  this->schedule(_shiftLand, 0.01f);

  // will call update(float delta) method
  this->scheduleUpdate();

  // Add contact listener
  //
  // If body->getCategoryBitmask() & groundBody->getContactTestBitmask() == 1
  // Then we can listen the physics touch event, otherwise not.
  //
  // If body->getCategoryBitmask() & groundBody->getCollisionBitmask() == 1
  // Then the bird and the ground can collide, otherwise not.
  auto listener = EventListenerPhysicsContact::create();
  listener->onContactBegin = CC_CALLBACK_1(GameScene::onContactBegin, this);
  this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, this);

  return true;
}

要想让A和B能够在相碰时,发出事件,规则如下:
// If body->getCategoryBitmask() & groundBody->getContactTestBitmask() == 1
// Then we can listen the physics touch event, otherwise not.
要想让A和B能够发生冲突并发出事件,规则如下:
//
// If body->getCategoryBitmask() & groundBody->getCollisionBitmask() == 1
// Then the bird and the ground can collide, otherwise not.

如果不添加,默认情况下是不会监听到的,刚开始我就遇到此问题,然后通过百度才明白原因。

下面是创建场景,需要把背景层、控制层、状态层、游戏层添加到场景中:

Scene* GameScene::createScene() {
  // create a scene with physics world
  // 场景需要使用物理世界来创建,否则添加的物理特性就无效
  auto scene = Scene::createWithPhysics();
  if (scene->getPhysicsWorld()) {
    scene->getPhysicsWorld()->setGravity(Vect(0, -900));
  } else {
    CCLOG("Error: Game scene get physics world, but it is nullptr");
  }

  // background layer
  auto backgroundLayer = BackgroundLayer::create();
  if (backgroundLayer) {
    scene->addChild(backgroundLayer);
  }

  // game layer
  auto gameLayer = GameScene::create();
  // status layer
  auto statusLayer = StatusLayer::create();

  if (gameLayer) {
    gameLayer->setPhysicsWorld(scene->getPhysicsWorld());
    // 游戏层与状态层是需要通信的,把状态层作为游戏层的代理
    gameLayer->setStatusDelegate(statusLayer);
    gameLayer->setTag(kGameLayerTag);
    scene->addChild(gameLayer);
  }
  if (statusLayer) {
    scene->addChild(statusLayer);
  }

  // option layer
  auto optionLayer = OptionLayer::create();
  if (optionLayer) {
  // 状态层与游戏层是需要通信的,把游戏层作为状态层的代理
    optionLayer->setOptionDelegate(gameLayer);
    scene->addChild(optionLayer);
  }

  return scene;
}

添加水管,给水管也添加物理特性,就可以让小鸟与水管在接触时,发出相碰事件

void GameScene::createPipes() {
  // create pipes
  auto size = Director::getInstance()->getVisibleSize();
  for (int i = 0; i < kPipePairCount; ++i) {
    auto pipeUp = Sprite::createWithSpriteFrame(AtlasLoader::getInstance()->getSpriteFrame("pipe_up"));
    //pipeUp->setPosition(0, <#float y#>)
    auto pipeDown = Sprite::createWithSpriteFrame(AtlasLoader::getInstance()->getSpriteFrame("pipe_down"));
    pipeDown->setPosition(0, kPipeHeight + kPipeUpDownDistance);

    auto pipeNode = Node::create();
    pipeNode->setPosition(size.width + i * kPipeInterval + kWaitDistance,
                          getRandomPipeHeight());
    pipeNode->addChild(pipeDown, 0, kPipeDownTag);
    pipeNode->addChild(pipeUp, 0, kPipeUpTag);

    // Add physics to pipe
    auto body = PhysicsBody::create();
    auto box = PhysicsShapeBox::create(pipeDown->getContentSize(),
                                       PHYSICSSHAPE_MATERIAL_DEFAULT,
                                       Vec2(0, kPipeHeight + kPipeUpDownDistance));
    body->addShape(box);
    body->addShape(PhysicsShapeBox::create(pipeUp->getContentSize()));
    body->setDynamic(false);
// If body->getCategoryBitmask() & 小鸟的物理body>->getCollisionBitmask() == 1
// 小鸟与水管接触才会发出事件
    body->setCategoryBitmask(0x01);
    body->setContactTestBitmask(0x01);
    body->setCollisionBitmask(0x01);

    pipeNode->setPhysicsBody(body);
    pipeNode->setTag(kPipeNewTag);
    this->addChild(pipeNode);

    _pipes.pushBack(pipeNode);
  }
}

小鸟通过水管检测:

void GameScene::checkHit() {
  for (auto pipe : _pipes) {
    if (pipe->getTag() == kPipeNewTag) {
      // 通过一根
      if (pipe->getPositionX() < BirdSprite::getInstance()->getPositionX()) {
        CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("sfx_point.ogg");

        ++_currentScore;

        if (this->getStatusDelegate()) {
          this->getStatusDelegate()->onGamePlaying(_currentScore);
        }

        pipe->setTag(kPipePassedTag);
      }
    }
  }
}

通过遍历所有水管,如果当前是显示在屏幕上新的水管,再判断与小鸟的X坐标,来判断小鸟是否通过了水管。
当通过后,又把水管设置为已经通过的水管,如此重复使用。

时间: 2024-09-16 14:57:17

cocos2dx3.3开发FlappyBird总结十六:游戏层实现的相关文章

cocos2dx3.3开发FlappyBird总结十:背景层设计

游戏背景层的任务是很简单的,只是根据当前时间来显示白天或者黑夜背景图,提供获取地面的高度方法. #ifndef __EngryBird__BackgroundLayer__ #define __EngryBird__BackgroundLayer__ #include "cocos2d.h" /** * The game background,showing the background information * in the game. */ class BackgroundLay

cocos2dx3.3开发FlappyBird总结十二:状态层设计

状态层是比较复杂的了,状态层需要与游戏层通信,因此也需要为游戏层先设计一个代理类,以便状态层遵守游戏层的代理,这样游戏层就可以在游戏开始.得分.结束时,告诉状态层做出相应的状态表现了. 游戏层的代理类: /** * The delegate between status layer and game layer */ class GameStatusDelegate { public: /** * When the game start, this method will be called *

cocos2dx3.3开发FlappyBird总结十四:常量定义

游戏层中水管等需要常量: #ifndef EngryBird_AppConstant_h #define EngryBird_AppConstant_h /** * The pipe has four state, using the following tag to mark. * > the state up * > the state down * > the state passed * > the state new created */ const int kPipeU

cocos2dx3.3开发FlappyBird总结十五:记录玩家得分

在游戏结束时,需要更新和获取最新得分. 设计一个工具类,只有类方法,这样外部就能很方便地获取和更新值. /** * This is a help class, using to operate the user information conveniencely */ class RecordTool { public: /** * Get the best score with a key, store in the UserDefault */ static int getBestScore

cocos2dx3.3开发FlappyBird总结十一:控制层功能设计

控制层的任务就是监听触摸事件,然后回调代理方法.控制层并不具体处理任务事情,只是抛给代理处理,因此需要先设计一个代理. 代理只是一个方法,那就是触摸: /** * The delegate between option layer and game layer */ class OptionDelegate { public: /** * When touch the option layer, it will be called */ virtual void onTouch() = 0; }

Kinect for Windows SDK开发入门(十六)面部追踪上

在前面一篇文章中,我们使用Emgu来识别人的脸部,当时的Kinect SDK版本是1.0,五月份发布1.5版本的SDK之后,我们就能够直接使用Kinect实现人脸识别,而不需要借助第三方类库. SDK1.5中新增了人脸识别类库:Microsoft.Kinect.Toolkit.FaceTracking使得在Kinect中进行人脸识别变得简单,该类库的源代码也在Developer Toolkit中.在Developer Toolkit中也自带人脸识别的例子,您也可以打开运行或者查看源代码. 开发入

Android开发入门(十六)其他视图 16.2 WebView

WebView能够让你在activity中去内嵌一个web浏览器.如果你的应用需要内嵌一些web内容的话,这是非 常有用的. 下面展示如何在activity中通过编码的方式去加载网页中的内容. 1. 新建一个工 程,WebView. 2. main.xml中的代码. <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.andr

Android开发入门(十六)其他视图 16.1 AnalogClock和DigitalClock

AnalogClock视图显示了一个模拟的时钟,其中有一个时针和一个分针.与其相对的是DigitalClock视图 ,它可以显示数字模拟时钟.这两个视图只能显示系统时间,不允许显示一个特定时区的时间.因此,如果 你想要显示一个特定时区的时间,那么你就不得不去实现你自己的自定义控件了. 注:关于如何自定 义控件,请查看如下网址. http://developer.android.com/guide/topics/ui/custom- components.html 使用AnalogClock与Di

Xamarin.Android开发实践(十六)

原文:Xamarin.Android开发实践(十六) Xamarin.Android之Fragment Walkthrough 利用Fragment设计能够兼容不同屏幕的应用 这里我们先围观下最后的成果图,给读者打打气:   普通手机上显示的结果:   在平板上显示的结果:   笔者要郑重声明下,虽然看似是两种不同的显示效果,但是同一个应用,而下面笔者将逐步教会大家如何利用Fragment制作出能够兼容不同屏幕的应用.   准备工作 创建一个项目是必不可少的,并且Android SDK的版本要在