Cocos2d-x 3.2 Final

Cập nhật lên phiên bản Cocos2d-x 3.2. Bổ sung 3D sprite, 3D animation, game controller, fast Tilemap,..v...v... Cập nhật thường xuyên nhé

Cocos2d-x 3.0 Final

Cocos2d-x là Engine lập trình game đa nền tảng phổ biến nhất trên thế giới. Tính năng mạnh mẽ, dễ sử dụng, miễn phí, mã nguồn mở.

Cocos2d-x V3.0 Final

Hướng dẫn chi tiết, đầy đủ bằng Video, Blog. Cộng đồng hỗ trợ rộng lớn. Sản phẩm phong phú mọi nền tảng

Game Badland

Một trong những game nổi tiếng nhất được tạo ra bởi Engine Cocos2d-x.

Cocos2d-x V3.1

Cập nhật phiên bản Engine Cocos2d-x V3.1 bổ sung thêm nhiều tính năng. Sửa các lỗi nhỏ của các phiên bản trước

Contra Evolution - KONAMI

1 Game rất hay không thể bỏ qua, 1 vé trở về với tuổi thơ.

Wednesday, May 28, 2014

Bài 19: Sprite Sheet Animation trong Cocos2dx-3

Hi!

Các bài trước chúng ta đã học và làm quen với các Sprite và Action cơ bản. Nhưng các bạn thấy rằng hầu hết các sprite đó trông có vẻ rất đơn điệu, chúng không hề có sự "cử động" - (animation) nào mặc dù chúng vẫn có "hành động" (action). Các bạn cần phân biệt Animation và Action nhé. Animation có thể hiểu ngắn gọn là những cử động của cơ thể nhân vật. Còn Action là những hành động của nhận vật để làm công việc gì đó. Đôi khi 2 khái niệm này cũng khá nhập nhằng. Hix.

Animation trong Cocos2d-x-3 có 2 loại
+ Sprite Sheet Animation: Tạo cử động bằng 1 loạt Sprite ảnh nối tiếp nhau
+ Skeleton Animation: Tạo cử động dạng khung xương

Bài này mình sẽ giới thiệu với mọi người loại Animation thứ nhất: Sprite Sheet Animation. Nội dung bài gồm:

+ Pack ảnh (texture paker) là gì? vì sao lại cần dùng pack ảnh
+ Cách tạo ra 1 Pack ảnh .Plist, prv.ccz
+ Cách import pack ảnh vào game và tạo Animation

Mình bắt đầu luôn đây!

B1- Pack ảnh là gì? dùng để làm gì

Khái niệm: 1 Pack ảnh bao gồm nhiều ảnh đơn gép lại với nhau, và có đi kèm 1 file để lưu thông số của từng ảnh. Thậm chí chỉ gồm có 1 file ( ví dụ pvr )
Dùng để làm gì? Quản lý file ảnh sẽ đơn giản hơn cho 1 project. Thường để nhóm các ảnh của một Animation, hoặc nhóm nhiều ảnh của chương trình lại, tối ưu chương trình, Ngăn chặn việc ăn cắp hình ảnh, và nhiều tác dụng khác.
- SpriteSheet có lẽ là 1 trường hợp riêng của Pack ảnh, gồm những hình được sắp xếp liên tiếp, có thứ tự của 1 chuyển động nào đó.
- Và đôi khi cũng nhập nhăng 2 cái này luôn, gọi chung hết là Sprite Sheet ( cứ hiểu là 1 nhóm ảnh vậy ). Haizzz

* Lưu ý, những định nghĩa hay tác dụng của pack ảnh do mình tự suy diễn nhé. Cũng ko biết phải tìm ở đâu. :-)). Ai biết rõ hơn thì chỉ với.

B2 - Cách tạo ra 1 pack ảnh .Plist, hoặc .Pvr

Bạn sử dụng chương trình TexturePacker ( hỗ trợ tốt cho Cocos2d) để tạo Pack. Do mình ko có license nên Publish ra sẽ báo lỗi. Chán ghê, mua full 2 bản TexturePacker và Physics Editor mất 1,1 triệu VNĐ. Haizzz, chưa kiếm được tiền từ game, mới học làm game mà đã chuẩn bị rút ví rồi.

Ngoài ra có 1 chương trình khác là ShoeBox cũng tạo được TexturePack nhé, các bạn search là thấy, Cài Adobe Air để chạy được phần mềm

Thôi chúng ta đi vào phần chính, giả sử đã tạo được pack ảnh từ TexturePacker nhé. Có khá nhiều định dạng Pack, nhưng bài mình tìm được là dạng .PNG + .PLIST

(Đã tìm được cách dùng FREE 4EVER phần mềm TexturePacker nhé, tuyệt vời ông mặt trời, mình sẽ hướng dẫn ở bài sau) . Đang test 1 thời gian xem lỗi gì ko?

B3 - Import pack ảnh vào game, tạo Animation

- Tạo 1 project mới tên animation nhé, nhớ thêm USING_NS_CC; vào phần #include của file HelloWorldScene.h
- Copy file Resource từ đây vào thư mục Resource
- Bắt đầu nghiên cứu code

* Mở file HelloWorldScene.h, Thêm vào đoạn code sau

public:

    HelloWorld(); // Hàm tạo
    ~HelloWorld(); // Hàm hủy
    virtual void onEnter(); // Hàm chồng ( override, not husband)
    // Bắt sự kiện Touch
    bool onTouchBegan(Touch* touch, Event* event);
    void onTouchMoved(Touch* touch, Event* event);
    void onTouchEnded(Touch* touch, Event* event);
    // Dừng lại
    void bearMoveEnded();

private:
    Sprite *bear; // Sẽ chứa ảnh con Gấu
    Action *walkAction; // Bước đi
    Action *moveAction; // Di chuyển
    bool moving;

* Mở file HelloWorldScene.cpp, Bạn định nghĩa 2 hàm tạo và hàm hủy như sau

HelloWorld::HelloWorld()
{
    moving =false;
}

HelloWorld::~HelloWorld()
{
    if (walkAction)
    {
        walkAction->release(); // Giải phóng con trỏ
        walkAction = NULL;
    }
}

Trong hàm init() xóa hết chỉ trừ return true và đoạn này

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

// Xóa hết

return true;

Thêm đoạn code sau vào phần đã xóa ở trên

// Bước 1, Nạp file .plist vào bộ đệm SpriteFrameCache, tạo 1 sheet = SpriteBatchNode, spritesheet để nạp 1 loạt các ảnh nằm trong 1 pack nhiều ảnh

SpriteFrameCache::getInstance()->addSpriteFramesWithFile("AnimBear.plist");
auto spriteSheet = SpriteBatchNode::create("AnimBear.png");
this->addChild(spriteSheet);

// Bước 2, Nạp frame từng frame từ bộ đệm SpriteFrameCache vào 1 Vector ( giống mảng)

Vector<SpriteFrame*> aniframe(15); // Khai báo 1 vector kiểu SpriteFrame, với size = 15

char str[50]={0}; // chuỗi trung gian để đọc tên ảnh trong pack

for(int i =1;i<9;i++) // Lặp để đọc 8 ảnh trong pak
{
sprintf(str,"bear%d.png",i); // Đọc vào chuỗi str tên file thứ i

// Tạo 1 khung, lấy ra từ bộ đệm SpriteFrameCache với tên = str
auto frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(str);

aniframe.pushBack(frame); // Nhét vào vector

}

// Bước 3, Tạo Animation từ Vector SPriteFrame

// Tạo khung hình animation từ vector SpriteFrame
auto animation = Animation::createWithSpriteFrames(aniframe,0.1f);
// Tạo ảnh 1con gấu
bear = Sprite::createWithSpriteFrameName("bear1.png");
// Đặt vị trí giữa màn hình thôi
bear->setPosition(Point(visibleSize.width/2, visibleSize.height/2));

// Tạo Action Animate ( hoạt họa ) bằng cách gọi hàm create của lớp Animate, Hãy tưởng tượng thế này, bạn có 8 cái hình ảnh nằm trên 8 trang giấy, lật nhanh 8 trang => ảnh chuyển động của nhân vật. Cái hàm create của lớp Animate có tác dụng "lật trang"gần giống thế, sẽ duyệt qua các khung hình của animation tạo ra ở trên

walkAction = RepeatForever::create(Animate::create(animation));
walkAction->retain(); // Hàm này chưa hiểu ý lắm
spriteSheet->addChild(bear); // Thêm ảnh con gấu tạo ở trên vào spritesheet

+ Dựng hàm onEnter()

void HelloWorld::onEnter()
{
Layer::onEnter();  //  Phải gọi hàm onEnter của Layer, lớp cha của HelloWorld

// Đặt Listener khi vào game
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);


}
+ Xây dựng các hàm Touch

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

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

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

// Lấy điểm Touch
auto touchPoint = touch->getLocation();
touchPoint = this->convertToNodeSpace(touchPoint);

//Vận tốc= 480px / 3 giây;
float bearVelocity = 480.0/3.0;

// Khoảng di chuyển
Point moveDifference = touchPoint - bear->getPosition();
float distanceToMove = moveDifference.getLength(); // Khoảng cách thực
float moveDuration = distanceToMove / bearVelocity; // Thời gian di chuyển

// Quay đầu tùy theo khoảng cách âm hay dương
if (moveDifference.x < 0)
{
bear->setFlippedX(false); // Quay đầu
}
else
{
bear->setFlippedX(true); // Quay đầu

bear->stopAction(walkAction);
bear->runAction(walkAction); // Thực hiện cái Animate cử động nhân vật
// Di chuyển tới điểm Touch, trong khi vẫn thực hiện Animate
moveAction = Sequence::create(MoveTo::create(moveDuration,touchPoint),
CallFuncN::create(CC_CALLBACK_0(HelloWorld::bearMoveEnded, this)),NULL);
bear->runAction(moveAction);
moving = true;

}

+ Hàm

void HelloWorld::bearMoveEnded()
{
bear->stopAction(walkAction); // Dừng việc bước đi
moving = false;
}

Build ra và run




Vậy là chúng ta đã kết thúc bài 19 khá dài, Trong bài này chúng ta đã biết cách
+ Tạo khung hình với Vector SpriteFrame
+ Tạo animation từ SpriteSheet

Download file nguồn

Sprite Sheet tạo ra Animation khá hay và đơn giản nhưng nó có một nhược điểm khá lớn đó là sẽ tốn bộ nhớ để load các ảnh spritesheet.

Và chắc sẽ có bạn thắc mắc tạo khung physic body cho các nhân vật chuyển động như thế nào.? Các bài sau sẽ trả lời cho bạn nhé.

P/S: Trong bài này có 1 bug: là khi di chuyển con Gấu tới 1 điểm, nếu ta lick đúp sẽ thấy có lúc con gấu sẽ không bước chân mà chỉ trượt đi. Mọi người tìm cách fix lỗi giúp nhé.

Xin chào và hẹn gặp lại!

Bài 20: Học làm game thứ 3: Sushi Crush - Like Candy Crush or Bejewer ( Part 1)

Saturday, May 24, 2014

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

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.

Thursday, May 22, 2014

1 Tin vui - 1 Tin buồn

Hix, Chào mọi người!

Không biết còn ai theo mình đọc tới bài này không nhỉ? Món lập trình vốn đã khô khan, lập trình game lại càng khó nuốt. Lúc mới đầu hẳn nhiều bạn cũng háo hức vào tìm hiểu, nhưng rồi theo thời gian đam mê giảm dần, sự hóc búa và lượng kiến thức tăng dần, đã có một vài người ra đi không trở lại. Đã thế món này cũng khá kén độc giả, không phải ai cũng hào hứng ghé vào đọc, có khi chỉ ghé qua +1 rồi lại đi ra, haizzz!

Sau 17 bài viết cơ bản này chắc các bạn cũng đã nắm được những điều căn bản trong Cocos2dx-V3 đủ để làm vài game đơn giản rồi nhỉ. Nhưng để làm được những game hay và hấp dẫn chúng ta còn phải học thêm nhiều thứ nữa ví như: thuật toán, animation, hiệu ứng, nâng cao hơn..v.v...

Thật đáng tiếc là từ trước tới nay, tài liệu làm game, viết game, chia sẻ trên mạng vốn đã ít, tài liệu bằng tiếng Việt lại càng khan hiếm hơn. Thật ra cũng có nhiều lý do:

+ Chỉ có tài liệu căn bản + nâng cao cho những bài, những game đơn giản, hoặc đã qua thời kiếm được tiền cho tác giả, nên mới được Share Code.
+ Không ai Share Project đang phát triển của họ cả. Làm vậy chẳng khác đem nồi cơm nhà mình đi cho hàng xóm.
+ Việt Nam mình cũng khan hiếm người làm game thực thụ để sống, đa phần là tay trái. Kiến thức thật sự không thể bằng các bạn nước ngoài nên hầu như ít có Tut chất lượng.
+ Tut tiếng Anh cũng kha khá nhưng hầu như là Tút cũ, vì họ cũng phải rành thời gian mà học phiên bản mới chứ.

Và đây là 1 Tin buồn cho các bạn
- Ở bài trước mình có giới thiệu với mọi người trang chia sẻ Video hướng dẫn Cocos2dx - V3 khá hay và bổ ích, nhưng đến thời điểm này đã TẠM DỪNG được 2-3 tuần nay rồi, chán ghê. Mất đi 1 nguồn bổ sung kiến thức. Không biết tác giả có bị sao không? Hi vọng là trong thời gian tới lại được tái khởi động.

Để tránh cho các bạn khỏi sự nản lòng, sau đây là Tin vui từ trang chủ Cocos2d-x.org
- Đã có phiên bản nâng cấp của V3 là Cocos2d-x V3.1 RC0: với những nâng cấp đáng giá sau
+ Sprite3D:
+ VideoPlayer
+ Nhiều thứ khác hấp dẫn lắm
ChangeLog đầy đủ ở đây 

=> Vậy là Engine vẫn tiếp tục được hỗ trợ và phát triển từ Team, hi vọng trong thời gian tới, nó sẽ hoàn thiện hơn, giúp chúng ta phát triển game 2D, và 3D ( chắc sẽ tiến tới thôi ) dễ dàng hơn.

Nói thêm chút Cocos2D-x V2 dừng lại ở 2.2.3 rồi nhé, và ko có cập nhật mới nữa. Anh em nên chuyển lên 3.x để tiếp cận với xu hướng mới nhé.

Thanks tất cả đã đọc bài.

Hãy tham khảo Các bài học của mình

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

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)

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

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)

Monday, May 19, 2014

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

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 )

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

Hi mọi người!

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

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

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

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

B1 - Phân tích file .JSON

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

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

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

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

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

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

B2 - Cách Import vào Game

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

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

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

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

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

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

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

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

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

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

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

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

+ Xét hàm onTouchBegan()

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

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

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

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

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



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

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

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

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

Saturday, May 17, 2014

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)

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)