Bài 11: Làm game đầu tiên - Phát hiện va chạm vật lý ( Part 3 )

Người đăng: share-nhungdieuhay on Thứ Ba, 13 tháng 5, 2014

Hix!

Không ngờ ra 3 bài này nhanh thật! Mà các bài trong Blog toàn là viết ngẫu hứng, viết 1 lần không cần nháp. Chả bù ngày đi học viết thư cho gái nháp nát cả mấy tờ A4 rồi mới chép lại để gửi ( thảo nào giờ viết cũng có chút lên tay ) : )

Trong 2 phần trước, chúng ta đã xây dựng được 1 nửa project rồi đó, tuy nhiên có 1 phần quan trong nhất cần phải làm trong project này là: tiêu diệt lũ quái bằng những viên đạn bắn ra. Phải làm thế nào đây? Với các phiên bản Engine 1.x, 2.x việc xây dựng các va chạm vật lý giữa các đối tượng là "Hơi bị khoai" do việc tính toán tọa độ, mảng, rồi véc tơ đủ kiểu, rất đau đầu. Tuy nhiên giờ đây trong bản 3 này công việc đó lại rất là đơn giản với chỉ vài dòng lệnh, Nào chúng ta bắt đầu thôi.

Sơ lược công việc trong phần 3 này:
+ Thiết lập thuộc tính vật lý cho các đối tượng ( nhân vật, đạn và quái )
+ Bắt các sự kiện va chạm vật lý xảy ra.
+ Xây dựng hàm xử lý va chạm đó.
Thế thôi nhỉ.

Go Go Go!

B1 - Thiết lập thuộc tính vật lý cho các đối tượng

Các bạn mở file HelloWorldScene.cpp lên, làm các công việc đơn giản sau

Trong hàm createScene(), ta sửa lại lệnh
auto scene = Scene::createScene() thành
auto scene = Scene::createWithPhysics();
// Tạm hiểu là tạo ra 1 Scene - 1 thế giới thu nhỏ có các đăc tính vật lý trong có chứa các đối tượng vật lý.

Thêm 1 lệnh sau
// Lệnh debug này cho phép nhìn thấy các khung body vật lý áp dụng vào các đối tượng ( đường viền đỏ bao quanh đối tượng)
scene->getPhysicsWorld()->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL);
//Thiết lập gia tốc trọng lực bằng 0, để các đối tượng của chúng ta ko rơi xuống đáy màn hình
scene->getPhysicsWorld()->setGravity(Vect(0.0f,0.0f));

1/Nhân vật

Bạn tìm đến dòng this->addChild(player) trong hàm init()

Trên dòng lệnh đó bạn thêm vào đoạn sau

//Tạo 1 bộ khung body vật lý dạng hình tròn
auto playerBody= PhysicsBody::createCircle(player->getContentSize().width / 2);
//Đặt cờ = 1, để kiểm tra đối tượng khi va chạm sau này
player->setTag(1);
//Lệnh này ko hiểu lắm nhưng thực sự ko thể thiếu, bỏ đi sẽ ko có gì xuất hiện khi va chạm
playerBody->setContactTestBitmask(0x1);
//Đặt bộ khung vật lý vào nhân vật
player->setPhysicsBody(playerBody);

2/ Quái

Bạn tìm đến dòng this->addChild(target); trong hàm addTarget()

Trên dòng lệnh đó bạn thêm vào đoạn sau:
// Giải thích giống phần Nhân vật
auto targetBody = PhysicsBody::createCircle(target->getContentSize().width / 2);
target->setTag(2);
targetBody->setContactTestBitmask(0x1);
target->setPhysicsBody(targetBody);

3/ Viên đạn

Trong hàm onTouchEnded bạn tìm dòng projectile->setPosition( Point(20, winSize.height/2) );, 

Thêm vào bên dưới nó đoạn sau, cách giải thích như 2 phần trên

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

Vậy là đã xong việc xây dựng bộ khung vật lý cho các đối tượng

B2 - Bắt sự kiện va chạm giữa các đối tượng

Để bắt sự kiện va chạm này chúng ta chũng tạo ra 1 listener lắng nghe việc va chạm, rồi truyền tới hàm xử lý va chạm thông qua 1 bộ truyền tải, thể hiện bằng code như sau

Trước lệnh return true; của hàm init() thêm vào đoạn code dưới đây:

//Tạo đối tượng lắng nghe va chạm nếu xảy ra
auto contactListener = EventListenerPhysicsContact::create();
//Khi có va chạm sẽ gọi hàm onContactBegin để xử lý va chạm đó, chú ý dòng CC_CALLBACK_1, nhiều tại liệu là CC_CALLBACK_2 sẽ báo lỗi ko chạy
contactListener->onContactBegin = CC_CALLBACK_1(HelloWorld::onContactBegin, this);
//Bộ truyền tải kết nối với đối tượng bắt va chạm
_eventDispatcher->addEventListenerWithSceneGraphPriority(contactListener, this);

Các bạn chú ý biến _eventDispatcher, biến này có 2 điểm đặc biệt là
+ Chưa từng được khai báo ở đâu ( kể cả trong file tiêu đề HelloWorldScene.h) đáng lẽ phải báo lỗi chứ nhỉ??
+ Biến đó có 1 gạch ở phía đầu, nghĩa là sao??

Bạn hỏi tôi trả lời => Đó là 1 biến đặc biệt có sẵn thuộc 1 lớp ( hình như là lớp Dispatcher ) trong thư viện, nên việc lôi nó ra dùng nó là hoàn toàn bình thường. Còn nếu ko dùng thì bạn phải khai báo 1 thằng khác cũng có chức năng như thế. Chú ý các biến có gạch đầu nhé, thường là biến có sẵn của các lớp.

B3 - Hàm xử lý va chạm

Trong file HelloWorldScene.h thêm vào 1 nguyên mẫu hàm 

//Nhớ là phải khai báo chuẩn như thế này, ko được sai 1 dấu chấm phảy nào nhé ( vì khai báo hàm chồng lên hàm của lớp cha)
bool onContactBegin(const PhysicsContact& contact);

Sau đó ta định nghĩa hàm đó trong HelloWorldScene.cpp như sau

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();
//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)
if((tag==2&tag1==3)||(tag==3&tag1==2))
    {

this->removeChild(bullet,true); // Xóa đạn

this->removeChild(target,true); // Xóa quái
}
// Nếu va chạm xảy ra giữa quái và nhân vật thì NV lăn ra chết , rồi GameOver, rồi tính điểm, cái này để bài sau
if((tag==1&tag1==2)||(tag==2&tag1==1))
        {
// Xử lý GameOver
// Tính điểm
}
    return true; // Phải trả lại giá trị true
}

Vậy là xong, Hãy build, chạy thử và xem kết quả có như mong đợi không nhé.
Kết quả đê, Lỗi luôn mới kinh, mặc dù Code không sai 1 dấu phẩy nào, mình khằng định thế


Nói thêm ở đây, có lẽ mình nên kết luận là 1 Bug nhỏ của Engine. Vì về mặt code không hề lỗi, đã lục tung trên mạng cũng không thấy bài nào hướng dẫn sửa. Trước gặp bài physic này đúng là ức chế vãi. Nhưng cuối cùng cũng tìm được cách, đó là kiếm 1 bài tut Physics rồi so code, thì phát hiện ra file HelloWorldScene.h thiếu đúng 1 dòng lệnh:

USING_NS_CC; ( các bạn copy paste dưới dòng #include "cocos2d.h" trong file HelloWorldScene.h)

Ngay từ khi tạo NewProject thì file HelloWorldScene.h đã ko có dòng này, về sau các bạn tạo Project mới nhớ thêm vào kẻo không biết vì sao lỗi, và không biết sửa ở đâu nhé. và các file ( .h ) các bạn tạo mới cũng nên điền thêm vào nhé.

Kết quả sau khi thêm dòng trên vào, là đây



Kết thúc bài 11 ở đây: Và chúng ta đã biết thêm cách:

+ Tạo 1 Scene chứa các đối tượng có thể tương tác vật lý
+ Tạo khung body vật lý cho các đối tượng
+ Bắt sự kiện va chạm bằng Listener Physics
+ Xây dựng hàm xử lý va chạm onContactBegin

Download file nguồn cho bác nào lười, trong này đã full comment của dòng lệnh

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


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

Đăng nhận xét