Xây dựng game đơn giản với cocos2dx

Mở đầu

Trong bài này tôi sẽ hướng dẫn các bạn cách sử dụng cocos2dx và thư viện PhysicsWorld để tạo 1 game đơn giản bắt các sự kiện va chạm và âm thanh.

Cài đặt

  1. Download thư viện cocos2dx tại đây
  2. Tạo project mới với cocos2dx$ cd cocos2d-x
$ ./setup.py
$ source ~/.bash_profile # may be ~/.bash_login or ~/.profile, depends on your environemnt
$ cocos new AndroidKidGame -p com.FG.AndroidKidGame -l cpp -d ~/AndroidKidGame

trong đó AndroidKidGame là tên project. sau khi chạy thành công thư mục dự án sẽ có dạng như dưới đây:

Screen Shot 2016-05-05 at 12.45.15.png

Ý tưởng

Để sử dụng được các sự kiện va chạm vật lý ta sử dụng các số từ 1->10 để làm game cho trẻ từ 1-7 tuổi.Hàng trên là dãy số để nhận biết ,user sẽ dùng touch để di chuyển con số ở hàng dưới tương ứng với số ở hàng trên, khi sự kiện va chạm xảy r thì có hiệu ứng đổi màu,chơi nhạc và tăng điểm số tương ứng.

Screen Shot 2016-05-05 at 13.13.20.png

Bắt đầu

Mở thư mục AndroidKidGame.xcodeproj bằng xcode trong thư mục Class là những file sẽ chưa mã nguồn của game. Khi bắt đầu sẽ có 4 file:

  1. HelloWorldScene.cpp
  2. HelloWorldScene.h
  3. AppDelegate.cpp
  4. AppDelegate.h AppDelegate là file mà project sẽ khởi động đầu tiên để chạy method.
 AppDelegate::applicationDidFinishLaunching()

trong cocos game được chia ra làm các Scene (có thể hiểu là 1 màn hình) trong Scene sẽ chưa các Layer các Layer có thể đặt chồng lên nhau trong 1 scene. Trong Layer sẽ chứa các child như : background, button, image, text....

    // initialize director
    auto director = Director::getInstance();
    // create a scene. it's an autorelease object
    auto scene = HelloWorld::createScene();
    // run
    director->runWithScene(scene);

Director trong cocos dùng để quản lý tất cả các Scene.Director có thể runScene, replaceScene, pushScene, popScene... cụ thể có thể tham khảo ở đây từ cocos3.x hỗ trợ từ khóa auto để tự động giải phóng bộ nhớ của các đối tượng khi không còn cần dùng nữa thay cho developer phải giải phóng bằng tay như trước đây.

HelloWorldScene

Đây là Scene khởi động của game, thực hiện implement các thành phần cần thiết cho game ở file .h

  Sprite* snumber;    //number to move
    Sprite* anumber;    //static number 1
    Sprite* bnumber;    //statuc number 2
    Sprite* cnumber;    //static number 3

khai báo các method sử dụng khi user tương tác.

    virtual bool onTouchBegan(cocos2d::Touch* touches,cocos2d::Event* event);
    virtual void onTouchMoved(cocos2d::Touch* touches,cocos2d::Event* event);
    virtual void onTouchEnded(cocos2d::Touch* touches,cocos2d::Event* event);

khai báo các hàm logic cần có trong quá trình chơi game

 bool onContactBegin(const PhysicsContact& contact); // xử lý khi va chạm
 virtual void addhoahong();   // add 1 bông hoa lên màn hình khi tăng điểm
 void gameLogic(float dt); // xử lý logic khi có va chạm và tiếp tục generate number mới để chơi tiếp
void numbers();      // create new number and add them to sence std::string generate_background(); //chuyển đổi background sau 1 thời gian

Chúng ta còn sử dụng thêm thư viện

 #include "SimpleAudioEngine.h"

để play file mp3 khi User tăng điểm.

Logic trong game

Khi GameDirector chạy Scene Hello thì trong file HelloWorldScene.cpp sẽ chạy vào method HelloWorld::createScene đầu tiên.Những dòng dưới đây tạo ra 1 môi trường Physic với trọng lực = 0 để các Sprite không bị rơi xuống đáy màn hình.

auto scene = Scene::createWithPhysics();
scene->getPhysicsWorld()->setGravity(Vect(0.0f,0.0f));               // trong luc = 0
auto layer = HelloWorld::create();
scene->addChild(layer);
return scene;

Cài đặt listener cho các sự kiện

Trong game này có 2 sự kiện đó là:

  1. User touch và di chuyển số
  2. Khi số va chạm với nhau, có 2 trường hợp là cùng số và khác số. sẽ tương ứng với 2 listener dưới đây.
auto listener1 = EventListenerPhysicsContact::create();
auto listener3 = EventListenerTouchOneByOne::create();

các listener sẽ được quản lý bằng EventDispatcher gắn với 1 scene cố định, thực hiện khai báo EventDispatcher.

auto _dispatcher = Director::getInstance()->getEventDispatcher();
_dispatcher->addEventListenerWithSceneGraphPriority(listener1, this);       // khi va chạm
_dispatcher->addEventListenerWithSceneGraphPriority(listener3, this);       // khi touch

Các method generate trong quá trình chơi game

  1. HelloWorld::generate_name : tạo random 1 số từ 1->9 generate ra tên ảnh tương ứng của số đó.
  2. HelloWorld::numbers : tạo ra 1 lượt chơi gồm 3 số đặt ở những vị trí cố định trên màn hình và 1 số đáp án.
  3. HelloWorld::generate_background : tạo ra background cho các lần chơi. vì các phần trên khá tỉ mỉ và phần source dài nên vui lòng tham khảo trong source mình share phía dưới.

Tạo physic body cho các Sprite

để thực hiện được va chạm các Sprite (object chứa ảnh trong game) cần phải có physic body bao quanh nó.Khi các vùng physic này chạm vào nhau thì sẽ phát sinh sự kiện onContactBegin().Dưới đây là đoạn ví dụ mình tạo physic body cho 1 số để chuẩn bị cho sự kiện va chạm.

     std::string s_name1 = this->generate_name();
    auto number1 = Sprite::create("a" + s_name1);
    auto body1 = PhysicsBody::createCircle(number1->getContentSize().width*2);
    body1->setContactTestBitmask(0x1);
    number1->setScale(2.0f, 2.0f);
    number1->setPhysicsBody(body1);
    number1->setTag(1);

Cần chú ý method setContactTestBitmask(0x1); là quan trọng nếu không có method này cho Physic body thì method onContactBegin() không có tác dụng với Physic body đó.Mình sử dụng các Sprite có hình tròn nên dùng createCircle, các bạn có thể sử dụng các option khác cho phù hợp với Sprite của mình. Screen Shot 2016-05-05 at 14.24.42.png Để nhận biết được đối tượng nào trong cocos2dx thường xuyên sử dụng setTag(int) để khi 2 đối tượng va chạm muốn biết rõ đối tượng nào cần xóa đi chỉ cần getTag().

Sự kiện va chạm

bool HelloWorld::onContactBegin(const PhysicsContact &contact){
    auto _obj1 = (Sprite*)contact.getShapeA()->getBody()->getNode();
    auto _obj2 = (Sprite*)contact.getShapeB()->getBody()->getNode();
    if(_obj1 == NULL || _obj2 == NULL)
        return false;
        }

2 dòng đầu giúp ta lấy được 2 đối tượng ứng với 2 Physic body đang va chạm nhau.Sau đó dựa vào các Tag sẽ biết được 2 Physicbody va chạm nhau có cùng giá trị số không, nếu đúng thì remove cả 2 và thay bằng 1 Sprite mới cùng giá trị nhưng khác màu, thực hiện cộng điểm, và chơi nhạc.Nếu không đúng thì không làm gì cả.

// Kiểm tra va chạm với Tag
bool isTrueContact = false;
if(_obj1->getTag() == 1 && _obj2->getTag() == 4){
 Point x = _obj1->getPosition();  //lấy lại vị trí của obj bị va chạm
 this->isdeleted = 1;         // loại bỏ các Sprite cũ để chuẩn bị cho màn chơi mới.
this->removeChild(snumber);
this->removeChild(anumber);
this->removeChild(bnumber);
this->removeChild(cnumber);

chơi nhạc trong game

  1. Đối với nhạc nền của game sẽ play ngay khi tạo ra Scene để bắt đầu chơi nên trong method HelloWorld::createScene :
 CocosDenshion::SimpleAudioEngine::sharedEngine()->playBackgroundMusic("backgroundforkid.mp3", true);

với tham số true file mp3 này sẽ được chơi lặp lại vô tận cho đến khi Scene này bị hủy. 2. Đối với âm thanh sự kiện như khi 2 vật thể chạm nhau chính xác thì đặt ngay tại vị trí sau khi kiểm tra sự kiện va chạm.

 CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect("tiengvotay2s.mp3");

Một số chú ý

  1. Trong một số trường hợp khi chạy trên máy ảo sẽ gặp lỗi "LLVM 6 errors linking OpenSSL" hãy add đoạn code sau vào AppDelegate.cpp
extern "C"
{
    char* strerror$UNIX2003( int errnum )
    {
        return strerror(errnum);
    }
    FILE *fopen$UNIX2003( const char *filename, const char *mode )
    {
        return fopen(filename, mode);
    }
    size_t fwrite$UNIX2003( const void *a, size_t b, size_t c, FILE *d )
    {
        return fwrite(a, b, c, d);
    }
    DIR *opendir$INODE64(const char * a)
    {
        return opendir(a);
    }
    struct dirent *readdir$INODE64(DIR *dir)
    {
        return readdir(dir);
    }
}
  1. Khi play nhạc trong game. Thông thường SimpleAudioEngine sẽ mất 1 khoảng thời gian để load file nhạc vào bộ nhớ đệm, tùy theo mức độ dài ngắn mà sẽ nhanh hay lâu vì thế có thể gây delay khi chơi.Tốt nhất là nên load sẵn trước khi dùng.
 CocosDenshion::SimpleAudioEngine::sharedEngine()->preloadEffect("file.mp3");
  1. Các phần xử lý chi tiết. Các phần chi tiết liên quan đến nội dung game,chơi nhạc các bạn vui lòng tham khảo trong source code ví dụ cách đặt tên image ,các asset dùng trong game, các trường hợp xử lý va chạm cụ thể...
  2. All source và Asset được đặt ở đây

Kết luận

Đây là một game đơn giản tích hợp sử dụng thư viện CCPhysic, trong game chỉ sử dụng cho các va chạm đơn giản nhưng bạn có thể tạo trọng lực và thử nghiệm với các va chạm hình khối khác nhau phức tạp hơn. Trong bài mình chỉ trình bày những phần cơ bản nhất để hướng dẫn phương pháp làm, những chi tiết khác vui lòng tham khảo source. Chúc các bạn có thể tự làm được game hay và giá trị.