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

Người đăng: vjnhhoa on Chủ Nhật, 18 tháng 5, 2014

Hi cả nhà!
Trong các bài trước, mình đã giới thiệu với mọi người về các physics cơ bản của Cocos2d-x 3.x, và các physics này mặc định sử dụng thư viện của Cocos2d-x 3.x đơn giản, đễ hiểu, gần gũi và dễ dùng. Hệ thống physics này được phát triển trên nền tảng của hệ thống physics Chipmunk ( bạn search GG nhé )

Ngoài ra bạn có thể sử dụng Chipmunk một cách độc lập với hệ thống Physics của cocos2d-x 3.x ( tất nhiên là nếu bạn đã quen sử dụng cú pháp, hàm của Chipmunk rồi ). Và ngoài Chipmunk bạn còn có 1 thư viện Vật lý khác cũng rất phổ biến là Box2D. Nếu để so sánh thì thật khó, vì mỗi thằng có 1 lợi điểm riêng, thôi thì 50 - 50 cho lành. Chừng nào lên Pro rồi thì so sánh ko muộn

Thể nào cũng có bạn thắc mắc, sao mà lắm Physic thế, sao ko phải là cái khác đi. Hix, thật không biết phải trả lời sao. Theo mình thì Physic là 1 phần cơ bản và quan trọng của game, có lẽ nó xếp đằng sau ý tưởng + thuật toán - tính toán để giải quyết vấn đề, rồi cuối cùng là các phần mắm muối như âm thanh, hiệu ứng, v.v... Cứ nên tìm hiểu đủ 2 thư viện Box2D và Chipmunk kỹ vào, ko thừa chút nào. 3 Thư viện vật lý này chỉ khác nhau về tập lệnh, còn lại thì khá giống nhau trong cách mô phỏng thế giới vật lý thật. 

Sau đây, mình sẽ giới thiệu với các bạn cách để sử dụng Box2D trong Cocos2d-x. Mặc định Cocos2d-x cài đặt cho người dùng sử dụng thư viện của Cocos2d-x + Chipmunk, nếu muốn sử dụng Box2D bạn phải làm một số công việc cụ thể để import nó vào trình biên dịch.

Nội dung chủ yếu của phần này là:
+ Cách thiết lập để sử dụng Box2D
+ Một bài tập physic nhỏ, áp dụng Box2D physic

Bắt đầu chém nào!

B1 - Thiết lập sử dụng Box2D

Vì mặc định Cocos2d-x bắt chúng ta sử dụng Chipmunk, nên chúng ta phải làm 1 số việc sau:
1/ Mở file CMakeLists.txt trong Project của chúng ta ( dùng NotePad++ nhé để nó hiện số dòng ). Tìm đến dòng 162, rồi thêm vào "Box2D" như hình đưới

(Ban đầu)


(Thêm vào box2d)

2/ Mở file physics.sln ( physics là tên Project của mình ) bằng VS2012 theo đường dẫn sau physics\proj.win32\physics.sln, 

- Để ý phía bên trái, chỉ có 3 thư viện cơ bản được IMPORT sẵn, Audio, Chipmunk, Cocos2d


Hãy làm theo các bước sau

*  FILE -> Add -> Existing Project


Bạn để ý đường dẫn bên dưới khi chọn Box2D project ( nó nằm trong đường dẫn physics\cocos2d\external\Box2D\proj.win32\Box2D.vcxproj )


Kết quả đây, đã import vào rồi, nhưng bạn chưa thể dùng ngay đâu, phải làm thêm 2 bước nữa



* Chuột phải vào Project ( physics) chọn Project Dependencies, tích vào Box2D




* Chuột phải vào Project physics chọn References, hiện lên bảng project Properties Page, Click tiếp vào nút Add New References, và tick vào ô Box2D, rồi OK là xong



* Cuối cùng bạn phải SAVE cái Solution này lại = Ctrl + S nhé, rồi kiểm tra bước cuối cùng Mở file physics.vcxproj theo đường dẫn sau physics\proj.win32\physics.vcxproj

Nếu thấy dòng sau thì nghĩa là Box2D đã được nạp vào Project Physics, bạn search "Box2D"


Nếu search không thấy Box2D trong file này nghĩa là bạn import không đúng ( do chưa SAVE solution ở trong VS2012 chẳng hạn ). Hãy làm lại các bước * ở trên là OK.

Sẽ có bạn thắc mắc là sao ko mở trực tiếp file này rồi add dòng trên vào, Bạn để ý thấy là có 1 dòng <Project>xyz ở dưới là 1 đoạn mã, mình cũng ko biết lấy ở đâu ra, chắc do VS quy định, nên khá khó tìm thông số này ở đâu. Thống nhất làm theo cách trên nhé, làm mấy lần quen ấy mà

Khi cần import 1 Thư viện nào đó, bạn cũng làm theo cách trên nhé.

B2 - 1 Bài physic nhỏ nhỏ thực hành

Tạo 1 Project mới mang tên Box2Dtest, ( bạn không nên đặt project là Box2D nhé, vì sẽ không import Box2D vào project = VS được)

>cocos new Box2Dtest -p com.vn.box2dtest -l cpp -d f:android/project

Mở file HelloWorldScene.h lên, làm những việc sau
+ Thêm vào #include "Box2D/Box2D.h" tại phần #include
+ Thêm USING_NS_CC;
+ Thêm đoạn code sau vào phần Public:

b2World *world; // World với physic
b2Body *ballBody ; // Body của bóng
b2BodyDef bodyDef; // Định nghĩa cái Body trên
b2FixtureDef fixtureDef; // Định nghĩa một số thuộc tính tĩnh: ma sát, độ đàn hồi, trọng lượng,v.v.
b2CircleShape bodyShape; // Hình khối của body

Sprite *ball; // Hình quả bóng
float deltaTime; // Biến tính thời gian

void addWall(float w,float h,float px,float py); // Tạo 1 khung Wall bao quanh màn hình để cho quả bóng va chạm

void update(float dt); // Update scene theo thời gian

Mở file HelloWorldScene.cpp, làm những việc sau
+ Thêm vào lệnh #define SCALE_RATIO 32.0 ( vì Box2D dùng đơn vị mm nên ta phải có hệ số chuyển đổi này từ pixel sang mm)
+ Trong hàm init() Xóa từ đoạn code this->addChild(label, 1); đến return true; sau đó thêm đoạn code này vào

b2Vec2 gravity = b2Vec2(0.0f,-10.0f); // Vector gia tốc ( dấu - là chỉ hướng xuống, vì trục y hướng lên trên)

world = new b2World(gravity); // Tạo world với vector gia tốc

    // Tạo 1 Sprite quả bóng
    ball = Sprite::create("ball.png");

    // Đặt vị trí giữa màn hình
    ball->setPosition(Point(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));

// Đoạn này quan trọng nhất để app body trong Box2D

//---------------------KHUNG VAT LY BOX2D--------------------

bodyShape.m_radius = 45 / SCALE_RATIO; // Bán kính của khối body

//fixtureDef
fixtureDef.density=10; // Trọng lượng
fixtureDef.friction=0.8; // Ma sát
fixtureDef.restitution=0.6; // Đàn hồi
fixtureDef.shape=&bodyShape; // Trỏ vào bodyShape

//bodyDef
bodyDef.type = b2_dynamicBody; // Va chạm động
bodyDef.userData = ball; // gắn với Sprite ball

// Đặt vị trí, và nhớ chuyển đổi đơn vị
bodyDef.position.Set(ball->getPosition().x/SCALE_RATIO,ball->getPosition().y/SCALE_RATIO);

//ballBody
ballBody = world->CreateBody(&bodyDef); // Tạo Body
ballBody->CreateFixture(&fixtureDef); // Tạo các thuộc tính tĩnh
ballBody->SetGravityScale(10); // Đặt tỷ lệ gia tốc, càng cao rơi càng nhanh

//-----------------------------------------------------------

    // Đặt quả bóng vào layer của Scene
    this->addChild(ball, 0);

scheduleUpdate(); // Update lại scene theo thời gian, phải có cái này nhé

+ Xây dựng hàm update(float dt) như sau

void HelloWorld::update(float dt){
   int positionIterations = 10;  // Vị trí
   int velocityIterations = 10; // Vận tốc

   deltaTime = dt; // Bước thời gian

// Mô phỏng chuyển động vật lý theo thời gian, hãy nghiên cứu ở đây http://www.box2d.org/manual.html và đây http://www.iforce2d.net/b2dtut/worlds\

// Có thể hiểu thế này, mỗi Step xảy ra trong dt giây , dt này trong file AppDelegate.cpp định nghĩa = dòng lệnh director->setAnimationInterval(1.0 / 60); Bạn thử thay 1/60 = 1/1 xem, rơi cực chậm theo từng giây

   world->Step(dt, velocityIterations, positionIterations);  

// Duyệt tất cả body của world
   for (b2Body *body = world->GetBodyList(); body != NULL; body = body->GetNext())   
// Xét những body có gắn vào Sprite
     if (body->GetUserData()) 
     {  

       // Trả về sprite quả bóng ( có mỗi sprite trong bài này )
       Sprite *sprite = (Sprite *) body->GetUserData();  
// Đặt lại vị trí của Sprite này theo vị trí của body ( body sẽ bị rơi dần theo time), nhớ nhân RATIO để chuyển sang tọa độ pixel
       sprite->setPosition(Point(body->GetPosition().x * SCALE_RATIO,body->GetPosition().y * SCALE_RATIO));  
      // Đặt khả năng quay tròn
       sprite->setRotation(-1 * CC_RADIANS_TO_DEGREES(body->GetAngle())); 

     }  
    world->ClearForces(); // Xóa mọi áp đặt cho Body
    world->DrawDebugData();  // Không hiểu, chắc là debug

}   
Build rồi chạy thử thôi, nếu bạn thấy 1 quả bóng rơi xuống đáy màn hình là đã thành công, chúc mừng nhé



Vậy là trong bài này chúng ta đã làm quen với Box2D và cách thiết lập physics body trong Box2D. Các bài sau mình sẽ hướng dẫn cụ thể hơn bằng các game nhỏ áp dụng Box2D nhé.

P/S: 1 số lưu ý nho nhỏ trong Box2D

+ Phải có 1 hàm có tham số vào là thời gian, trong này là update (float dt)
+ Khi gắn body cho sprite thì vị trí của sprite luôn bị phụ thuộc vào vị trí của body
+ Cách nhớ Quy đổi tỷ lệ, ở đây ta có 2 hệ tọa độ: Tọa độ của màn hình, và hệ tọa độ của Box2D ( khó hình dung đấy, và hay nhầm ). Hãy nhớ như sau

- Khi thiết lập vị trí ( Position ), kích thước cho các đối tượng, thành phần của Box2D (body, shape)  nếu tham số vào là tọa độ màn hình thì phải chia cho tỷ lệ ( "/SCALE_RATIO" ), ví dụ:

bodyDef.position.Set(ball->getPosition().x/SCALE_RATIO,ball->getPosition().y/SCALE_RATIO);

- Ngượi lại để thiết lập vị trí, kích thước cho các đối tượng thành phần của màn hình ( sprite, label,v..v..) nếu tham số vào là tọa độ Box2D thì  luôn nhân với tỷ lệ (*SCALE_RATIO), ví dụ

  sprite->setPosition(Point(body->GetPosition().x * SCALE_RATIO,body->GetPosition().y * SCALE_RATIO));  


Chào và hẹn gặp lại ở bài sau!

Bài 16: Box2D - Một thư viện vật lý khác của Cocos2d-x - Nâng cao ( Part 2 )
More about

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)

Người đăng: vjnhhoa

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)

More about

Bài 13: Tạo khung vật lý của đối tượng phức tạp bằng phần mềm Physicbody Editor (Part 1)

Người đăng: vjnhhoa on Thứ Bảy, 17 tháng 5, 2014

Hi cả nhà!

Ở bài 5 và bài 6, chúng ta đã biết cách tạo physic trong Cocos2d-x V3.0 rồi phải không? Nhưng trong trường hợp đó chỉ  là 1 hình tròn, nên việc tạo khung body khá dễ dàng. Vậy nếu như đối tượng của chúng ta có đường bao phức tạp thì làm như thế nào?. Sau đây mình sẽ trình bày với các bạn 1 cách, thật ra là viết lại từ 1 bài Tut trên mạng, viết lại cho nhớ thôi mà, hêhê

Bài này có 2 phần như sau:
+ Tạo PhysicsBody Polygons ( Khung vật lý đa giác ) từ hình ảnh bằng phần mềm Box2d-Editor
+ Import vào game và để cho hệ thống Physic của Engine xử lý thông qua các hàm phân tích.



Vậy nhé, ở bài này chúng ta sẽ đi khảo sát 1 phần đầu tiên nhé

Phần 1: Tạo PhysicsBody Polygons ( Khung vật lý đa giác )

1/ Trước tiên phải down phần mềm PhysicBody Editor về, ở đây PhysicsBody Editor, down bản 2.9.2 nhé
giải nén và chạy file physics-body-editor.jar (click đúp). Nếu bạn đã cài JDK thì sẽ chạy thôi.

2/ Phần này định viết bằng chữ, nhưng quyết định quay video hướng dẫn cho nhanh, Bạn dowload file hình ảnh ở đây nhé


Video cho máu nha



Quên mất không giới thiệu trong video là Nếu bạn ko dùng tính năng Auto- Trace thì có thể chọn bằng tay, tuy nhiên sẽ lâu hơn chút

Mình kết thúc bài 13 ở đây nhé, vì bài sau có thể sẽ hơi dài một chút

Hẹn gặp lại vào bài sau!

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

Bài 12: Làm game đầu tiên - GameOver, tính điểm, thêm âm thanh cho game sôi động (Part 4)

Người đăng: vjnhhoa

Hello all!

Vậy là chúng ta đã đi được 3/4 chặng đường cho project đầu tiên này rồi. Tuy rằng khá đơn giản, nhưng công việc viết ( thực ra là đang áp dụng ) code sao cho đúng và dễ kiểm tra sửa lỗi cũng hoa mắt ghê.

Chỉ cần nốt 1 bài này thôi là chúng ta có 1 game đầu tiên để chơi rồi, hehe. Trong phần cuối này chúng ta sẽ cùng làm nốt những công việc sau:

+ Kiểm tra game Over khi quái lao vào nhân vật
+ Thêm âm thanh khi bắn đạn và thêm chút nhạc nền cho hấp dẫn nhỉ.
+ Mình bỏ phần Tính điểm nha, hơi phức tạp 1 tí, hẹn bài sau. Bài này để mức đơn giản thôi.

Bắt đầu nào!!!!!

B1 - Thêm âm thanh vào game

Các bạn Download resource ở đây, chép vào trong thư mục Resource của Project
Sau đó mở file HelloWordScene.cpp, trong hàm init() thêm vào dòng lệnh sau

CocosDenshion::SimpleAudioEngine::getInstance()->preloadBackgroundMusic("background-music-aac.wav");
CocosDenshion::SimpleAudioEngine::getInstance()->playBackgroundMusic("background-music-aac.wav",true); // True = lặp lại vô hạn


Tìm tới hàm onTouchEnded(), thêm vào dòng lệnh sau


CocosDenshion::SimpleAudioEngine::getInstance()->preloadEffect("pew-pew-lei.wav");

CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("pew-pew-lei.wav");

Thêm vào 1 lệnh #include"SimpleAudioEngine.h" ở sau dòng lệnh #include" " đầu tiên nhé.

B1 - Tạo màn GameOver

Bạn tạo thêm 1 Scene mới tên là GameOverScene.h và GameOverScene.cpp như sau, Down file Class


Chúng ta ngó qua 2 file này chút nhé:

* Mở file GameOverScene.h khai báo 2 Class GameOverLayer, GameOverScene, cùng với các hàm tạo hàm hủy, hàm khởi tạo init(), không quá phức tạp phải không?

Tuy nhiên, nên chú ý 1 chút tới 2 lệnh này

CC_SYNTHESIZE_READONLY(cocos2d::LabelTTF*, _label, Label); (1)

CC_SYNTHESIZE_READONLY(GameOverLayer*, _layer, Layer); (2)

Nhìn có vẻ khó hiểu nhỉ, nhưng bạn có thể hiểu thế này

(1) tạo ra 1 biến _label có kiểu LabelTTF* thông qua 1 phương thức của lớp Label

(2) tạo ra 1 biến _layer có kiểu GameOverLayer* thông qua 1 phương thức của lớp Layer

đơn giản thế thôi, đừng phức tạp hóa lên quá, Bạn có thể tra cứu thêm về các hàm Inline ở đây

* Mở file GameOverScene.cpp, hàm hủy (có dấu ~ ) thì dễ hiểu rồi nhé, đơn giản là giải phóng cho các con trỏ của lớp để giải phóng bộ nhớ.

Xét các hàm Init()
GameOverScene::init(), tạo ra 1 Scene mới và 1 Layer mới
GameOverLayer::init(), tạo 1 layer màu trắng Color4B(255,255,255,255), có ai thắc mắc Color4B, Color3B là gì không?

Color3B tạo ra màu RGB từ 3 tham số vào. Color4B tạo ra màu RGBA từ 4 tham số vào, trong đó tham số cuối điều chỉnh độ opacity ( đục ).

Đoạn này 

this->runAction( Sequence::create(
                                DelayTime::create(3),
                                CallFunc::create(this, 
                                callfunc_selector(GameOverLayer::gameOverDone)),
                                NULL));

Là tạo 2 Action liên tiếp nhau
+ Làm trễ 3 giây  DelayTime::create(3)
+Tiếp theo CallFunc::create(this, callfunc_selector(GameOverLayer::gameOverDone)), // Gọi hàm game OverDone

Hàm gameOverDone() làm mỗi việc là thay thế cái Scene GameOverScene bằng 1 Game Scene mới để tiếp tục chơi

void GameOverLayer::gameOverDone()
{
    Director::getInstance()->replaceScene(HelloWorld::createScene()); // Tạo 1 Game Scene mới
}


Bạn mở file HelloWordScene.cpp tìm tới hàm onContactBegin(), bên trong khối lệnh

// Nhân vật bị quái đụng vào
if((tag==1&tag1==2)||(tag==2&tag1==1))
 {
// Xử lý GameOver
// Tính điểm 
 }

Thêm vào đoạn code sau đây

auto gameOverScene = GameOverScene::create(); // Tạo 1 Scene Over của lớp GameOverScene
gameOverScene->getLayer()->getLabel()->setString("You Lose :["); // Đặt 1 dòng thông báo lên màn hình
Director::getInstance()->replaceScene(gameOverScene); // Thay thế game Scene =  game Over Scene 

Nhớ thêm #include"GameOverScene.h" ở phần #include nhé.

Trước khi biên dịch chạy thử, bạn cần làm 1 việc sau

+ Build Android, hãy mở file Android.mk trong đường dẫn firstgame\proj.android\jni\Android.mk thêm vào dòng ../../Classes/GameOverScene.cpp bên dưới dòng ../../Classes/HelloWorldScene.cpp
+ Build Win32: hãy mở file firstgame.vcxproj theo đường dẫn firstgame\proj.win32\firstgame.vcxproj thêm vào 2 dòng

<ClCompile Include="..\Classes\GameOverScene.h" /> dưới dòng <ClCompile Include="..\Classes\HelloWorldScene.h" />

<ClCompile Include="..\Classes\GameOverScene.cpp"/>dưới dòng <ClCompile Include="..\Classes\HelloWorldScene.cpp" />

Build và chạy thử nhé, 

Ặc, lại lỗi tè le, và ko hiểu là lỗi gì luôn, thông báo lỗi loằng ngoằng quá. Nếu như bạn không đọc cái chú ý của mình ( phần chữ đỏ ) ở Bài 11 chắc chắn sẽ chẳng thể tìm được cách sửa lỗi này đâu, cá đó ( Tinh vi tí thôi, mò mãi cũng ra thôi, chả nhớ ngày trước tìm được cách sửa ở đâu).

Túm lại lỗi này sửa thế này thêm USING_NS_CC; vào dưới phàn #include trong file GameOverScene.h là xong ( mọi file .h nên thêm vào, mất gì đâu ).

Xong, Build lại và chạy là OK ngay, ko OK mình xóa Blog này luôn nhé! Hứa đấy
























Vậy là chúng ta đã cùng nhau hoàn thành xong Project Game đầu tiên rồi đấy, cũng không khó phải không?
Trong Project này chúng ta đã nắm được các phần cơ bản sau đây:

+ Tạo nhân vật bằng Sprite
+ Bắt sự kiện Touch - Chạm màn hình
+ Bắt sự kiện Contact - va chạm vật lý
+ Xử lý âm thanh
+ Xử lý game Over

- Hi vọng ở những bài sau chúng ta sẽ cùng nhau làm được nhiều Project phức tạp hơn, hay hơn, đẹp hơn nhé.

Chào và hẹn gặp lại các bạn ở bài sau!


More about

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

Người đăng: vjnhhoa 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


More about

Bài 10: Làm game đầu tiên - Sự kiện chạm màn hình và bắn đạn (Part 2)

Người đăng: vjnhhoa

Hi!

Đi làm ngồi rảnh quá, tranh thủ post bài vậy. Trong bài trước chúng ta đang triển khai dang dở 1 Project game đầu tiên nhỉ? Hoành tráng chưa. Cũng bình thường mà, Project nào chẳng bắt đầu từ sự đơn giản, sau đó phức tạp hóa lên dần dần.

Ở phần đầu chúng ta đã học được cách làm thế nào để tạo 1 nhân vật, 1 đám quái từ những hình ảnh thông qua lớp Sprite. Và trong phần thêm quái, bạn chú ý 1 đoạn tính toán vị trí xuất hiện, và tốc độ di chuyển cũng khá hay nhỉ. Chỉ là tính toán thôi mà, cũng chưa phải thuật toán gì ghê ghớm cả, nhưng thực sự nó là thuật toán ( đơn giản ) mà.

Trong phần tiếp theo này, chúng ta sẽ làm công việc sau:

+ Bắt sự kiện khi chạm màn hình
+ Khi chạm vào màn hình, nhân vật của ta sẽ bắn ra đạn
+ Xử lý viên đạn bay theo hướng của điểm chạm trên màn hình

Lets GOOOOÔ!

B1 - Sự kiện chạm màn hình và cách phát hiện

Các bạn mở file HelloWorldScene.cpp lên , trong hàm init() tìm đến cuối hàm trước lệnh return true; thêm vào đoạn lệnh sau:

//Tạo đối tượng truyền tải thông tin của các sự kiện
auto dispatcher = Director::getInstance()->getEventDispatcher();
//Tạo 1 đối tượng lắng nghe sự kiện Chạm màn hình theo cách One by One, xử lý 1 chạm tại 1 thời điểm
auto listener1 = EventListenerTouchOneByOne::create();
//Thiết lập "nuốt" sự kiện Touch khi xảy ra, ngăn ko cho các đối tượng Bắt sự kiện khác sử dụng event này
listener1->setSwallowTouches(true);

//Bắt sự kiện Touch, khi xảy ra sự kiện Touch nào thì sẽ gọi đến hàm tương ứng của lớp HelloWorld
listener1->onTouchBegan = CC_CALLBACK_2(HelloWorld::onTouchBegan, this);
listener1->onTouchMoved = CC_CALLBACK_2(HelloWorld::onTouchMoved, this);
listener1->onTouchEnded = CC_CALLBACK_2(HelloWorld::onTouchEnded, this);

//Gửi cho dispatcher xử lý
dispatcher->addEventListenerWithSceneGraphPriority(listener1, this);

return true;

* Lưu ý: dù rằng ko dùng onTouchMoved, nhưng chúng ta cũng nen cho vào để sự kiện Touch được xử lý đầy đủ.

B2 - Xử lý sự kiện chạm - tạo khả năng bắn đạn cho nhân vật

Bạn mở file HelloWorldScene.h, thêm vào 3 nguyên mẫu hàm:

void onTouchEnded (cocos2d::Touch* touches, cocos2d::Event* event);
void onTouchMoved (cocos2d::Touch* touches, cocos2d::Event* event);
//Chú ý hàm onTouchBegan phải trả về bool
bool onTouchBegan (cocos2d::Touch* touches, cocos2d::Event* event);

Sau đó mở file HelloWorldScene.cpp để định nghĩa 3 hàm này

Mặc dù chúng ta ko dùng hết cả 3 hàm nhưng để sự kiện Touch được xử lý đầy đủ bạn phải khai báo cả 3 hàm như sau


bool HelloWorld::onTouchBegan(Touch* touch, Event* event)

{  

return true; // Phải trả về True

}



void HelloWorld::onTouchMoved(Touch* touch, Event* event)
{  

// Không xử lý gì ở đây

}

void HelloWorld::onTouchEnded (Touch* touches, Event* event){
   
// Lấy tọa độ của điểm chạm
Point location =  touches->getLocationInView();
location = Director::getInstance()->convertToGL(location);

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

//Tạo viên đạn là 1 Sprite, đặt vị trí đầu tiên gần nhân vật chính
auto projectile = Sprite::create("Projectile.png");
projectile->setPosition( Point(20, winSize.height/2) );

// Đoạn này tính toán điểm cuối cùng của viên đạn thông qua vị trí đầu và vị trí Touch, hình ảnh bên dưới sẽ minh họa cho điều này. Ở đây áp dụng 1 vài công thức toán học rất cơ bản thôi nhé. Không phức tạp lắm

// Lấy tọa độ điểm chạm trừ đi tọa độ đầu của viên đạn (offX, offY)
int offX = location.x - projectile->getPosition().x;
int offY = location.y - projectile->getPosition().y;

// Không cho phép bắn ngược và bắn thẳng đứng xuống dưới ( bên dưới nhân vật )

if (offX <= 0) return;

// Thỏa mãn điều trên thì tạo hình viên đạn trên màn
this->addChild(projectile,1);

//Tính toán tọa độ điểm cuối thông qua toa độ điểm đầu và khoảng offX, offY
// Tọa độ tuyệt đối realX = chiều rộng màn hình + 1/2 chiều rộng viên đạn, vừa lúc bay ra khỏi màn hình 
int realX = winSize.width  + (projectile->getContentSize().width/2); 

// Tỷ lệ giữa offY và offX
float ratio = (float)offY / (float)offX;

// Tọa độ tuyệt đối realY tính dựa trên realX và tỷ lệ trên + thêm tọa độ Y ban đầu của đạn ( tính theo Talet trong tam giác, hoặc theo tính tang 1 góc)

int realY = (realX * ratio) + projectile->getPosition().y; // Chỗ này theo mình là chưa đúng, đúng ra phải thế này int realY = ((realX-projectile->getPosition().x) * ratio) + projectile->getPosition().y; (realX-projectile->getPosition().x mới đúng là chiều dài từ điểm đầu tới điểm cuối trên trục X

//Tọa độ điểm cuối
auto realDest = Point(realX, realY);

//Chiều dài đường đi của viên đạn, tính theo Pitago a*a = b*b + c*c, a là cạnh huyền tam giác vuông
int offRealX = realX - projectile->getPosition().x;
int offRealY = realY - projectile->getPosition().y;
float length = sqrtf((offRealX * offRealX)  + (offRealY*offRealY));

// Thiết lập vận tốc 480pixels/1giây
float velocity = 480/1;

// Thời gian bay của đạn = quãng đường đạn bay chia vận tốc ở trên
float realMoveDuration = length/velocity;

// Di chuyển viên đạn tới điểm cuối với thời gian, và tọa độ đã tính ở trên. Khi qua viền màn hình thì biến mất
projectile->runAction( Sequence::create(
MoveTo::create(realMoveDuration, realDest),
CallFuncN::create(CC_CALLBACK_1(HelloWorld::spriteMoveFinished,this)), NULL) );

}

Đoạn code runAction nhìn có vẻ phức tạp nhưng nếu viết tường minh nó sẽ thế này

//Di chuyể đạn với thời gian và tọa độ tính toán ở trên
auto move= MoveTo::create(realMoveDuration,realDest);

//Khi move tới điểm cuối xong, bạn sẽ gọi hàm spriteMoveFinished để xóa bỏ hình ảnh viên đạn, nếu không xóa bỏ đi, thì viên đạn bay ra ngoài vẫn nằm trong Layer, ngày càng nhiều, xử lý ngày càng nặng. Đoạn này sẽ trả về 1 Action*
auto finish=CallFuncN::create(CC_CALLBACK1(HelloWorld::spriteMoveFinished,this));

//Thực hiện tuần tự 2 việc, Move sau đó Xóa 
auto run = Sequence::create(move,finish,NULL);

//Thực hiện công việc xử lý hàm Sequence
projectile->runAction(run);

Bạn nên xem lại bài về Các Action cơ bản của Sprite

Ảnh minh họa cho việc tính toán đường bay của đạn



Phần này như vậy là đã OK rồi nhé, tìm hiểu kỹ thì cũng không có gì phức tạp cả phải không nào. Tóm tắt lại công việc như sau

+ Bắt sự kiện chạm màn hình bằng các Listener
+ Xây dựng hàm xử lý khi có sự kiện Chạm màn hình, bắn ra viên đạn, tính toán đường bay, tọa độ, tốc độ viên đạn, có dính tí toán học, vật lý học nè
+ Tạo Action di chuyển viên đạn với tốc độ, tọa độ đã tính toán

Các bạn có thể down file nguồn ở đây, nếu ngại copy và kiểm tra code ở phía trên

Ảnh Demo chút


Tuy nhiên bạn sẽ thấy đạn bắn "xuyên táo" con quái luôn, hehe. Ở bài sau chúng ta sẽ thiết lập sự kiện va chạm cho viên đạn với quái nhé. 


More about

Tản mạn chuyện học game - làm game

Người đăng: vjnhhoa on Thứ Hai, 12 tháng 5, 2014

Hi!

Công nhận là học lập trình, đặc biệt là lâp trình game khá vất vả, nhưng đầy thú vị. Không biết cảm giác của mọi người thế nào chứ mình thấy công việc này làm cho mình giống như một vị Chúa với đầy đủ quyền năng, sáng tạo ra hẳn 1 thế giới mới tươi đẹp và hấp dẫn.

Vui vậy thôi, chứ mệt ghê đấy, phải luôn chăm chỉ, tìm tòi và sáng tạo nữa. HIX

Buồn buồn lang thang trên mạng đọc được mấy bài báo về Học làm game, chủ yếu là Quảng cáo cho mấy trung tâm, đại học dạy làm game thôi các bạn ạ. Đọc nghe hoàng tráng lắm ( QC mà lị ), nào là:

+ Cơ hội việc làm, thăng tiến, lương cao
+ Đội ngũ chuyên gia về game : online, ofline, 2D, 3D, các thể loại
+ Công nghệ tiên tiến mới nhất của thế giới
+ Hợp tác đầu tư với các doanh nghiệp Game đầu ngành: Gameloft, FPT,vv
+ Vân vân và vân vân....
+ Học phí có 20 -60 triệu 1 khóa từ 1- 2 năm, tính ra cũng 2-3 triệu/tháng. :((

Thực hư không biết thế nào, nhưng có mấy điều chúng ta phải nhìn nhận vào thực tế là:
+ Chất lượng giáo dục nói chung, ngành Game nói riêng của Việt Nam ta còn yếu, nặng về thành tích, số lượng, điểm trác.
+ Thực trạng kinh doanh giáo dục, lấy lợi nhuận đặt lên hàng đầu chứ không lấy chất lượng của học sinh, sinh viên làm trọng tâm.
+ 100% học song ra có việc làm, xin lỗi chỉ là Quảng Cáo, ở bất cứ đâu cũng vậy, giỏi thực sự bạn sẽ được săn đón, còn chỉ làng nhàng trung bình khá thì hãy tự bơi nhé, dù có chết chìm cũng ko ai vớt đâu chứ đừng nói cứu. Vì thế con số này nếu có chỉ là 1-2 % thôi.
+ Các Sudio Game hàng đầu của Việt Nam giờ ở đâu, và ra sao. bạn cứ tìm hiểu là biết, hầu hết đều đang thoi thóp sau 1 thời gian mọc lên theo phong trào. Có sống thì một vài năm mới ra được 1 -2 game chìm ngỉm trong muôn ngàn game nước ngoài. Haiizzz!

Vậy thì: Học làm game có nên chăng, học như thế nào, học để làm làm gì? Thực là chính mình cũng băn khoăn lắm. vì hiện tại làm game ko phải nghề của mình chỉ là thú vui, là đam mê, là sự tìm tòi, học hỏi , vậy thôi. 

Còn những ai có tư tưởng học làm game theo kiểu phong trào, hoặc làm game để lấy le với gái, hoặc để nổi tiếng như NHĐ thì xác định là thôi khỏi học làm gì, chẳng tới đâu và tốn thời gian tâm sức lắm.

Tản mạn đôi chút cùng chính mình, không biết có ai đọc không!?

Bài tham khảo ( 1 dạng QC trá hình, đọc tại đây: http://game.thanhnien.com.vn/print/2014/03/07/ban-tre-lam-game-hoc-gi-o-dau.1124.html )



More about