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