Bài 30: Học làm game thứ 5 - Space Ship ( Part 2 - End )

Người đăng: share-nhungdieuhay on Thứ Tư, 6 tháng 8, 2014


Hi, Rảnh rỗi tranh thủ viết cho xong game Space Ship này.
Bài trước chúng ta đã thiết kế sơ bộ xong phần màn chơi, song còn thiếu một số phần quan trọng trong game nên có mà chúng ta sẽ bổ sung ngay sau đây:

+ Bắn đạn khi Touch màn hình
+ Bắt sự kiện va chạm giữa đạn và thiên thạch
+ Tính điểm
+ Game Over

Đơn giản có thế thôi, chúng ta sẽ lướt nhanh!

B1: Bắn đạn khi Touch màn hình

Bạn mở file HelloWorldScene.h thêm vào dòng lệnh sau, trong public

// Hàm bắt sự kiện touch, dùng multiTouch, hoặc Touch thôi cũng được
void HelloWorld::onTouchesBegan(const std::vector<Touch*>& touches, Event *event)

Tiếp đó trong HelloWorldScene.cpp ta thiết kế hàm này như sau

void HelloWorld::onTouchesBegan(const std::vector<Touch*>& touches, Event *event)
{
SimpleAudioEngine::getInstance()->playEffect("laser_ship.wav"); // Âm thanh

Size winSize = Director::getInstance()->getWinSize();

// Lấy sprite Laser từ bộ lưu trữ Vector
Sprite *shipLaser = (Sprite *) _shipLasers->at(_nextShipLaser++);

if ( _nextShipLaser >=_shipLasers->size())   // Reset index laser
_nextShipLaser = 0;
// Đặt vị trí ở phía mũi tàu, và cho hiện lên
shipLaser->setPosition(Point(_ship->getPosition().x + shipLaser->getContentSize().width/2, _ship->getPosition().y));
shipLaser->setVisible(true);
// set body
auto laserbody = PhysicsBody::createBox(shipLaser->getContentSize()/2);  

laserbody->setContactTestBitmask(0xf);  
laserbody->setDynamic(true);
shipLaser->setPhysicsBody(laserbody);

// Di chuyển đạn, gọi tới hàm setInvisible để xử lý
shipLaser->stopAllActions();
shipLaser->runAction(Sequence::create( 
MoveBy::create(0.5,Point(winSize.width, 0)),
CallFuncN::create(this, callfuncN_selector(HelloWorld::setInvisible)), 
NULL 
));
}

B2: Bắt sự kiện va chạm

Thêm hàm sau vào file HelloWorldScene.h

bool onContactBegin(const PhysicsContact &contact);

Và xây dựng nó trong file HelloWorldScene.cpp như sau

bool HelloWorld::onContactBegin(const PhysicsContact& contact)    
{
auto laser = (Sprite*)contact.getShapeA()->getBody()->getNode();
int Tag1 = -1;
if(laser) 
Tag1 = laser->getTag();
auto asteroid = (Sprite*)contact.getShapeB()->getBody()->getNode();
int Tag2 = -1;
if(asteroid) Tag2 =  asteroid->getTag();

//Va chạm giữa đạn và Thiên Thạch
if((Tag1==KLASER&Tag2==KASTEROID)||(Tag2==KLASER&Tag1==KASTEROID))
{
SimpleAudioEngine::sharedEngine()->playEffect("explosion_large.wav"); 
_world->removeBody(laser->getPhysicsBody());
laser->setVisible(false);
_world->removeBody(asteroid->getPhysicsBody());
asteroid->setVisible(false); 
}
// Va chạm giữa thiên thạch và Ship
if((Tag1==KSHIP&Tag2==KASTEROID)||(Tag2==KSHIP&Tag1==KASTEROID))

{
_lives--;

}

return true; 
}

Và không được quên đoạn code Listener ở init()

auto contactListener = EventListenerPhysicsContact::create();
contactListener->onContactBegin = CC_CALLBACK_1(HelloWorld::onContactBegin, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(contactListener, this);

OK, giờ là tới phần Tính điểm và GameOver

B3: Tính điểm và GameOver

Trong hàm update() bạn thêm vào 1 đoạn sau đây

if (_lives <= 0) { // Kiểm tra không còn mạng nào thì game Over
_ship->stopAllActions();
_ship->setVisible(false);
_world->removeBody(_ship->getPhysicsBody());
this->endScene(KENDREASONLOSE);   // Game Over

Hàm endScene xây dựng như sau

void HelloWorld::endScene( EndReason endReason ) {

if (_gameOver) // trạng thái game
return;
_gameOver = true;

Size winSize = Director::getInstance()->getWinSize();

char message[10] = "";
if ( endReason == KENDREASONLOSE)
strcpy(message,"You Lose"); 

// Tạo 2 Label để làm thông báo
LabelBMFont * label ;
label = LabelBMFont::create(message, "Arial.fnt");
label->setScale(0.1);
label->setPosition(Point(winSize.width/2 , winSize.height*0.6));
this->addChild(label);


// Tạo 1 nút reset game là 1 label
LabelBMFont * restartLabel;
strcpy(message,"Restart");
restartLabel = LabelBMFont::create(message, "Arial.fnt");

MenuItemLabel *restartItem =  MenuItemLabel::create(restartLabel,CC_CALLBACK_1(HelloWorld::resetGame,this));

restartItem->setScale(0.1);
restartItem->setPosition( Point(winSize.width/2, winSize.height*0.4));

Menu *menu = Menu::create(restartItem, NULL);
menu->setPosition(Point::ZERO);
this->addChild(menu);

restartItem->runAction(ScaleTo::create(0.5, 1.0));
label ->runAction(ScaleTo::create(0.5, 1.0));
this->unscheduleUpdate(); // dừng update Scene
}

Và nhớ phải thêm thuộc tính bool _gameOver vào phần public của HelloWorldScene.h, đồng thời trong hàm init() phải khởi tạo nó với giá trị false

Bổ sung hàm endScene() và resetGame() vào trong lớp HelloWorld, và hàm resetGame như sau

void HelloWorld::resetGame(Ref* pSender) {
auto scene = HelloWorld::createScene();
Director::getInstance()->replaceScene(TransitionZoomFlipY::create(0.5, scene));  

Size winSize = Director::getInstance()->getVisibleSize();

}

Giờ ta thêm 1 chút phần tính điểm. Khi bắn mỗi thiên thạch ta được 10 điểm.

Bạn thêm 1 thuộc tính int score, LabelBMFont * _scoreDisplay;vào lớp HelloWorldScene, và khi khởi tạo thêm đoạn code này

_scoreDisplay = LabelBMFont::create("Score: 0", "Arial.fnt", 
visibaleSize.width * 0.3f);
_scoreDisplay->setAnchorPoint(Point(1, 0.5));
_scoreDisplay->setPosition(
Point(visibaleSize.width * 0.8f, visibaleSize.height * 0.94f));
this->addChild(_scoreDisplay);

Trong hàm kiểm tra va chạm chúng ta sẽ tính điểm bằng đoạn code nhỏ như thế này

score+=10;
char szValue[100] = { 0 }; // Lấy ra điểm qua mảng đệm char
sprintf(szValue, "Score: %i", score); // Chuyển sang chuỗi => chuỗi
_scoreDisplay->setString(szValue); // Hiện điểm lên

Bạn có thể làm thế với Live để theo dõi số mạng của Ship

OK, Build thử xem kết quả thế nào nhé, cũng không tệ với 1 game "tự tui".

Và sau đây mình làm thêm 1 bước Bonus nữa là 

Bonus: Điều khiển Ship bằng Accelerometer - gia tốc kế

Trước hết bạn copy 2 file VisibleRect.h, .cpp trong bài cpp-tests vào Class của chúng ta. sau đó trong phần init() thêm đoạn code này vào

#define FIX_POS(_pos, _min, _max) \
if (_pos < _min)        \
_pos = _min;        \
else if (_pos > _max)   \
_pos = _max; 

auto listener = EventListenerAcceleration::create([=](Acceleration* acc, Event* event){
auto shipSize  = _ship->getContentSize();

auto ptNow  = _ship->getPosition();

log("acc: x = %lf, y = %lf", acc->x, acc->y);

ptNow.x += acc->x * 9.81f;
ptNow.y += acc->y * 9.81f;

FIX_POS(ptNow.x, (VisibleRect::left().x+shipSize.width / 2.0), (VisibleRect::right().x - shipSize.width / 2.0));
FIX_POS(ptNow.y, (VisibleRect::bottom().y+shipSize.height / 2.0), (VisibleRect::top().y - shipSize.height / 2.0));
_ship->setPosition(ptNow);
});

auto dispathcher = Director::getInstance()->getEventDispatcher();

dispathcher->addEventListenerWithSceneGraphPriority(listener, this);

Vậy thôi, hãy build lại và thử trên ĐT thật, khi nghiêng xem Ship có di chuyển không nhé, nếu di chuyển là đã thành công

Kết thúc bài này, chúng ta cùng nghiên cứu 1 số vấn đề sau

+ Bắn đạn = Touche, duyệt vector
+ Va chạm
+ Tính điểm, game Over
+ Di chuyển Ship bằng gia tốc kế

Download Code

Mình dừng bài học ở đây nhé

Bài 31: Làm game gì bây giờ?

{ 0 nhận xét... read them below or add one }

Đăng nhận xét