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ế
Mình dừng bài học ở đây nhé
Bài 31: Làm game gì bây giờ?