Saturday, July 5, 2014

Bài 27: Học làm game thứ 4 - Game đập chuột ( Part 2)

Hi các bạn!

Mấy ngày xem bóng hoa mắt quá đi, cũng làm tí cá độ cho vui, rất may là chưa có cái gì ra đi cả hehe. Nay trở lại hoàn thành nốt phần 2 game đập chuột này nhé ( Máy tính vẫn còn mà ).

Nhiệm vụ chính trong phần 2 này bao gồm các công việc như sau:
+ Đập chuột.
+ Tính điểm + Game Over
+ Add âm thanh.
+ Làm một chút hiệu ứng animation.

Đi luôn nhé!

Bạn mở file HelloWorldScene.h ra, thêm vào đoạn code sau vào phần public

// Tạo 1 Animation với các thông số, Tên ảnh, thứ tự hình ảnh, số khung, trễ
Animation* createAnimation(string prefixName, int* pFramesOrder, int framesNum, float delay);
bool onTouchBegan(Touch *touch, Event *unused_event); // Sự kiện Touch
// Mở , khóa chế độ đập chuột
void unsetTappable(Object* pSender);
void setTappable(Object* pSender);

// Cười + Đánh chuột
Animation *laughAnim;
Animation *hitAnim;
// Label dùng để hiển thị điểm
LabelTTF *label;

// Điểm số
int score;
// Số chuột không bị đập để tính game over
int totalSpawns;
bool gameOver;

nhớ thêm using namespace std; ở đầu file nhé ( vì dùng string )

Tiếp theo mở file HelloWorldScene.cpp ra

Include thêm #include "SimpleAudioEngine.h", và using namespace std;


Trong hàm init() thêm vào đoạn code sau ( trước return true )

auto visibleSize = Director::getInstance()->getWinSize();
auto visibleOrigin = Director::getInstance()->getVisibleOrigin();

//Tạo Animation chuột cười + Hit lên đầu chuột
int laughAnimOrder[6] = { 1, 2, 3, 2, 3, 1 }; // thứ tự các hình ảnh duyệt trong spritesheet
laughAnim = this->createAnimation("mole_laugh", laughAnimOrder, 6, 0.1f);
int hitAnimOrder[6] = { 1, 2, 3, 4 }; // Tương tụ trên
hitAnim = this->createAnimation("mole_thump", hitAnimOrder, 4, 0.02f);
// Nạp vào bộ đệm animation và đặt tên
AnimationCache::getInstance()->addAnimation(laughAnim, "laughAnim");
AnimationCache::getInstance()->addAnimation(hitAnim, "hitAnim");

//Touch
auto listener = EventListenerTouchOneByOne::create();
listener->onTouchBegan = CC_CALLBACK_2(HelloWorld::onTouchBegan, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);

//Tạo 1 Label hiển thị điểm số
float margin = 10;
label = LabelTTF::create("Score: 0", "fonts/Marker Felt.ttf", 14);
label->setAnchorPoint(Point(1, 0));
label->setPosition(visibleOrigin.x + visibleSize.width - margin, 
visibleOrigin.y + margin);
this->addChild(label, 10);

//Khởi tạo các giá trị : điểm, tổng số chuột, game over
gameOver = false;
totalSpawns = 0;
score = 0;

// Nạp trước Âm thanh, play nhạc nền
CocosDenshion::SimpleAudioEngine::getInstance()->preloadEffect("laugh.mp3");
CocosDenshion::SimpleAudioEngine::getInstance()->preloadEffect("ow.mp3");
CocosDenshion::SimpleAudioEngine::getInstance()->playBackgroundMusic("whack.mp3", true);

Hàm Touch

bool HelloWorld::onTouchBegan(Touch *touch, Event *unused_event)
{
// Lấy tọa độ điểm Touch
Point touchLocation = this->convertTouchToNodeSpace(touch);
// Duyệt vector molesVector
for (Sprite *mole : molesVector) 
{
if (0 == mole->getTag()) continue; // bỏ qua, do đã ko được phép đập
// Lấy đường bao của Sprite ( dạng Rectang ), nếu chứa điểm Touch - tức là đập trúng chuột thì
if ( mole->getBoundingBox().containsPoint(touchLocation) ) 
{
// Âm thanh
CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("ow.mp3");
// Cộng điểm và gán tag - ko đập thêm được
mole->setTag(0);
score += 10;

mole->stopAllActions(); // Dừng tất cả Action
// Tạo hitAnimation từ Cache
Animate *hit = Animate::create(hitAnim);
// Thụt xuống + biến mất
MoveBy *moveDown = MoveBy::create(0.2f, Point(0, -mole->getContentSize().height));
EaseInOut *easeMoveDown = EaseInOut::create(moveDown, 3.0f);
// Thực hiện 2 Animation tuần tự, bị hit và thụt xuống
mole->runAction(Sequence::create(hit, easeMoveDown, NULL));
}
}

return true;
}


Hàm createAnimation

Animation* HelloWorld::createAnimation(string prefixName, 
  int* pFramesOrder, 
  int framesNum, 
  float delay)
{
// Hãy xem lại bài 19 về cách làm animation từ spritesheet, có 5 bước nhỏ như sau
Vector<SpriteFrame*> animFrames; //1
// Tạo frame
for (int i = 0; i < framesNum; i++) //2
{
char buffer[20] = { 0 }; //3
sprintf(buffer, "%d.png", pFramesOrder[i]); //3
string str = prefixName + buffer; //3 Chính là tên của ảnh trong spritesheet 
// tạo frame, add vào vector
auto frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(str); //4
animFrames.pushBack(frame); //4
}
// Trả về 1 animation
return Animation::createWithSpriteFrames(animFrames, delay); //5
}


Hàm tryPopMoles, sửa lại 1 chút, hàm này là hàm update Scene theo thời gian, mình sẽ làm các việc kiểm tra gameOver, tính điểm trong này, vì các sự kiện này luôn phụ thuộc thời gian mà.

void HelloWorld::tryPopMoles(float dt)
{

if (gameOver) return; // Game over rồi thì thôi

// Hiển thị điểm = Label, tất nhiên có cách hiển thị điểm = font, text, mình ko xét ở đây
char scoreStr[30] = { 0 };
sprintf(scoreStr, "Score: %d", score);
label->setString(scoreStr);

// Xuất hiện đủ 50 chú chuột thì kết thúc game
if (totalSpawns >= 50) {
Size winSize = Director::getInstance()->getWinSize();
// Kết thúc game
LabelTTF *goLabel = LabelTTF::create("Game Over!", "fonts/Marker Felt.ttf", 48.0f);
goLabel->setPosition(Point(winSize.width / 2, winSize.height / 2));
goLabel->setScale(0.1f);
this->addChild(goLabel, 10);
goLabel->runAction(ScaleTo::create(0.5f, 1.0f)); // Tạo hoạt cảnh 1 tí cho đẹp

gameOver = true; // gán game Over = true để timeline sau ko hiện ra label kết thúc game nữa, bạn test = false xem sẽ thấy
return;
}
//-----------
// Code vòng for ở bài trước vẫn thế nhé vẫn thế nhé

}

2 Hàm khóa và mở khóa trạng thái " được đập" và "ko được đập "

void HelloWorld::setTappable(Object* pSender) // được phép đập
{
Sprite *mole = (Sprite *)pSender;
mole->setTag(1); // cho phép đập
//Play âm thanh, --- Cứ cười đi rồi ăn đập nhá ku!
CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("laugh.mp3");
}

void HelloWorld::unsetTappable(Object* pSender)
{
Sprite *mole = (Sprite *)pSender;
mole->setTag(0); // Ko cho phép đập
}

Giờ còn mỗi hàm PopMole này nữa là xong

void HelloWorld::popMole(Sprite *mole)
{
if (totalSpawns > 50) return; // Kết thúc khi đủ 50
totalSpawns++; // đếm số chuột thò lên

// Lệnh này ko rõ lắm, vì bỏ đi game vẫn chạy mole->setSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("mole_1.png"));

// Action của chuột, nhô lên, thụt xuống
auto moveUp = MoveBy::create(0.2f, Point(0, mole->getContentSize().height));  // 1
auto easeMoveUp = EaseInOut::create(moveUp, 3.0f); // 2
auto easeMoveDown = easeMoveUp->reverse(); // 3

// Aimaition Cười
Animate *laugh = Animate::create(laughAnim);

// Thực hiện tuần tự các Action: Thò lên, MỞ BỤP, Cười, KHÓA ĐẬP, Thụt xuống
mole->runAction(Sequence::create(easeMoveUp, 
CallFuncN::create(CC_CALLBACK_1(HelloWorld::setTappable, this)),
laugh,
CallFuncN::create(CC_CALLBACK_1(HelloWorld::unsetTappable, this)),
easeMoveDown, 
NULL)); // 5
}

Xong hết code rồi, BUILD & BỤP thôi anh em




  
OK. Vậy là chúng ta đã ngâm cứu xong 1 game nhẹ nhàng nữa rồi. Cũng không quá khó nhỉ, các bạn nhớ đọc lại code vài lần nhé để nhớ cho lâu.

Vì là bản game TUT nên vẫn còn một số hạn chế:

+ Không có búa đập cho đã tay
+ Đập vào đầu chuột hiệu ứng chưa đẹp, và rõ ràng lắm
+ Điểm số label, không đẹp bằng image
+ Hơi ít lỗ chuột, làm nhiều 1 chút là đập mỏi tay, hỏng màn hình như chơi
+ Thêm vài màn chơi nữa là đẹp
+ Thêm được phần Pause game cũng ko tồi

- Đó là một số phần mở rộng của game, các bạn có thể xây dựng thêm, và mở rộng tới đâu là tùy thuộc ý tưởng của mỗi người nữa. Mình chỉ tới đây thôi.

Chào và hẹn gặp lại các bạn ở những bài sau.

7 comments:

  1. This comment has been removed by a blog administrator.

    ReplyDelete
  2. Ko có download source hả bạn?

    ReplyDelete
  3. Assert failed: The object should not be nullptr
    Assertion failed: (object != nullptr), function pushBack, file /Users/NXT/Desktop/AllGameDemo/proj.ios_mac/../cocos2d/cocos/base/CCVector.h, line 271.
    mình đnag bị lỗi này sửa giùm mình với tks :)

    ReplyDelete
    Replies
    1. tôi cũng đang bị lỗi này. bạn sửa được chưa ?

      Delete
  4. cho mình hỏi EaseInOut có tác dụng như nào nhỉ ?

    ReplyDelete
  5. anh ơi cho em xin souce code part 2 với ạ

    ReplyDelete