Hiển thị các bài đăng có nhãn Box2D. Hiển thị tất cả bài đăng
Hiển thị các bài đăng có nhãn Box2D. 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 16: Box2D - Một thư viện vật lý khác của Cocos2d-x (Part 2)

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

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