Monday, May 19, 2014

Bài 14 - Tạo khung vật lý của đối tượng phức tạp bằng PhysicsBody Editor - Import vào Game (Part 2)

Hi mọi người!

Trong bài 13, mình đã giới thiệu qua với các bạn cách sử dụng phần mềm Physicsbody Editor để detect khung body physic từ 1 hình ảnh có dạng phức tạp. Phần mềm sẽ tạo ra 1 file .JSON để lưu thông tin về physics body đó. Công việc của chúng ta bây giờ là sử dụng file .JSON có sẵn để import vào game mà thôi.

Công việc trong phần này như sau:
+ Tìm hiểu một chút về cách phân tích file .JSON
+ Tạo ra physics body từ file JSON và import vào game.

Thế thôi nhỉ, chúng ta bắt đầu,

Đầu tiền, hãy down file nguồn ở đây nhé, file JSON mọi người tự tao như hướng dẫn ở bài trước nha. Mình chỉ up file Class lên thôi

B1 - Phân tích file .JSON

Bước này mình cũng nói luôn là chúng ta chỉ "Cưỡi ngựa xem hoa thôi nhé", vì mình thấy nó giống như là giải thích 1 số hàm sẵn có trong thư viện hàm vậy. Hãy mặc nhiên sử dụng có lẽ tốt hơn.. Thôi thì cứ ngó qua cho biết chút.

Mở file MyBodyParser.h , trong này khai báo 1 lớp MyBodyParser với các hàm cần thiết để phân tích file JSON, chủ yếu là mấy hàm đọc chuỗi, ví dụ
bool parseJsonFile(const std::string& pFile); // Đọc thông tin từ 1 file pFile

bool parse(unsigned char* buffer, long length); // Đọc thông tin vào 1 buffer có độ dài length

Và hàm quan trọng nhất, nên nhớ.

PhysicsBody* bodyFormJson(Node* pNode, const std::string& name); // Tạo ra 1 cái physic body và gắn cho Node nào đó ( thường là Sprite).

MyBodyParser.cpp sẽ định nghĩa các hàm ở trên nhé, chỗ này cũng khá phức tạp để giải thích ( trình cũng chưa tới nên cũng hơi bị khó hiểu ) Tạm thời chúng ta nhớ mấy hàm này về sau cứ thể sử dụng như 1 hàm trong thư viện có sẵn thôi. ( Tất nhiên là phải bê nguyên cả 2 file này vào Class của Project rồi)

B2 - Cách Import vào Game

Mở file HelloWorleScene.h nghiêng ngó chút
+ Trong này khai báo mấy hàm bắt sự kiện Touch
+ Khai báo 1 con trỏ Sprite*, 1 con trỏ LabelTTF*
+ Khai báo 1 hàm nodeUnderTouch(), để xác định vị trí Node khi Touch lên màn hình

Mở file HelloWorleScene.cpp
+ Phần Inlcude có #include "MyBodyParser.h"
+ Hãy chú ý từ phần này
sp_2dx = Sprite::create("2dx.png"); // Thêm vào 1 Sprite ảnh 
sau đó là lệnh setPosition quen thuộc nhé.

Và chúng ta tập trung chú ý vào đoạn lệnh quan trọng sau

//Import file bodies.JSON để phân tích
MyBodyParser::getInstance()->parseJsonFile("bodies.json");

// Tạo ra 1 physic body bằng lệnh bodyFormJson của lớp MyBodyParser, hàm này nhận 2 đối số, 1 là sprite ở trên, 2 là tên của project tạo bởi Physicbody Editor ( hãy nhớ lúc ta dùng phần mềm có tạo 1 project là "2dx")

auto _body = MyBodyParser::getInstance()->bodyFormJson(sp_2dx, "2dx");
// Kiểm tra có tồn tại không?
    if (_body != nullptr) {
        _body->setDynamic(false); // Thiết lập body tĩnh, khi vật bị va chạm sẽ đứng im không nhúc nhích gì hết
        _body->setCollisionBitmask(0x000000); // Thiết lập không va chạm vật khác
        sp_2dx->setPhysicsBody(_body); // Đặt body vào sprite, đã quen thuộc
    }

// Đặt Sprite vào layer của Scene
this->addChild(sp_2dx, 0);

Đoạn lệnh bên dưới là tạo Listener lắng nghe sự kiện Chạm màn hình, đã quá quen thuộc nhé, các bạn trở lại các bài 9-12 để nhớ lại.

Xong rồi các mem, với đoạn lệnh trên chúng ta đã biết cách đặt 1 khung physic body từ 1 file JSON vào đối tượng Sprite rồi nhé, quá đơn giản rồi ( Nếu ko xét lớp MyBodyParser). Tiếp theo chúng ta nâng cao hơn 1 chút là kiếm tra xem, có đúng là physic body áp vào có khớp với phần viền của sprite hay ko

+ Xét hàm Node* HelloWorld::nodeUnderTouch(cocos2d::Touch *touch)

   Node* node = nullptr;
    auto location = this->convertTouchToNodeSpace(touch); // Lấy vị trí
    auto scene = Director::getInstance()->getRunningScene(); // Lấy Scene đang chạy
    auto arr = scene->getPhysicsWorld()->getShapes(location); // Trả về 1 mảng <PhysicsShape*> của các hình khối vật lý có tọa độ là location, 

    for (auto& obj : arr) // Vòng lặp kiểm tra trong mảng đó có Node nào là Sprite sp_2dx không
    {
        // Nếu có
        if ( obj->getBody()->getNode() == sp_2dx)
        {
            node = obj->getBody()->getNode(); // Lưu lại node
            break; // Kết thúc vòng lặp luôn nếu tìm thấy
        }
    }
    return node; // Trả lại giá trị là node cho hàm

+ Xét hàm onTouchBegan()

auto current_node = nodeUnderTouch(touch); // Gọi hàm nodeUnderTouch để trà về 1 node

    // Nếu nốt đó đúng là sp_2dx hoặc ko là sp_2dx thì "kêu lên" như sau
    if (current_node == sp_2dx)
    {
        status_label->setColor(Color3B::GREEN); // đặt màu xanh
        status_label->setString("Ohoo, DUNG CHAM VAO EM!");
    }
    else
    {
        status_label->setColor(Color3B::RED);
        status_label->setString("Haha, RA NGOAI ROI ANH!");
    }

    return true; // phải trả về true

Còn hàm onTouchEnded, onTouchMoved thì đơn giản rồi, các bạn tự tìm hiểu nhé.

Bây giờ biên dịch và chạy thử xem thành quả nào



Kết thúc bài 14, chúng ta đã học được cách tạo physic body khá hay từ file JSON, và cách nạp nó vào game.

Ngoài cách import từ file JSON để tạo khung vật lý từ hình phức tạp, ta còn 1 cách khác là import từ file PLIST được tạo bởi phần mềm PHYSICS EDITOR, Hoặc Cocos Studio

Hẹn gặp lại các bạn ở bài sau!

Bài 15: Box2D - Một thư viện vật lý khác của Cocos2d-x (Part 1)

16 comments:

  1. Nếu mình áp dụng khung PhysicsBody cho các con quái của bài tập 10,11,12 thì khi bắn bị lỗi, getNode() của va chạm bằng null.

    1 cái là PhysicsBody::createCircle va chạm với 1 cái MyBodyParser::getInstance()->bodyFormJson

    Mình khác phục thế nào vậy bạn?

    ReplyDelete
    Replies
    1. Vẫn bình thường mà bạn

      Mình thay đoạn này

      auto targetBody = PhysicsBody::createCircle(target->getContentSize().width / 2);
      target->setTag(2);
      targetBody->setContactTestBitmask(0x1);
      target->setPhysicsBody(targetBody);

      Bằng đoạn này

      MyBodyParser::getInstance()->parseJsonFile("quai.json");

      auto _body = MyBodyParser::getInstance()->bodyFormJson(target, "quai");

      if (_body != nullptr) {

      _body->setContactTestBitmask(0x000001); // Lệnh setCollisionBitmask ko hoạt động
      target->setPhysicsBody(_body);
      target->setTag(2);
      }

      Đều là PhysicBody cả nên vẫn va chạm nhau bình thường, nhưng mình đang có một vấn đề về bộ nhớ, bắn một lúc tự Stop Working, có lẽ phải xử lý thêm. Nhìn chung là PhysicBody ok chạy tốt nhé. Bạn test lại xem

      Delete
    2. Đúng rồi, ý mình là cái stop working đó đấy. nó getNode() ra null.
      Dẫn tới stop working

      Delete
    3. getNode() ra null à, bạn debug bằng gì thế, mình dùng VS mà ko thấy?

      Delete
    4. dùng VS2013, lúc đạn va chạm với quái. callback va chạm.

      bool HelloWorld::onContactBegin(const PhysicsContact& contact)
      {
      //Lấy đối tượng va chạm thứ nhất, ép kiểu con trỏ Sprite*
      auto bullet = (Sprite*)contact.getShapeA()->getBody()->getNode(); //// Null ở đây
      //Lấy giá trị cờ để xét xem đối tượng nào ( đạn, quái, hay nhân vật)
      int tag = bullet->getTag();

      //Lấy đối tượng va chạm thứ hai, ép kiểu con trỏ Sprite*
      auto target = (Sprite*)contact.getShapeB()->getBody()->getNode();
      //Lấy giá trị cờ để xét xem đối tượng nào ( đạn, quái, hay nhân vật)
      int tag1 = target->getTag();
      //Nếu va chạm xảy ra giữa đạn và quái thì xử lý xóa cả đạn và quái khỏi Layer trong Scene ( biến mất khỏi màn)

      Delete
    5. auto bullet , auto target mình đặt cho dễ nhìn vậy thôi, chứ đã biết cái nào là bullet, cái nào là target đâu, vai trò như nhau. Trong VS nó chỉ ra là Null, hay bạn đoán là Null?

      Delete
    6. Nó chỉ ra null mà. do đó khi gọi bullet->getTag(); sẽ báo lỗi ngay, vì bullet null

      Đương nhiên bullet ở đây có thể là đạn hoặc cũng có thể là quái

      Delete
    7. uhm, chua fix được lỗi này, có khi phải tìm cách khác

      Delete
  2. bạn ơi sao vẽ lên cái hình lại có các đoạn thẳng xuyên qua thế nhỉ

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. Bạn ơi làm thế nào để ẩn khung physics body lại chỉ còn hình ảnh không thôi?

    ReplyDelete
  5. sp_2dx = Sprite::create("2dx.png");
    sp_2dx->setPosition(Point(visibleSize.width/2, visibleSize.height/2));
    MyBodyParser::getInstance()->parseJsonFile("image.json");
    auto _body = MyBodyParser::getInstance()->bodyFormJson(sp_2dx, "image");
    if (_body != nullptr) {
    _body->setDynamic(false);
    _body->setCollisionBitmask(0x000000);
    _body->setContactTestBitmask(0x1);
    sp_2dx->setPhysicsBody(_body); // set Body
    }
    else
    {
    PhysicsBody* physics = PhysicsBody::createCircle(sp_2dx->getContentSize().width/2);
    physics->setContactTestBitmask(0x1);
    physics->setDynamic(false);
    sp_2dx->setPhysicsBody(physics);
    }
    Bạn cho minh hỏi sao đoạn code này _body lại trả về nullptr, mình đã tạo file trong Re đầu đủ rồi

    ReplyDelete
  6. Admin ơi cho mình hỏi, mình tạo khung sau đó add vào thì khung vật lý không trùng với sprite, cách sửa lỗi như thế nào nhỉ?

    ReplyDelete