Tuesday, October 7, 2014

Cocos2d-x: Game Sky Defense (Phần cuối)


1.1 Action và Animation

1.1.1 Action


Mỗi Node trong Cocos2d-x có các thông tin thuộc tính về: position, rotation, scale, visibility, opacity. Action trong Cocos2d-x sẽ thay đổi một trong các thuộc tính này theo thời gian.

Trong file InGameScene.h


class InGameScene : public Layer

{

private:

      void createActions();

      void shockWaveFinish();

      void animationFinish(Node *sender);

private:

      Action *swingAction_;

      Action *shockwaveAction_;

      Action *growBombAction_;

      Action *rotateAction_;

      Action *groundHitAction_;

      Action *explosionAction_;

};



- Action cho sóng xung lực khi bom nổ

FiniteTimeAction *easeSwing = Sequence::create(EaseInOut::create(RotateTo::create(1.2f, -10), 2),

EaseInOut::create(RotateTo::create(1.2f, 10), 2),

NULL);

swingAction_ = RepeatForever::create((ActionInterval *)easeSwing);

swingAction_->retain();

shockwaveAction_ = Sequence::create(FadeOut::create(1.0f),

CallFunc::create(CC_CALLBACK_0(InGameScene::shockWaveFinish, this)),

NULL);

shockwaveAction_->retain();



void InGameScene::shockWaveFinish()

{

      shockWave_->setVisible(false);

}

swingAction_ là một action lặp đi lặp lại mãi mãi gồm các action quay theo chiều kim đồng hồ và ngược lại 1 góc 10 độ . Action EaseInOut sẽ thực thi một action con bên trong theo tỷ lệ đưa vào. Hàm create của lớp Action tạo ra một đối tượng Action autorelease, do vậy để sử dụng swingAction_ sau này, chúng ta cần phải gọi hàm retain().

Sequence là một action kiểu “hàng đợi”, thực hiện xong action con 1 sẽ tiếp action con 2,…

CC_CALLBACK_0 là macro gọi hàm gọi ngược với tên và target truyền vào, hàm gọi ngược này không có tham số.

- Action của Bom và action quay cho Thiên thạch

growBombAction_ = ScaleTo::create(6.0, 1.0);

growBombAction_->retain();

ActionInterval *rotate = RotateBy::create(0.5f, -90);

rotateAction_ = RepeatForever::create(rotate);

rotateAction_->retain();

1.1.2 Animation

//Create animation for meteor falls to ground

Animation *animation;

SpriteFrame *frame;


animation = Animation::create();

std::string name;

for (int i = 1; i <= 10; i ++) {

      name = "boom" + std::to_string(i) + ".png";

      frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(name);

      animation->addSpriteFrame(frame);

}


animation->setDelayPerUnit(1 / 10.f);

animation->setRestoreOriginalFrame(true);

groundHitAction_ = Sequence::create(MoveBy::create(0, Vec2(0, screenSize_.height * 0.12f)),

Animate::create(animation),

CallFuncN::create(CC_CALLBACK_1(InGameScene::animationFinish, this)),

NULL);

groundHitAction_->retain();



//Create animation for explosion

animation = Animation::create();

for (int i = 1; i <= 7; i ++) {

      name = "explosion_small" + std::to_string(i) + ".png";

      frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(name);

      animation->addSpriteFrame(frame);

}



animation->setDelayPerUnit(0.5 / 7.0f);

animation->setRestoreOriginalFrame(true);

explosionAction_ = Sequence::create(Animate::create(animation),

CallFuncN::create(CC_CALLBACK_1(InGameScene::animationFinish, this)),

NULL);

explosionAction_->retain();



void InGameScene::animationFinish(Node* sender)

{

      sender->setVisible(false);

}

- Lớp Animation sẽ add các SpriteFrame (ví dụ boom1, boom2…) và thiết lập thời gian delay giữa mỗi frame.

- Sau đó ta khởi tạo một action Animate từ animation vừa tạo để chuẩn bị cho các sprite tương ứng gọi sau này.

- Chúng ta để ý thấy CC_CALLBACK_1, khác với CC_CALLBACK_0 ở trên, macro này gọi đến hàm gọi ngược có 1 tham số.

1.2 Xử lý Touch

Khi người chơi touch lên màn hình game

- Nếu đang xuất hiện text tiêu đề game thì sẽ chuyển game sang trạng thái bắt đầu hoạt động

- Nếu game đang trạng thái hoạt động và chưa có đặt bom thì sẽ đặt bom

- Nếu bom đã có

o Nếu bom chưa đủ độ lớn thì hủy bom

o Nếu bom đủ độ lớn thì kích nổ bom

bool InGameScene::onTouchBegan(Touch *touch, Event *unused_event)

{

      if (!isRunning_) {

            if (startMessage_->isVisible()) {

                  startMessage_->setVisible(false);

            } else if (gameOverMessage_->isVisible()) {

                  gameOverMessage_->setVisible(false);

            }



            this->resetGame();

            isRunning_ = true;

            return true;

      }



      if (bomb_->isVisible()) {

      bomb_->stopAllActions();


      Sprite *sprite;

      sprite = (Sprite *)bomb_->getChildByTag(TAG_HALO);

      sprite->stopAllActions();


      sprite = (Sprite *)bomb_->getChildByTag(TAG_SPARKLE);

      sprite->stopAllActions();



      if (bomb_->getScale() > 0.3f) {

            shockWave_->setScale(0.1f);

            shockWave_->setVisible(true);

            shockWave_->setOpacity(255);

            shockWave_->setPosition(bomb_->getPosition());

            shockWave_->runAction(ScaleTo::create(0.5, bomb_->getScale() * 2.0f));

            shockWave_->runAction(shockwaveAction_->clone());

            AudioUtils::play(BombRelease);

      } else {

            AudioUtils::play(BombFail);

      }

      bomb_->setVisible(false);

      shockWaveHits_ = 0;

      } else {

      Vec2 pos = touch->getLocation();

      bomb_->stopAllActions();

      bomb_->setPosition(pos);

      bomb_->setScale(0.1f);

      bomb_->setVisible(true);

      bomb_->setOpacity(50);

      bomb_->runAction(growBombAction_->clone());


      Sprite *sprite;

      sprite = (Sprite *)bomb_->getChildByTag(TAG_HALO);

      sprite->runAction(rotateAction_->clone());

      sprite = (Sprite *)bomb_->getChildByTag(TAG_SPARKLE);

      sprite->runAction(rotateAction_->clone());

      }

      return true;

}


1.3 Reset Game


void InGameScene::resetGame()

{

      gameScore_ = 0;

      cityHealth_ = 100;


      //reset timers and "speeds"

      meteorInterval_ = 2.5;

      meteorTimer_ = meteorInterval_ * 0.99f;

      meteorSpeed_ = 10;//in seconds to reach ground

      healthInterval_ = 20;

      healthTimer_ = 0;

      healthSpeed_ = 15;//in seconds to reach ground


      difficultyInterval_ = 60;

      difficultyTimer_ = 0;


      isRunning_ = true;



      lblHealth_->setString("100%");

      lblScore_->setString("0");

}

1.4 Stop Game


void InGameScene::stopGame()

{

      isRunning_ = false;

      Sprite *sprite;

      while (fallingObjects_.size() > 0) {

            sprite = (Sprite *)fallingObjects_.at(fallingObjects_.size() - 1);

            sprite->stopAllActions();

            sprite->setVisible(false);

            fallingObjects_.eraseObject(sprite);

      }


      if (bomb_->isVisible()) {

            bomb_->stopAllActions();

            bomb_->setVisible(false);

            Sprite *child;

            child = (Sprite *)bomb_->getChildByTag(TAG_HALO);

            child->stopAllActions();

            child = (Sprite *)bomb_->getChildByTag(TAG_SPARKLE);

            child->stopAllActions();

      }


      if (shockWave_->isVisible()) {

            shockWave_->stopAllActions();

            shockWave_->setVisible(false);

      }

}

1.5 Thêm mới Thiên thạch rơi từ bầu trời


void InGameScene::addNewMeteor()

{

      if (fallingObjects_.size() > 30) {

            return;

      }



      Sprite *meteor = (Sprite *)meteorPool_.at(meteorPoolIndex_);

            meteorPoolIndex_ ++;

            if (meteorPoolIndex_ == meteorPool_.size()) {

            meteorPoolIndex_ = 0;

      }



      int mx = rand() % (int)(screenSize_.width * 0.8f) + screenSize_.width * 0.1f;

      int my = screenSize_.height + meteor->getBoundingBox().size.height * 0.5f;

     int m_target_x = rand() % (int)(screenSize_.width * 0.8f) + screenSize_.width * 0.1f;

      int m_target_y = screenSize_.height * 0.15f;

      meteor->stopAllActions();

      meteor->setPosition(Vec2(mx, my));

      ActionInterval *rotate = RotateBy::create(0.5f, -90);

      Action *repeatRotate = RepeatForever::create(rotate);

 FiniteTimeAction *sequence = Sequence::create(MoveTo::create(meteorSpeed_, Vec2(m_target_x, m_target_y)),

     CallFuncN::create(CC_CALLBACK_1(InGameScene::fallingObjectDone, this)),

NULL);

      meteor->setVisible(true);

      meteor->runAction(repeatRotate);

      meteor->runAction(sequence);

      fallingObjects_.pushBack(meteor);

}

1.6 Thêm mới Hộp cứu thương rơi từ bầu trời


void InGameScene::addNewHealth()

{

      if (fallingObjects_.size() > 30) {

            return;

      }
      Sprite *health = (Sprite *)healthPool_.at(healthPoolIndex_);

      healthPoolIndex_ ++;

      if (healthPoolIndex_ == healthPool_.size()) {

            healthPoolIndex_ = 0;

      }



      int hx = rand() % (int)(screenSize_.width * 0.8f) + screenSize_.width * 0.1f;

      int hy = screenSize_.height + health->getBoundingBox().size.height * 0.5f;

      int h_target_x = rand() % (int)(screenSize_.width * 0.8f) + screenSize_.width * 0.1f;

      int h_target_y = screenSize_.height * 0.15f;

      health->stopAllActions();

      health->setPosition(Vec2(hx, hy));

      ActionInterval *rotate = RotateBy::create(0.8f, 90);

   ActionInterval *seqRotate = Sequence::create(rotate, rotate->reverse(), NULL);

      Action *repeatRotate = RepeatForever::create(seqRotate);

 FiniteTimeAction *sequence = Sequence::create(MoveTo::create(meteorSpeed_, Vec2(h_target_x, h_target_y)),

CallFuncN::create(CC_CALLBACK_1(InGameScene::fallingObjectDone, this)),

NULL);

      health->setVisible(true);

      health->runAction(repeatRotate);

      health->runAction(sequence);

      fallingObjects_.pushBack(health);

}

1.7 Kiểm tra game over

Khi một đối tượng rơi từ bầu trời xuống đến mặt đất, có thể sẽ là hộp cứu thương hoặc là thiên thạch, do đó chúng ta sẽ kiểm tra tăng lượng “máu” hoặc giảm lượng “máu” của thành phố. Khi “máu” về 0 sẽ chuyển sang trạng thái gameover.


void InGameScene::fallingObjectDone(Node *sender)

{

      Sprite *sprite = (Sprite *)sender;

      fallingObjects_.eraseObject(sprite);

      sprite->stopAllActions();

      sprite->setRotation(0);

      if (sprite->getTag() == TAG_METEOR) {

            cityHealth_ -= 15;

            sprite->runAction(groundHitAction_->clone());

            AudioUtils::play(Bomb);

      } else {

            sprite->setVisible(false);

            if (cityHealth_ == 100) {

                  gameScore_ += 25;

                  std::string s = std::to_string(gameScore_);

                  lblScore_->setString(s);

            } else {

                  cityHealth_ += 10;

                  if (cityHealth_ > 100) {

                        cityHealth_ = 100;

                  }

            }

            AudioUtils::play(Health);

      }



      if (cityHealth_ <= 0) {

            cityHealth_ = 0;

            this->stopGame();

            AudioUtils::play(FireTruck);

            gameOverMessage_->setVisible(true);

      }



      std::string s = std::to_string(cityHealth_) + "%";

      lblHealth_->setString(s);

}


1.8 Tăng độ khó của game


Khi thời gian chơi kéo dài, chúng ta sẽ tăng độ khó của game bằng cách rút ngắn thời gian thêm thiên thạch, tăng tốc độ rơi thiên thạch, giảm tốc độ rơi của hộp cứu thương.


void InGameScene::increaseDifficulty()

{

      meteorInterval_ -= 0.2f;

      if (meteorInterval_ < 0.25f) {

            meteorInterval_ = 0.25f;

      }


      meteorSpeed_ -= 1;

      if (meteorSpeed_ < 4) {

            meteorSpeed_ = 4;

      }

      healthSpeed_ -= 1;

      if (healthSpeed_ < 8) {

            healthSpeed_ = 8;

      }

}


1.9 Vòng lặp Game


void InGameScene::update(float fDelta)

{

      if (!isRunning_) {

            return;

      }


      int i, count;

      Sprite *sprite;


      meteorTimer_ += fDelta;

      if (meteorTimer_ > meteorInterval_) {

            meteorTimer_ = 0;

            this->addNewMeteor();

      }


      healthTimer_ += fDelta;

      if (healthTimer_ > healthInterval_) {

            healthTimer_ = 0;

            this->addNewHealth();

      }


      difficultyTimer_ += fDelta;

      if (difficultyTimer_ > difficultyInterval_) {

            difficultyTimer_ = 0;

            this->increaseDifficulty();

      }


      if (bomb_->isVisible()) {

            if (bomb_->getOpacity() < 255 && bomb_->getScale() > 0.3f) {

                  bomb_->setOpacity(255);

            }

      }



      //check collision with shockwave

      if (shockWave_->isVisible()) {

            count = fallingObjects_.size();

            float diffX, diffY;

            for (i = count - 1; i >= 0; i--) {

                  sprite = fallingObjects_.at(i);

                  diffX = shockWave_->getPositionX() - sprite->getPositionX();

                  diffY = shockWave_->getPositionY() - sprite->getPositionY();

                  Vec2 distance = Vec2(diffX, diffY);

            if (distance.length() <= shockWave_->getBoundingBox().size.width * 0.5) {

                        sprite->stopAllActions();

                        sprite->runAction(explosionAction_->clone());

                        AudioUtils::play(Bomb);

                        if (sprite->getTag() == TAG_METEOR) {

                              shockWaveHits_ ++;

                              gameScore_ += (shockWaveHits_ * 15);

                        }

                        fallingObjects_.eraseObject(sprite);

                 }

            }



            std::string s = std::to_string(gameScore_);

            lblScore_->setString(s);

      }



      //move clouds

      count = clouds_.size();

      for (i = 0; i < count; i ++) {

            sprite = clouds_.at(i);

            sprite->setPositionX(sprite->getPositionX() + fDelta * 20);

if (sprite->getPositionX() > screenSize_.width + sprite->getBoundingBox().size.width * 0.5f) {

            sprite->setPositionX(-sprite->getBoundingBox().size.width * 0.5f);

            }

      }

}


1.10 Hàm hủy


Giải phóng các Action mà chúng ta đã retain.


InGameScene::~InGameScene()

{

      CC_SAFE_RELEASE(swingAction_);

      CC_SAFE_RELEASE(shockwaveAction_);

      CC_SAFE_RELEASE(growBombAction_);

      CC_SAFE_RELEASE(rotateAction_);

      CC_SAFE_RELEASE(groundHitAction_);

      CC_SAFE_RELEASE(explosionAction_);

}


Sau đó Build, Run và Test game.



Download mã nguồn và Resource của SkyDefense


No comments:

Post a Comment