Hiển thị các bài đăng có nhãn Physics. Hiển thị tất cả bài đăng
Hiển thị các bài đăng có nhãn Physics. Hiển thị tất cả bài đăng

Bài 28 - Box2D - Physics Body cho những vật tĩnh có hình khối phức tạp

Người đăng: share-nhungdieuhay on Thứ Sáu, 11 tháng 7, 2014

Hi, tất cả!

Ở mấy bài trước ( 15-16 gì đó) chúng ta đã cùng nhau nghiên cứu về cách sử dụng Box2D để tạo môi trường vật lý trong Game 2D. Trong bài đó, đối tượng của chúng ta chỉ là một quả bóng hình tròn bán kính R, nên việc đặt body cho nó là quá dễ dàng. Vậy còn đối với những đối tượng có hình dáng bất kỳ thì sao ( chỉ xét những vật tĩnh, ko xét animation nhé) ? Làm thế nào để đặt body cho chúng. Đó sẽ chính là nội dung trong bài hôm nay của chúng ta.

Trong bài này chúng ta sẽ buộc phải sử dụng 2 công cụ PhysicsEditor và TexturePacker ( có thể sử dụng ShoeBox, hoặc SpritePacker để thay thế nếu TexturePacker crack bị lỗi - Win 64 của mình ko bị lỗi sau crack, 32 thì dính chưởng : ((

Đồng thời bạn cũng sẽ được cung cấp 1 bộ thư viện để phân tích file ảnh + file thông số physics được tạo ra bởi 2 phần mềm kể trên. Do đó, hầu như bạn chẳng phải viết code gì nặng nhọc đâu, chỉ việc sử dụng cái thư viện đó để phân tích file nạp vào rồi thiết lập được body ngay. Để hiểu sâu bạn có thể tìm hiểu thư viện đó ( chỉ gồm 1 file .h và .cpp ) sẽ rất có ích nếu bạn có ý định sử dụng Box2D về sau.

Nội dung trong bài gồm các phần sau đây:

+ Sử dụng PhysicsEditor + TexturePacker để thiết kế body cho các đối tượng có hình khối phức tạp
+ Nạp vào game
+ Phương pháp Debug - vẽ khung của đối tượng 1 cách trực quan
+ Test thử

OK, ngắn gọn như vậy thôi, triển nào

À, bài này mình tìm được trên trang codeandweb - trang chủ của PhysicsEditor và TexturePacker

Đây https://www.codeandweb.com/physicseditor/tutorials

Nhưng chủ yếu là Cocos2D, và các Engine khác ( phân biệt đối xử thật ). Lúc đầu cũng khá hì hụi để tìm code trên Github, nhưng khá cũ. Đành sửa và port sang bản 3.x vậy. Các bạn đọc hết 27 bài trước đây trong Blog này thì mình tin là việc port Code từ 2.x sang 3.x thật dễ dàng. Mình muốn chia sẻ 1 điều là, khi tự mình nghiên cứu tìm tòi cách để làm 1 cái gì đó thì kết quả đạt được sẽ rất ý nghĩa, vui và nhớ lâu hơn.

À, các bạn nhớ ôn lại:
+ Cách Import Box2D ( Cũng như các thư viện khác ) ở bài 15, 23,24
+ Cách khai báo Class mới, cũng ở các bài trên
+ Hãy đọc bài chú ý khi biên dịch cho cả Win + Android ( Side bar bên phải có dấu tam giác to đùng )

B1 - Sử dụng PhysicsEditor + TexturePacker 

TexturePacker 

Phầm mềm này rất dễ sử dụng, chức năng chính của nó là đóng gói ( pack ) nhiều ảnh đơn thành 1 bức ảnh to + 1 file thông số đi kèm. File ảnh to xuất ra có thể là png hoặc các dạng nén khác. Mục đích là để giảm tải bộ nhớ, dung lượng game, vậy thôi. Đơn giản vô cùng.

Mở chương trình lên, để các thông số mặc định không sửa đổi gì, import ảnh vào thông qua nút + SPrite hoặc + thư mục, trỏ tới THƯ MỤC ẢNH chứa các ảnh sử dụng trong chương trình này. Nhấn Publish, được 2 file, quăng vào Resource của project. Phần này khá đơn giản nên mình ko minh họa bằng hình ảnh nhé.

PhysicsEditor

Mở phần mềm lên, Import hình ảnh vào thông qua nút Open hoặc nút AddSprites, và cũng trỏ  vào THƯ MỤC ẢNH ở trên ( quan trọng nhé ).




Bây giờ bạn để ý tới 3 khu vực quan trọng nhất
+ Trái : là các hình ảnh bạn import vào
+ Giữa là khu vực bạn sẽ vẽ, hoặc auto tracer shape cho hình ảnh thông qua các hình tròn, đa giác, nút auto, flip ngay bên dưới nút AddSprites
+ Phải: Thiết lập chế độ xuất ra - Exporter ( Box2D, Engine xx) và các thông số vật lý cơ bản của Box2D ( dynamic, friction, density, bitmask, v..v...). Bạn chọn mục Exporter là Box2D generic (PLIST) nhé

Lưu ý 1 chút: để tạo shape dạng Polygon, bạn chọn công cụ đa giác, sẽ hiện 1 tam giác đỏ, để thêm đỉnh mới bạn click đúp, rồi kéo các đỉnh khớp với đối tượng

Thực hành luôn, bạn nhấn vào nút Shape Tracer ( nút thứ 3, sau hình đa giác ) nhé sẽ hiện lên bảng Tracer

Bạn để các thông số như hình





 Để ý là
+ Thông số Telerance càng cao thì Vertexer càng nhỏ và Shape càng thiếu chính xác, với hình ko quá phức  tạp bạn để Vertexes vừa phải thôi nhé. Chỉnh sao sao cái Shape tạo ra ( màu đỏ khớp tương đối với hình ảnh của chúng ta là được).

Sau khi tạo Shape xong cho các ảnh trong này, bạn nhấp chuột vào nút Publish, đặt tên file và chọn nơi lưu là Resource của project

Tuy 2file Plist tạo bởi Physics Editor và TexturePacker khá giống nhau nhưng nội dung chúng lưu khác nhau nhé: 1 thằng lưu thông số hình ảnh, 1 thằng lưu thông số hình khối. Bạn mở 2 file lên để so sách sự khác biệt. Và Physics Editor chỉ tạo ra 1 file Plist, còn TexturePacker tạo ra 2 file là: Plist + ảnh Png ( hoặc dạng khác tùy mình chọn ).

B2 - Nạp vào game + Debug drawing Box2D

Ở bước này, bạn tạo 1 project, giả sử tên là newbox2d chẳng hạn, nhớ Import thư viện Box2D vào theo bài 15, 23,24

Mở thư mục cpp-tests trong thư mục Engine  lên, copy các file sau vào Class Project của chúng ta:

+ VisibleRect.h, .cpp theo đường dẫn Q:\ANDROID\Cocos2dx3\tests\cpp-tests\Classes
+ GLES-Render.h, .cpp theo đường dẫn Q:\ANDROID\Cocos2dx3\tests\cpp-tests\Classes\Box2DTestBed
Download và copy 2 file này vào thư mục Class (Tất cả down ở cuối bài)

OK, các bạn không phải làm gì với các file ở trên, vì mình sử dụng chúng làm thư viện hỗ trợ thôi, các bạn thích thì có thể ngó nghiêng qua, không thì thôi, vào phần chính luôn, đó là file HelloWorldScene.h và .cpp

À, các bạn nhớ thêm các class trên vào file. newbox2d.vcxproj trong proj.win32 ( nếu build win )

hoặc trong file Android.MK ( nếu build Apk )

OK, giờ thì tập trung vào file HelloWorldScene.h

Các bạn mở lên, Paste full code sau vào

#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__

// Nhớ include Box2D, và các mở rộng
#include "cocos2d.h"
#include "Box2D/Box2D.h"
#include "GLES-Render.h"
#include "GB2ShapeCache-x.h"
#include "VisibleRect.h"

USING_NS_CC;

class HelloWorld : public cocos2d::Layer
{
public:

    static cocos2d::Scene* createScene();

    virtual bool init();  
    
    void menuCloseCallback(cocos2d::Ref* pSender);
void addNewSpriteWithCoords(Point p); // Tạo đối tượng tại Point xác định
// Touch, nên để cả 3 hàm, mặc dù chỉ dùng 1 hàm thôi
bool onTouchBegan(Touch* touch, Event* event);
void onTouchMoved(Touch* touch, Event* event);
void onTouchEnded(Touch* touch, Event* event);

// Hàm vẽ để Debug khung body của đối tượng, lưu ý: Hàm này chỉ đúng với Engine 3.0,3.1.x nhé, Ai dùng Engine 3.2 RC0 trở lên ( ko xét alpha,beta) thì phải sửa thành.

// virtual void draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) override;

// Kẻo lỗi tùm lum

virtual void draw(Renderer *renderer, const Mat4 &transform, bool transformUpdated) override;

void update(float dt);
    
    CREATE_FUNC(HelloWorld);

private:

// World và đối tượng Debug Draw
b2World* world;
GLESDebugDraw *m_debugDraw;
};

#endif // __HELLOWORLD_SCENE_H__

Giờ thì tới lượt file HelloWorldScene.cpp

Phần Include, thêm code sau

USING_NS_CC;
using namespace std;
 // Nếu ko dùng namespace này, khai báo string str sẽ lỗi
#define PTM_RATIO 32  

Hàm init()

bool HelloWorld::init()
{
    if ( !Layer::init() )
    {
        return false;
    }
    
// Lớp Point đã được thay = Vec2 từ V3.1
    Size visibleSize = Director::getInstance()->getVisibleSize();
    Vec2 origin = Director::getInstance()->getVisibleOrigin();
  
setTouchEnabled( true );
Size screenSize = Director::getInstance()->getWinSize();

// Khởi tạo world của Box2D
b2Vec2 gravity;
gravity.Set(0.0f, -10.0f);
bool doSleep = true;  
world = new b2World(gravity);
world->SetAllowSleeping(doSleep);    
world->SetContinuousPhysics(true);

//-----------DEBUG------------và phải có hàm Draw ở phía dưới nữa
// Debug khung body
m_debugDraw = new GLESDebugDraw( PTM_RATIO );
world->SetDebugDraw(m_debugDraw);

// Các tham số để vẽ, bạn mở dần từng comment ra để thấy sự thay đổi
uint32 flags = 0;
flags += b2Draw::e_shapeBit;
//flags += b2Draw::e_jointBit;
//flags += b2Draw::e_aabbBit;
// flags += b2Draw::e_pairBit;
// flags += b2Draw::e_centerOfMassBit;
m_debugDraw->SetFlags(flags);
//-------------End Debug------------

//Tạo Ground bao quanh màn hình
b2BodyDef groundBodyDef;


groundBodyDef.position.Set(screenSize.width/2/PTM_RATIO, 
screenSize.height/2/PTM_RATIO); 

b2Body* groundBody = world->CreateBody(&groundBodyDef);

b2PolygonShape groundBox;

// bottom
groundBox.SetAsBox(screenSize.width/2/PTM_RATIO, 0, b2Vec2(0, -screenSize.height/2/PTM_RATIO), 0);
groundBody->CreateFixture(&groundBox, 0);

// top
groundBox.SetAsBox(screenSize.width/2/PTM_RATIO, 0, b2Vec2(0, screenSize.height/2/PTM_RATIO), 0);
groundBody->CreateFixture(&groundBox, 0);

// left
groundBox.SetAsBox(0, screenSize.height/2/PTM_RATIO, b2Vec2(-screenSize.width/2/PTM_RATIO, 0), 0);
groundBody->CreateFixture(&groundBox, 0);

// right
groundBox.SetAsBox(0, screenSize.height/2/PTM_RATIO, b2Vec2(screenSize.width/2/PTM_RATIO, 0), 0);
groundBody->CreateFixture(&groundBox, 0);

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

// Nạp vào game file hình ảnh đóng gói và thông số của nó tạo bởi TexturePacker
SpriteFrameCache::getInstance()->addSpriteFramesWithFile("INFOR.plist");
auto spriteSheet = SpriteBatchNode::create("INFOR.png");
this->addChild(spriteSheet);

// Nạp vào file thông số hình khối tạo bởi Physics Editor
GB2ShapeCache::sharedGB2ShapeCache()->addShapesWithFile("shapedefs.plist");

// Bắt sự kiện Touch

auto touchListener = EventListenerTouchOneByOne::create();
touchListener->setSwallowTouches(true);
touchListener->onTouchBegan= CC_CALLBACK_2(HelloWorld::onTouchBegan,this);
touchListener->onTouchMoved=CC_CALLBACK_2(HelloWorld::onTouchMoved,this);
touchListener->onTouchEnded=CC_CALLBACK_2(HelloWorld::onTouchEnded,this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener,this); 

// Cập nhật Scene
schedule( schedule_selector(HelloWorld::update) );

    return true;
}

3 Hàm Touch

void HelloWorld::onTouchEnded(Touch* touch, Event* event)
{
// Lấy tọa độ điểm touch và tạo ra đối tượng tại tọa độ đó
auto touchPoint = touch->getLocation(); 
touchPoint = this->convertToNodeSpace(touchPoint);
addNewSpriteWithCoords( touchPoint );
}

bool HelloWorld::onTouchBegan(Touch* touch, Event* event)
{
return true;
}

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

Hàm addNewSpriteWithCoords

// Chuỗi string lưu tên các file ảnh nằm trong pack
string names[] = {
"hotdog",
"drink",
"icecream",
"icecream2",
"icecream3",
"hamburger",
"orange"
};

void HelloWorld::addNewSpriteWithCoords(Point p)
{
string name = names[rand()%7]; // Random 0-6

// Trả về xâu ký tự ví dụ: "orange.php" - = phương thức c_str()
Sprite *sprite = Sprite::createWithSpriteFrameName((name+".png").c_str());

sprite->setPosition(p);
addChild(sprite);

// Tạo b2BodyDef
b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody;
bodyDef.position.Set(p.x/PTM_RATIO, p.y/PTM_RATIO);
bodyDef.userData = sprite;

b2Body *body = world->CreateBody(&bodyDef);

// Tạo fixture cho body, bằng cách sử dụng GB2ShapeCache

GB2ShapeCache *sc = GB2ShapeCache::sharedGB2ShapeCache();
sc->addFixturesToBody(body, name.c_str()); 
sprite->setAnchorPoint(sc->anchorPointForShape(name.c_str())); // Đặt cùng 1 điển neo
}

Hàm update, chỉ việc copy lại ở những bài trước thôi, nếu có thay đổi thì đổi giá trị của velocityIterations + positionIterations 


void HelloWorld::update(float dt)

{

int velocityIterations = 8;
int positionIterations = 1;
world->Step(dt, velocityIterations, positionIterations);

for (b2Body *body = world->GetBodyList(); body != NULL; body = body->GetNext())   
if (body->GetUserData()) 
{  
Sprite *sprite = (Sprite *) body->GetUserData();  
sprite->setPosition(Point(body->GetPosition().x * PTM_RATIO,body->GetPosition().y * PTM_RATIO));  
sprite->setRotation(-1 * CC_RADIANS_TO_DEGREES(body->GetAngle())); 

}  
world->ClearForces(); 
world->DrawDebugData();   

}

Và đây là hàm Draw để Debug cho Box2D ( V3.0, 3.1 thì như bên dưới ). V3.2 RC0 trở lên thì thay đổi 1 chút ở phần tham số vào virtual void draw(Renderer *renderer, const Mat4 &transform, uint32_t flags). Các bạn chú ý. Nội dung hàm mình ko giải thích nhé, lấy từ thư viện ra thôi.

void HelloWorld::draw(Renderer *renderer, const Mat4 &transform, bool transformUpdated)
{
GL::enableVertexAttribs( GL::VERTEX_ATTRIB_FLAG_POSITION );
Director* director = Director::getInstance();
CCASSERT(nullptr != director, "Director is null when seting matrix stack");
director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
world->DrawDebugData();
director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
}

OK rồi đó, Build thử xem cơm cháo thế nào

Cũng khá hay phải không nào.
Trong bài này chúng ta đã được nghâm cứu các vấn đề sau:

+ Cách sử dụng Physics Editor và TexturePacker ( có thể = ShoeBox hoặc SpritePacker thay thế )
+ Phân tích body của 1 Sprite phức tạp = thư viện GB2ShapeCache
+ Debug drawing trong Box2D


Mình kết thúc bài này ở đây nhé, chào và hẹn các bạn ở bài sau!

More about

Bài 18: Game thứ 2 - Breakout - Tạo và phá gạch (Part 2)

Người đăng: share-nhungdieuhay on Thứ Sáu, 23 tháng 5, 2014

Chào mọi người!

Vậy là chúng ta đã đi được 1 chặng đường kha khá của Cocos2d-x V3 rồi. Cũng chuẩn bị xong Project thứ 2 đấy chứ. Trong phần này mình sẽ hướng dẫn các bạn nốt công việc đơn giản là "xếp gạch và phá gạch" nhé. Sẽ rất đơn giản thôi.

Những công việc trong bài này:
+ Tạo gạch
+ Xử lý va chạm vật lý
+ Kiểm tra việc phá gạch, hết thì WINGAME
+ Kiểm tra GameOver khi bóng rơi không trúng thanh chắn

- Nhìn có vẻ nhiều việc vậy thôi, nhưng mà đơn giản lắm, vì cũng khá giống Game đầu tiên

Bắt đầu luôn nhé! À, file Resource và Class các bạn down hết ở bài 17 rồi đó

B1 - Tạo gạch

Vì Class mình up ở bài 17 dùng cho cả bài 18 ( Mình comment những đoạn code chưa dùng, bạn chỉ việc phá comment ra thôi)

Mở file HelloWorldScene.cpp thêm vào đoạn code sau

for (int i = 0; i < 5; i++) {
static int padding = 100;
auto block = Sprite::create("blocks.png");
auto blockBody = PhysicsBody::createBox(block->getContentSize(), PHYSICSBODY_MATERIAL_DEFAULT);
blockBody->getShape(0)->setDensity(10.0f);
blockBody->getShape(0)->setFriction(0.0f);
blockBody->getShape(0)->setRestitution(1.f);
blockBody->setDynamic(false);
// Tạo khoảng cách đều nhau giữa cách khối gạch
int xOffset = padding + block->getContentSize().width / 2 +
((block->getContentSize().width + padding)*i);
block->setPosition(xOffset, 450);
blockBody->setContactTestBitmask(0x000001);
block->setPhysicsBody(blockBody);
block->setTag(3);
this->addChild(block);
}

B2 - Xử lý va chạm - Phá gạch, Game Over

* Thêm ContactListener

auto dispatcher = Director::getInstance()->getEventDispatcher();
auto contactListener = EventListenerPhysicsContact::create();
contactListener->onContactBegin = CC_CALLBACK_1(HelloWorld::onContactBegin, this);  
dispatcher->addEventListenerWithSceneGraphPriority(contactListener, this);

* Dựng 1 hàm onContactBegin

bool HelloWorld::onContactBegin(PhysicsContact& contact)
{
// Lấy 2 đối tượng va chạm
auto spriteA = (Sprite*)contact.getShapeA()->getBody()->getNode();
auto spriteB = (Sprite*)contact.getShapeB()->getBody()->getNode();

// Kiểm tra loại đối tượng
int tagA = spriteA->getTag();
int tagB = spriteB->getTag();

if (tagA == 3) // Là gạch
{

this->removeChild(spriteA,true); // Xóa gạch

//spriteA->removeFromParentAndCleanup(true);
}

if (tagB == 3)  // Là gạch
{
this->removeChild(spriteB,true); // Xóa gạch

//spriteB->removeFromParentAndCleanup(true);
}

// Nếu bóng va chạm với sạn mà tọa độ Y của bóng nhỏ hơn thanh chắn thì Game Over
if ((tagA == 0 || tagB  == 0 )& (ball->getPositionY() <= paddle->getPositionY()))
{
auto gameOverScene = GameOverScene::create();
gameOverScene->getLayer()->getLabel()->setString("You Lose!");
Director::getInstance()->replaceScene(gameOverScene);
}

return true;
}

Lớp GameOverScene này giống với Project đầu tiên nhé

B2 - Kiểm tra Win game

Xây dựng hàm Tick() như sau, nhớ khai báo nguyên mẫu hàm

void HelloWorld::tick(float dt)
{
// 1 biến bool xác nhận Win game ban đầu gán = true;
bool isWin = true;
// Vector bodies lấy tất cả các bodies của world ( ball, edge, paddle body), về vector bạn nghiên cứu thêm C++ nâng cao nhé, cũng gần giống mảng, và cũng khá giống Stack. Khai báo vector thì như này Vector<Kiểu biến> tên_biến
Vector<PhysicsBody*> bodies = m_world->getAllBodies();

// Duyệt từng phần tử của vector trên, kiếm tra loại đối tượng = Tag, Bạn nên tìm hiểu lại lệnh for nhé, nó có nhiều biến thể cho từng loại lớp đặc biệt, đọc phần C++ nâng cao phần list, vector, queue,v..
// Đừng dập khuôn for chỉ có dạng for( int i=0; i<N; i++) nhé

for each(PhysicsBody* body in bodies) // Câu lệnh này lỗi Khi build android, bạn hãy sửa lại thành  for (auto body : bodies) nhé, đây là chuẩn mới C++ 11
{
if (body->getNode()->getTag() == 3) // Nếu còn body của "gạch", tức là chưa phá hết
{
isWin = false; // Chưa Win
}
}
// Duyệt hết mà  isWin vẫn ko đổi thì xử lý Win game
if (isWin == true)
{
auto gameOverScene = GameOverScene::create();
gameOverScene->getLayer()->getLabel()->setString("You Win!");
Director::getInstance()->replaceScene(gameOverScene);
}
}

Để gọi hàm Tich() này bạn thêm 1 dòng lệnh này 
this->schedule(schedule_selector(HelloWorld::tick),0); vào cuối hàm init() là xong,

Nếu bạn dùng scheduleUpdate() thì build hàm update(float dt) nhé

Build and Run nào

















Chúng ta kết thúc bài 18 ở đây nhé. Tóm lại trong cả bài 17 và 18 chúng ta học được gì?

+ Tạo world vật lý với Chipmunk
+ Move đối tượng = kéo, drag trên màn hình
+ Xử lý va chạm, 
+ Win game, Over Game
+ Biết thêm 1 chút về Vector, để dành các bài sau nhé

Những hạn chế của game này
+ Thiếu phần tính điểm, để những bài sau
+ Thiếu âm thanh ( bạn có thể thêm vào như Game trước nhé )
+ Thiếu phần Level chơi
+ Thiếu phần menu 
+ Đồ họa hơi cùi mía.

* Lưu ý khi build Android, bạn phải khai báo các file cpp mới tạo vào file Android.MK trong thư mục prj.android, như sau, chú ý dấu \








Dòng cuối ko có dấu \

Các dòng trên đều có

GameOverScene.cpp là Class mới tạo, phải khai báo vào đây, file .h thì ko cần

OK, và chú ý sửa lỗi code theo đúng chuẩn C++11 là build APK thành công thôi. Đã test và rút ra EXP nhé


Các bạn thấy đấy, 1 bài quá dễ đúng không. Nhưng nếu không đọc qua những bài phía trước thì chắc hẳn bài này ko dễ nuốt đâu nhỉ. Mong mọi người đừng ỷ vào những gì đã biết mà coi thường những thứ dễ dàng. Hãy học từ dễ đến khó, từ chưa biết đến biết, từ biết thành PRO. từ pro tới SÁNG TẠO nha, đó mới là đích tới của chúng ta. Thực ra đâu có đích nào là cuối.
Các bạn có thể phát triển thêm game này nếu có thời gian nhé.
Nếu không chúng ta cùng đi tiếp nào.

More about

Bài 17: Game thứ 2 - Breakout ( Part 1)

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

Chào các bạn!

Cũng nhanh ghê nhỉ, 17 bài rồi đấy, hehe. Chúng ta đã cùng nhau làm xong 1 Project đầu tiên bằng Cocos2d-x v3 cơ đấy. Trong bài này mình sẽ giới thiệu với mọi người 1 Project game khác cũng hay ho không kém, đó là dạng game phá gạch nổi tiếng của điện tử 4 nút ngày xưa.

Hãy cùng tìm hiểu nhé!

Trong game này mình sẽ phải làm các công việc sau:
+ Thêm các đối tượng vào game
+ Thiết lập các thuộc tính vật lý ( dùng Chipmunk cho dễ )
+ Tạo chuyển động của bóng
+ Di chuyển thanh chắn

B1 - Thêm các đối tượng vào game - Thiết lập thuộc tính vật lý

Tạo 1 Project mới với lệnh quen thuộc

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

Và nhớ thêm dòng lệnh USING_NS_CC; vào file HelloWorldScene.h nhé

Mở file HelloWorldScene.h thêm vào các dòng lệnh sau ở phần public

Sprite* ball; // Bóng
Sprite* paddle; // Thanh chắn
Sprite* edgeSp; // Khung màn hình

PhysicsWorld* m_world; // World

void setPhyWorld(PhysicsWorld* world){ m_world = world; };
// Sự kiện Touch
void onTouchMoved(Touch *touch, Event *event);
void onTouchEnded(Touch *touch, Event *event);

bool onTouchBegan(Touch *touch, Event *event);
OK
Mở file HelloWorldScene.cpp, ta phải sửa hàm createScene() 1 chút như sau
// Các lệnh này chắc mình ko cần giải thích nhiều nhỉ
auto scene = Scene::createWithPhysics();
scene->getPhysicsWorld()->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL);
Vect gravity(0.0f, 0.0f); // Vector gia tốc =0
scene->getPhysicsWorld()->setGravity(gravity);  
auto layer = HelloWorld::create();
layer->setPhyWorld(scene->getPhysicsWorld());

Hàm init() xóa hết chỉ để lại các dòng này

 if ( !Layer::init() )
    {
        return false;
    }

auto visibleSize = Director::getInstance()->getVisibleSize();
auto origin = Director::getInstance()->getVisibleOrigin();

//---Xóa hết

return true;

Thêm vào giữa phần "Xóa hết" đoạn code dài sau
// Cũng rất quen thuộc nếu bạn đã đọc các bài trước về phần vật lý sử dụng Chipmunk, mình giải thích một số cái thôi nhé
edgeSp = Sprite::create();
auto boundBody = PhysicsBody::createEdgeBox(visibleSize, PHYSICSBODY_MATERIAL_DEFAULT, 3); // Tạo khung vật lý
boundBody->getShape(0)->setRestitution(1.0f); // Đàn hồi
boundBody->getShape(0)->setFriction(0.0f); // Ma sát
boundBody->getShape(0)->setDensity(1.0f); // Tỷ trọng, mật độ
edgeSp->setPosition(Point(visibleSize.width / 2, visibleSize.height / 2)); // Đặt vị trí, tâm của Box trung giữa màn hình
edgeSp->setPhysicsBody(boundBody); // Đặt physicsBody
boundBody->setContactTestBitmask(0x000001); // Đây là lệnh quan trọng, nếu ko có nó, thì ko có điều gì xảy ra khi có va chạm hết
this->addChild(edgeSp); // Add vào Layer
edgeSp->setTag(0); // Tag==0, để kiểm tra đối tượng khi va chạm thuộc loại nào

ball = Sprite::create("Ball.png", Rect(0, 0, 52, 52));
ball->setPosition(100, 100);
auto ballBody = PhysicsBody::createCircle(ball->getContentSize().width / 2.); // Khung vật lý hình tròn
ballBody->getShape(0)->setRestitution(1.0f);
ballBody->getShape(0)->setFriction(0.0f);
ballBody->getShape(0)->setDensity(1.0f);
ballBody->setGravityEnable(false); // Không set Gia tốc
Vect force = Vect(1010000.0f, 1010000.0f); // Tạo 1 Vector lực tác động theo hướng 45 độ, vì x = y kìa
ballBody->applyImpulse(force); // Đẩy 1 lực vào khung quả bóng
ball->setPhysicsBody(ballBody); // Sét Physic body
ballBody->setContactTestBitmask(0x000001); //
ball->setTag(1);
this->addChild(ball);

// Giải thích tương tự phần ball
paddle = Sprite::create("Paddle.png");
auto paddleBody = PhysicsBody::createBox(paddle->getContentSize(), PHYSICSBODY_MATERIAL_DEFAULT);
paddleBody->getShape(0)->setRestitution(1.0f);
paddleBody->getShape(0)->setFriction(0.0f);
paddleBody->getShape(0)->setDensity(10.0f);
paddleBody->setGravityEnable(false);
paddleBody->setDynamic(false); // Vật tĩnh khi tương tác, ko đàn hồi, ko đổi vị trí
paddle->setPosition(visibleSize.width / 2, 50);
paddle->setPhysicsBody(paddleBody);
paddleBody->setContactTestBitmask(0x000001); // Có tương tác 
ball->setTag(2);
this->addChild(paddle);

Thêm 1 đoạn code tạo 1 em Listener để bắt sự kiện Touch ( dùng cho điều khiển thanh chắn ) trước lệnh Return true;
// Quá quen thuộc rồi
auto touchListener = EventListenerTouchOneByOne::create();
touchListener->setSwallowTouches(true);
touchListener->onTouchBegan = CC_CALLBACK_2(HelloWorld::onTouchBegan, this);
touchListener->onTouchMoved = CC_CALLBACK_2(HelloWorld::onTouchMoved, this);
touchListener->onTouchEnded = CC_CALLBACK_2(HelloWorld::onTouchEnded, this);

_eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener, this);

B2 - Tạo chuyển động của bóng

Tạo 1 chuyển động đầu cho quả bóng bằng lệnh sau ( ở hàm init() phía trên đã tạo rồi đó)

Vect force = Vect(1010000.0f, 1010000.0f); // Tạo 1 Vector lực tác động theo hướng 45 độ, vì x = y kìa

ballBody->applyImpulse(force); // Đẩy 1 lực vào khung quả bóng

B3 - Di chuyển thanh chắn

Xây dựng 3 hàm Touch, vì ta chỉ sử dụng hàm onTouchMoved để di chuyển nên các hàm Touch khác ta để rỗng nhé:

bool HelloWorld::onTouchBegan(Touch* touch, Event* event)
{
return true; // Không dùng nhưng vẫn phải trả lại giá trị True
}

void HelloWorld::onTouchEnded(Touch* touch, Event* event)
{
// Không dùng
}

// Dùng để di chuyển thanh chắn sang ngang, 1 cách đơn giản nhất
void HelloWorld::onTouchMoved(Touch* touch, Event* event){
Point touchLocation = this->convertToWorldSpace(this->convertTouchToNodeSpace(touch));
// Để đơn giản thì dùng lệnh này cho dễ hiểu Point touchLocation = touch->getLocation();
paddle->setPositionX(touchLocation.x); // Đặt vị trí ngang của thanh chắn theo vị trí Touch
}

OK các men!

Chúng ta build và chạy thử luôn nhé,


Vậy là xong bài 17, rất đơn giản phải không, vì toàn kiến thức cơ bản chúng ta đã học phần trước. Tóm lại ở bài này chúng ta làm được một số việc sau:

+ Ôn lại kiến thức về Chipmunk Physics
+ Tạo quả bóng di chuyển = Vector lực, dễ vãi
+ Di chuyển đối tượng = onTouchMoved - Cái này hay và thiết thực nè

Download ResourceClass ở đây

Ở bài sau chúng ta sẽ tìm hiểu cách "đóng gạch" à, tạo gạch, và phá gạch, rồi game Over nữa

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

Bài 18: Game thứ 2 - Breakout - Tạo và phá gạch ( Part 2)

More about

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

Người đăng: share-nhungdieuhay

Hi all!

Bài 15 Chúng ta đang tìm hiểu về Box2D, và xây dựng 1 ứng dụng nhỏ sử dụng physic Box2D. Ở bài này chúng ta nâng cao lên 1 chút, là điều khiển quả bóng, làm cho nó va đập với màn hình. Nội dung chính như sau:


+ Làm chuyển động quả bóng bằng vector + lực
+ Đoán trước hướng di chuyển của quả bóng

Mời bạn cùng theo dõi!

B1 - Làm chuyển động quả bóng bằng vector + lực

Các bạn mở Class đã làm ở bài 15 ra

* Mở file HelloWorldScene.h, phần Public thêm vào đoạn code sau:

// Các biến này, bạn xem các đoạn code dưới sẽ hiểu ý nghĩa
bool existBall;
float ballX;
float ballY;  
int dragOffsetStartX;
int dragOffsetEndX;
int dragOffsetStartY;
int dragOffsetEndY;  
float powerMultiplier; // :Lực
Sprite *points[32];
void defineBall(); // Tạo quả bóng theo Box2D

//Mô phỏng đường đivoid simulateTrajectory(b2Vec2 coord);

//Bắt các sự kiện Touch bool onTouchBegan(Touch* touch, Event* event);
void onTouchMoved(Touch* touch, Event* event);
void onTouchEnded(Touch* touch, Event* event);

* Mở file HelloWorldScene.cpp, Xóa hết code của bài cũ ( để đỡ nhầm lẫn chỉ trừ lại đoạn sau)

if ( !Layer::init() )
{
return false;
}

Size visibleSize = Director::getInstance()->getVisibleSize();
Point origin = Director::getInstance()->getVisibleOrigin();

// ĐÃ XÓA HẾT
return true;


+ Thêm vào chỗ xóa kia Các đoạn code sau:

b2Vec2 gravity = b2Vec2(0.0f, -10.0f); // Vector gia tốc
world = new b2World(gravity);  // Tạo world

//Tạo 1 quả bóng, Lưu tọa độ các điểm đầu và cuối dragOffsetStartX = 0;  
dragOffsetEndX = 0;  
dragOffsetStartY = 0;  
dragOffsetEndY = 0;  
existBall= false;

ballX = 500;
ballY = 200;
powerMultiplier = 10; // Giá trị lực 10
ball =Sprite::create("ball.png");
ball->setPosition(Point(ballX,ballY));
this->addChild(ball);

// Dựng khung bao quanh 3 phía màn hình addWall(visibleSize.width ,10,(visibleSize.width / 2) ,0); // Sàn
addWall(10 ,visibleSize.height ,0,(visibleSize.height / 2) ); //Trái
addWall(10 ,visibleSize.height ,visibleSize.width,(visibleSize.height / 2) ); // Phải


//Bắt sự kiện Touch auto listener = EventListenerTouchOneByOne::create();
listener->setSwallowTouches(true);

listener->onTouchBegan = CC_CALLBACK_2(HelloWorld::onTouchBegan, this);
listener->onTouchMoved = CC_CALLBACK_2(HelloWorld::onTouchMoved, this);
listener->onTouchEnded = CC_CALLBACK_2(HelloWorld::onTouchEnded, this);

_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);

scheduleUpdate(); // Update Scene theo Time


+ Xây dựng hàm addWall như sau, rất giống bài 15, thì vẫn là dựng khung physic body = Box2D mà

void HelloWorld::addWall(float w,float h,float px,float py) {

b2PolygonShape floorShape; // Hình dạng Sàn

floorShape.SetAsBox(w/ SCALE_RATIO,h/ SCALE_RATIO); // Hình vuông, hoặc chữ nhật
b2FixtureDef floorFixture;

floorFixture.density=0;
floorFixture.friction=10;
floorFixture.restitution=0.5;
floorFixture.shape=&floorShape;

b2BodyDef floorBodyDef;
floorBodyDef.position.Set(px/ SCALE_RATIO,py/ SCALE_RATIO);

b2Body *floorBody = world->CreateBody(&floorBodyDef);
floorBody->CreateFixture(&floorFixture);

}

+ Hàm defineBall(), thật ra là khai báo lại việc tạo body physic cho quả bóng ở bài 15, chuyển nó thành 1 hàm riêng thôi

void HelloWorld::defineBall(){
bodyShape.m_radius = 45 / SCALE_RATIO;

fixtureDef.density=10;
fixtureDef.friction=0.8;
fixtureDef.restitution=0.6;
fixtureDef.shape=&bodyShape;

bodyDef.type= b2_dynamicBody;
bodyDef.userData=ball;

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

ballBody = world->CreateBody(&bodyDef);
ballBody->CreateFixture(&fixtureDef);
ballBody->SetGravityScale(10);
}

+ Dựng hàm onTouchBegan

bool HelloWorld::onTouchBegan(Touch* touch, Event* event)
{
// Lưu tọa độ điểm Touch đầu tiên dragOffsetStartX = touch->getLocation().x;
dragOffsetStartY = touch->getLocation().y;

Point touchLocation = touch->getLocation(); // Lấy tọa độ điểm Touch

// Lưu lại
ballX = touchLocation.x;
ballY = touchLocation.y;

// Kiểm tra nếu điểm Touch chưa có quả bóng, thì xóa body bóng đã tạo ở hàm defineBall()
if (existBall){      
world->DestroyBody(ballBody);
}

ball->setPosition(Point(ballX ,ballY)); // Đặt vị trị mới ở ballX ,ballY
return true;
}

+ Hàm onTouchEnded

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

existBall = true;

HelloWorld::defineBall(); // Tạo body quả bóng tại điểm Touch cuối

Point touchLocation = touch->getLocation(); // Lấy điểm Touch

// Lưu điểm Touch cuốidragOffsetEndX = touchLocation.x;
dragOffsetEndY = touchLocation.y;

// Khoảng di chuyển của bóngfloat dragDistanceX = dragOffsetStartX - dragOffsetEndX;
float dragDistanceY = dragOffsetStartY - dragOffsetEndY;

// Tạo chuyển động cho body với 1 vận tốc không đổi có phương và độ lớn = vector có điểm cuối, điểm đầu đã lưu, nhân thêm 1 lực = 10 = powerMultiplierballBody->SetLinearVelocity(b2Vec2((dragDistanceX*powerMultiplier)/SCALE_RATIO,(dragDistanceY*powerMultiplier)/SCALE_RATIO));
}

+ Hàm onTouchMoved

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

}

Bạn build & run thử xem, có thành công ko?, thử kéo quả bóng và thả ra, sẽ thấy quả bóng bắn đi và va đập với thành màn hình, OK nha, sang ngay bước 2

B2 - Đoán hướng di chuyển của bóng

Bạn cần xây dựng 2 hàm sau đây

onTouchMoved

void HelloWorld::onTouchMoved(Touch* touch, Event* event)
{
Point touchLocation = touch->getLocation();

// Lưu lại điểm cuối dragOffsetEndX = touchLocation.x;
dragOffsetEndY = touchLocation.y;

// Khoảng di chuyển của bóng float dragDistanceX = dragOffsetStartX - dragOffsetEndX;
float dragDistanceY = dragOffsetStartY - dragOffsetEndY;

//Gọi hàm mô phỏng đường đi của bóng, vector nhân thêm 1 lực = 10 = powerMultiplier HelloWorld::simulateTrajectory(b2Vec2((dragDistanceX * powerMultiplier)/SCALE_RATIO,(dragDistanceY * powerMultiplier)/SCALE_RATIO));
}

+ simulateTrajectory

void HelloWorld::simulateTrajectory(b2Vec2 coord){
//Định nghĩa physics body bóng HelloWorld::defineBall();

// Tạo chuyển động cho body với 1 vận tốc không đổi có phương và độ lớn = vector truyền vào
ballBody->SetLinearVelocity(b2Vec2(coord.x,coord.y));


// Duyệt mảng point for (int i = 1; i <= 31; i++){

//Trong hàm Step, Giá trị đối số phải bằng gia tốc 10 và powerMultiplier (=10) world->Step(deltaTime,10,10);
points[i]->setPosition(Point(ballBody->GetPosition().x*SCALE_RATIO,ballBody->GetPosition().y*SCALE_RATIO));
world->ClearForces();
}
world->DestroyBody(ballBody);
}

+ Trong hàm init(), thêm vào đoạn code

// Khởi tạo mảng Sprite gồm 31 chấm nhỏ, để biểu diễn đường đi của bóng for (int i = 1 ; i <= 31; i++){
points[i] =Sprite::create("dot.png");
this->addChild(points[i]);
}

Build rồi chạy thử nào, Khi bạn kéo quả bóng sẽ thấy có 1 đường chấm chấm biểu diễn hướng đi của nó. Thả tay ra, quả bóng bay đúng theo hướng đó là thành công.



Vậy là đã xong bài 16, tổng kết lại trong bài này chúng ta học được cách:
+ Tạo lực chuyển động cho 1 đối tượng
+ Biểu diễn đường đi của nó trong world

Mở rộng bài này ra, ta có thể làm được trò chơi Bi-a đấy, các bạn ạ.

Thôi để dành các bài sau nghiên cứu tiếp

P/S: Qua bài 15, 16 các bạn thấy là trong Box2D, sprite bị phụ thuộc vào Body, thiết lập và di chuyển body trước, sau đó mới hiện sprite theo vị trí body. Còn trong Chipmunk thì ngược lại, cài đặt vị trí của Sprite trước, đặt body theo sau. Xem lại các bài trước về Physics.

Chào và hẹn gặp lại.

Bài 17: Game thứ 2 - Breakout ( Part 1)

More about

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

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