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ơ.

Saturday, May 31, 2014

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

Hi mọi người!

Vậy là chúng ta đã cùng nhau làm xong 2 Game đơn giản ở những bài trước. Trong bài này mình sẽ hướng dẫn các bạn làm 1 game khó hơn, giống như là Candy Crush, và Bejewer nhé.

À tiện đây mình cũng muốn nói đôi lời. Những ai đang có ý định kiếm mấy game người khác đã chia sẻ code hoặc hướng dẫn làm trên mạng rồi Clone lại, chỉnh sửa tí chút sau đó add quảng cáo rồi tung lên Store để kiếm tiền thì mình có lời như này: XIN ĐỪNG LÀM RÁC các kho ứng dụng theo cách như vậy.

Hãy học làm game một cách chuyên nghiệp, suy nghĩ và sáng tạo ra sản phẩm của riêng mình, mới lạ độc đáo thì sẽ được người dùng đón nhận thôi chứ đừng Clone, nhái, ăn theo => RÁC lắm. Nếu có nhái, ăn theo, thì hãy làm cho nó mới hơn, hay hơn thì hãy làm. Còn không thì tự suy nghĩ và sáng tạo ra 1 game của riêng mình ấy, dù có dở cũng không ai chửi bạn. Chứ đã nhái lại mà còn dở thì..... ăn GẠCH nhá.

Chúng ta bắt đầu bài học thôi.

Để chuẩn bị cho bài học, các bạn cần ôn lại 1 chút kiến thức C++ về mảng 1-2 chiều, con trỏ cấp 1 (*pointer) và con trỏ cấp 2(**pointer), Mối quan hệ giữa con trỏ và mảng nhé, khá quan trọng đó

Trong bài này, chúng ta cần làm các công việc sau đây:

+ Xây dựng Class Sushi, => tạo ra các miếng sushi ở vị trí hàng cột ( thuộc ma trận )
+ Xây dựng màn chơi bằng cách tạo 1 ma trận ( mảng 2 chiều ) chứa các Sushi.
+ Tạo và làm rơi Sushi xuống khi khởi đầu màn chơi

Nhiệm vụ chỉ có vậy thôi. Chúng ta Go nhé

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

B1 - Xây dựng Class Sushi

Mở Class của Project Sushi vừa tạo, mở file AppDelegate.cpp sửa và thêm 1 số lệnh sau

+ Phần include thay HelloWorldScene.h = PlayLayer.h, đơn giản chỉ là ko dùng tên HelloWorld nữa, nghe Amatuer làm sao
+ Thêm đoạn code sau vào trước lệnh director->setDisplayStats(true);

   // Thiết lập độ phân giải
   glview->setDesignResolutionSize(320, 480, ResolutionPolicy::FIXED_WIDTH);

   // Thiết lập đường dẫn tới thư mục w640 trong Resource khi biên dịch
    std::vector<std::string> searchPath;
    searchPath.push_back("w640");
    CCFileUtils::getInstance()->setSearchPaths(searchPath);
    director->setContentScaleFactor(640 / 320);

+ Sửa lệnh  auto scene = HelloWorldScene::createScene(); thành auto scene = PlayLayer::createScene();

Bạn xóa 2 file HelloWorldScene.h và .cpp đi nhé vì bài này chúng ta ko dùng đến nữa, mà tạo hẳn file mới và các lớp mới.

+ Bạn tạo 4 file sau ( file rỗng)
- PlayLayer.h, .cpp
- SushiSprite.h, .cpp

+ Đồng thời mở file sushi.vcxproj ( trong thư mục proj.win32) và add vào 4 file trên nhé. Cách add bạn search HelloWorldScene là biết cách. ( Và phải xóa HelloWorldScene ở trong này luôn nhé ).

Dựng Class Sushi

Mở file SushiSprite.h, chèn vào đoạn lệnh sau

#ifndef __SushiSprite_H__
#define __SushiSprite_H__

#include "cocos2d.h"

USING_NS_CC;

class SushiSprite:public Sprite // Kế thừa từ lớp Sprite nhé

{
public:

static SushiSprite* create(int row, int col); // Tạo 1 Sushi tại vị trí hàng, cột thuộc Ma trận
static float getContentWidth(); // Lấy chiều rộng của sprite sushi, cần thiết cho việc tính toán về sau
CC_SYNTHESIZE(int,m_row,Row); // Vị trí hàng của Sushi trong Ma trận
CC_SYNTHESIZE(int,m_col,Col);  // Vị trí hàng của Sushi trong Ma trận
CC_SYNTHESIZE(int,m_imgIndex,ImgIndex); // Loại Sushi
};

#endif

CC_SYNTHESIZE thì ở các bài trước mình đã giải thích rồi nhé, ko quá khó để hiểu đâu. Hãy dùng ô Search của Blog và search "CC_SYNTHESIZE_READONLY" nhé ra ngay bài 12

File Sushi.cpp, thêm vào 

#include "SushiSprite.h"

USING_NS_CC;

#define TOTAL_SUSHI 6 // Tổng số loại Sushi

// Tạo 1 mảng con trỏ, mỗi con trỏ trỏ tới 1 chuỗi, sushiNormal[i] lưu địa chỉ chuỗi i
static const char *sushiNormal[TOTAL_SUSHI] = {
"sushi_1n.png",
"sushi_2n.png",
"sushi_3n.png",
"sushi_4n.png",
"sushi_5n.png",
 "sushi_6n.png"
};

// Lấy chiều rộng của đối tượng 
float SushiSprite::getContentWidth()
{
    static float itemWidth = 0;
    if (itemWidth==0) {

// Tạo ra 1 sushi từ mảng trên
        auto sprite = Sprite::createWithSpriteFrameName(sushiNormal[0]);
        itemWidth = sprite->getContentSize().width;
    }
    return itemWidth;
}

// Tạo mới 1 Sushi có vị trí row, col, trả về 1 con trỏ kiếu SushiSprite*
SushiSprite *SushiSprite::create(int row, int col)
{
// Tạo mới
SushiSprite *sushi = new SushiSprite();
// Gắn hàng, cột, index
sushi->m_row = row;
sushi->m_col = col;
sushi->m_imgIndex =  rand() % TOTAL_SUSHI; // random loại Sushi từ 0-5 (= index của mảng)
// Tạo hình ảnh từ chuỗi của mảng trên
sushi->initWithSpriteFrameName(sushiNormal[sushi->m_imgIndex]);
sushi->autorelease(); // Tự động hủy khi cần
return sushi;
}

B2 - Xây dựng Màn chơi, tạo hiệu ứng rơi Sushi

Ở bước trên chúng ta đã xây dựng xong Class Sushi, vậy làm thế nào để tạo ra màn chơi với 1 ma trận hàng +cột chứa sushi đây?. Các bạn theo dõi bên dưới nhé

+ Mở file PlayLayer.h ( đang trống) Code file như sau

#ifndef __PlayLayer_H__
#define __PlayLayer_H__

#include "cocos2d.h"

USING_NS_CC;

class SushiSprite; // Chỗ này bạn có thể cho lên #include

class PlayLayer : public Layer
{
public:

    PlayLayer();
    ~PlayLayer();
    static Scene* createScene(); // Tạo màn chơi
    CREATE_FUNC(PlayLayer);
    // Khởi tạo
    virtual bool init() override;
private:

    // Sprite Sheet để lưu các loạt ảnh tạo animation, học ở bài 19
    SpriteBatchNode *spriteSheet;

    // Ma trận 2 chiều dùng con trỏ cấp 2 để lưu SushiSprite* ( Hãy đọc lại phần con trỏ và mảng 2 chiều) 
    SushiSprite **m_matrix;

    // Kích thước Ma trận, hàng, cột
    int m_width;
    int m_height;

    // Vị trí căn chỉnh trên màn hình ( Tọa độ Left Bottom)
    float m_matrixLeftBottomX;
    float m_matrixLeftBottomY;
    
    // Hàm tạo ma trận
    void initMatrix();

    // Tạo Sushi và cho rơi xuống ở vị trí hàng cột bất kỳ
    void createAndDropSushi(int row, int col);

    // Trả lại vị trí tọa độ Point của Sushi tại vị trí hàng + cột trong ma trận
    Point positionOfItem(int row, int col);
};

#endif /* defined(__PlayLayer_H__) */

+ Mở file PlayLayer.cpp để xây dựng các hàm, Code file như sau ( Thực sự là hơi dài nếu tính cả Comment của mình ). Cũng đành copy vậy

#include "PlayLayer.h"
#include "SushiSprite.h"

// Định nghĩa kích thước ma trận 6x8
#define MATRIX_WIDTH (6)
#define MATRIX_HEIGHT (8)

// Khoảng cách giữa cách ảnh Sushi = 1
#define SUSHI_GAP (1)

// Hàm tạo Contructor, tất cả con trỏ = NULL, giá trị =0

PlayLayer::PlayLayer()
: spriteSheet(NULL) //chỗ này là dấu 2 chấm, đằng sau dấu phẩy hết nha
, m_matrix(NULL)
, m_width(0)
, m_height(0)
, m_matrixLeftBottomX(0)
, m_matrixLeftBottomY(0)
{
}

// Hàm hủy thì giải phóng con trỏ

PlayLayer::~PlayLayer()
{
    if (m_matrix) {
        free(m_matrix);
    }
}

// Hàm tạo Scene, đơn giản quá

Scene *PlayLayer::createScene()
{
    auto scene = Scene::create();
    auto layer = PlayLayer::create();
    scene->addChild(layer);
    return scene;
}


// Hàm khởi tạo init()
bool PlayLayer::init()
{
    if (!Layer::init()) {
        return false;
    }
 
    //Tạo ảnh nền
    Size winSize = Director::getInstance()->getWinSize();
    auto background = Sprite::create("background.png");

    // Điểm neo, điểm này sẽ ảnh hưởng tới việc đặt setPosition của sprite, nếu ko đặt điểm neo thì khi setPosition sẽ mặc định lấy điểm trung tâm của Sprite đặt lên màn hình 
    background->setAnchorPoint(Point(0, 1));
    background->setPosition(Point(0, winSize.height)); // Điểm neo như trên dễ đặt Position hơn nhỉ
    this->addChild(background);

 
    // Khởi tạo bộ đệm Sprite Frame
    SpriteFrameCache::getInstance()->addSpriteFramesWithFile("sushi.plist");
    spriteSheet = SpriteBatchNode::create("sushi.pvr.ccz"); // Chú ý hàm này, "pvr.ccz" tập tin đã nén và hõa hóa = TexturePacker, chứa hình ảnh, bạn có thể xem chúng bằng phần mềm TexturePacker, tool PVR View

    addChild(spriteSheet); // Thêm SpriteSheet vào Layer
 
// Kích thước ma trận, 
    m_width = MATRIX_WIDTH; // =6
    m_height = MATRIX_HEIGHT; //=8
 
    // Đặt vị trí ma trận, tính toán 1 chút là ra ấy mà, lấy tổng kích thước màn hình, trừ đi các khoảng cách sẽ ra 2 khoảng bên trái và phải của Ma trận

    m_matrixLeftBottomX = (winSize.width - SushiSprite::getContentWidth() * m_width - (m_width - 1) * SUSHI_GAP) / 2;
    m_matrixLeftBottomY = (winSize.height - SushiSprite::getContentWidth() * m_height - (m_height - 1) * SUSHI_GAP) / 2;
 
 // Khởi tạo 1 mảng

// Kích thước bộ nhớ arraySize = sizeof (kiểu) x kích thước mảng

    int arraySize = sizeof(SushiSprite *) * m_width * m_height;

// Cấp phát bộ nhớ bằng hàm malloc, ( xem lại cách sử dụng hàm này ), ép kiểu về kiểu của biến SushiSprite **, rồi cấp phát với kích thước arraySize 
    m_matrix = (SushiSprite **)malloc(arraySize);
    memset((void*)m_matrix, 0, arraySize); // Đặt tất cả giá trị của mảng là 0, bắt buộc ép kiểu void* của mọi loại mảng
 
    initMatrix(); // Khởi tạo ma trận Sushi
    return true;
}

void PlayLayer::initMatrix()
{

// Duyệt các phần tử ma trận 2 chiều
    for (int row = 0; row < m_height; row++) {
for (int col = 0; col < m_width; col++) {
            createAndDropSushi(row, col); // Tạo và làm rơi Sushi xuống vị trí hàng + cột
        }
    }
}

void PlayLayer::createAndDropSushi(int row, int col)
{
    Size size = Director::getInstance()->getWinSize();
 
    SushiSprite *sushi = SushiSprite::create(row, col); // Gọi đến hàm tạo ra Sushi của lớp SushiSprite
 
    // Tạo animation, or Action?
    Point endPosition = positionOfItem(row, col); // Lấy tọa độ Point từ row, col truyền vào
    Point startPosition = Point(endPosition.x, endPosition.y + size.height / 2); // (y) Điểm đầu = Điểm Cuối + 1 khoảng nửa màn hình
    sushi->setPosition(startPosition);

    float speed = startPosition.y / (2 * size.height); // tốc độ

    sushi->runAction(MoveTo::create(speed, endPosition)); // Di chuyển rơi xuống

    // Thêm vào Spritesheet
    spriteSheet->addChild(sushi);


// Thêm sushi vào mảng, chỗ này là cách quy mảng 2 chiều về mảng 1 chiều nhé, a[i][j] = a[i*COL + j]

    m_matrix[row * m_width + col] = sushi;
}

// Tọa độ Point từ vị trí row, col
Point PlayLayer::positionOfItem(int row, int col)
{
    float x = m_matrixLeftBottomX + (SushiSprite::getContentWidth() + SUSHI_GAP) * col + SushiSprite::getContentWidth() / 2;
    float y = m_matrixLeftBottomY + (SushiSprite::getContentWidth() + SUSHI_GAP) * row + SushiSprite::getContentWidth() / 2;
    return Point(x, y);
}

Xong phần Code, hãy build và chạy thử xem thế nào?



Ngon rồi

Tổng kết lại ở bài này chúng ta học được :
+ Tạo Class mới ( Sushi ) đơn giản bằng C++
+ Ôn lại kiến thức về mảng, ma trận cấp 2, con trỏ đơn, con trỏ 2 cấp
+ Tạo ma trận Sushi
+ Tính toán 1 chút về cách đặt ma trận trên màn hình
+ Tạo Action rơi các sushi xuống ở màn chơi

* Lưu ý 1 chút:

1/ Bạn thấy rằng hình như các sushu + background có vẻ tràn ra màn hình, kích thước không được hợp lý lắm. Thì phải thôi, nguyên nhân khiến bị như thế này là

+ Trong file AppDelegate.cpp, ta chỉ setting chỉnh kích thước trên mobile thôi, chứ ko phải cho window

Các bạn thử chạy trên máy thật hoặc máy ảo xem như nào. ĐT mình mới chêt nguồn rồi. và máy ảo thì nặng quá, và thường là chạy không đúng, hay force stop.

2/ Có vẻ hàm rand() không hiệu quả ( trên win) thì phải, chạy đi chạy lại, thì loại Sushi vẫn thế. Không biết trên máy ĐT thế nào?

Mình kết thúc bài này ở đây nhé.

Download:

+ Class
+ Resource
+ Texture, mở bằng TexturePacker ( hướng dẫn )

Chào và hẹn gặp lại các bạn trong những bài sau

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

Wednesday, May 28, 2014

Lưu ý khi sử dụng phiên bản Cocos2d-x 3.1

Hi!

Ở các bài trước, mình đã báo 1 tin vui với các bạn là đã có phiên bản cập nhật mới nhất của Cocos2d-x 3.1 với một số điểm mới. Mình cũng háo hức down về trải nghiệm, nhưng khi build những Project cũ thì báo lỗi. : ((. Chả hiểu sao vì các bản 3.0 ( RC0, RC1 ) chỉ việc down về và chiến thôi.

Sau khi mình thử tạo mới 1 project, build lại thì thấy chạy ngon lành, ẹc. => Rút ra 1 EXP

+ Với Project của phiên bản cũ tạo ra ( trường hợp này 3.0 ) thì chỉ dữ lại Class + Resource.
+ Dùng bản 3.1 tạo new Project, rồi đập Class + Resource vào, rồi build là chắc ăn nhất, không lỗi trừ khí Engine nó lên hẳn bản v4, v5 thì cần xét lại code



Đây là 1 bài nhỏ thôi, để mọi người lưu ý khi xài bản 3.1 thôi chứ không phải câu VIEW

Hẹn gặp lại các bạn ở các bài sau hay ho, hấp dẫn hơn.

Thanks!

Cách xài chùa phầm mềm TexturePacker 3.3.4 vĩnh viễn

Hi all!

Lâu lâu không viết bài, cũng thấy ngứa ngáy tay chân. Cơ mà tại sếp dạo này chăm chỉ ở nhà, cứ dí việc vào đầu, thành thử không rảnh để nghiên cứu tiếp. Tuy nhiên hôm nay mình sẽ tranh thủ hướng dẫn cho mọi người cách làm 1 thứ rất hay ho.

Hẳn các bạn đã biết, trong Làm game thì ngoài ý tưởng, code nguồn, còn 1 thành phần rất quan trọng khác đó là HÌNH ẢNH, âm thanh. Game bạn có thể không cần âm thanh, nhưng Hình ảnh thì chắc hẳn không thể thiếu.

Mình xin giới thiệu với mọi người 1 phần mềm quản lý file Hình ảnh rất tuyệt vời đó là TexturePacker - http://www.codeandweb.com/texturepacker

Một số tính năng chính của nó:

+ Tạo và quản lý các Sprite Sheet
+ Nén hình ảnh, tối ưu hóa cho chương trình
+ Hỗ trợ nhiều nền tảng Engine nổi tiếng 2D và 3D
+ Hỗ trợ nhiều định dạng: PNG, PSD, SWF, v..v..
+ Publish ra nhiều định dạng phù hợp với các loại Engine ( Plist, pvr, ccz ,....)

Quá tuyệt vời phải không, và giá nó cũng không quá cao ~800k VNĐ thôi. :))




Tất nhiên mình giới thiệu phần mềm này không phải để các bạn mua nó của mình, vì mình có bán đâu. Mà là vì theo truyền thống của người Việt Nam chúng ta vẫn thích dùng đồ chùa hơn. Nên sau đây mình sẽ hướng dẫn các bạn cách đề xài chùa phần mềm rất ngon này.

Các bạn download bản mới nhất ( 3.3.4) tại trang chủ theo Link ở trên. Cài đặt như bình thường thôi, Click and Next là xong.

Bạn bật phần mềm lên, có 2 trường hợp xảy ra

1/ Bạn cài lần đầu

Nó sẽ tự động lựa chọn cho bạn chế độ Trial Pro 6 ngày ( dùng thử bản Pro trong 6 ngày ). Chế độ này bạn được full chức năng, không bị hạn chế bất cứ điều gì. Rất ngon, nhưng chỉ 6 ngày thôi ( hix ) .

2/ Bạn đã cài đặt và dùng hết 6 ngày, bật lên chỉ hiện ra lựa chọn sau














Bạn chỉ được chọn Lite version, với nhiều hạn chế ví dụ, hình ảnh sẽ bị đóng dấu, giới hạn định dạng file,v..v...

(Bản Lite bị giới hạn - rất giống bản Pro nhưng có dòng chữ phái dưới bên phải, chức năng bị giới hạn)



Vậy làm thế nào để được xài chùa mãi mãi đây? Bạn chỉ cần làm theo các bước sau đây:

B1: Bạn mở Registry Editor lên ( Run> gõ vào regedit)
B2: Tìm tới khóa sau :

HKEY_CURRENT_USER\Software\code-and-web.de\TexturePacker\licensing\data




Chú ý vào 2 dòng mình đánh dấu đỏ ( bên phải )
+ expiryDate : là ngày hết hạn, như mình đã cài, và bị hết hạn vào ngày 04-05, Click đúp vào đó và sửa thành 2999-04-05 chẳng hạn. Thế là xong, never hết hạn

+ licenseType: Essential, nghĩa là bạn đang dùng bản Lite, hãy Click vào và sửa thành trial 

Để chắc ăn, bạn có thể thực hiện thêm Bước 3

B3: Chỉnh trong Registry khóa sau về false


Xong rồi đó 

Chúng ta sẽ được dùng trial phiên bản Pro vĩnh viễn nhé

(Hãy chú ý thời hạn trial của phần mềm qua ảnh bên dưới )



những 359711 ngày ~ 100 năm ( Chỉnh 1000 năm cũng được )

Thử Test nào, xem có bị gì không, Oh NOOOOOOOOOO!!!!!!!!

(Đã bị đóng dấu "kiểm dịch")

Nhưng các bạn đừng lo, mình đã tìm ra được 1 cách khắc phục bệnh này. Lúc đầu cũng loay hoay, rầu lòng phết, xong cuối cùng cách của mình cũng khá đơn giản. 

Bạn chú ý ở hình dưới này, cái expiryDate là ngày 2014-04-05, đến thời điểm này 28/5, đã bị hết hạn. Phần mềm nó lưu lại ở đâu đó ( chịu ko tìm ra ). Do đó nó sẽ vẫn đóng dấu "kiểm dịch" khi ta Publish, mặc dù thời gian Trial là 100 năm ( khi chỉnh ở trên )


Mình sẽ bày với các bạn 1 cách đơn giản đó là, CHỈNH THỜI GIAN CỦA HỆ THỐNG về trước thời điểm hết hạn này là xong, ( Bạn tích vào đồng hồ trên Startbar Window ấy, chọn dòng rồi Change date and time settings, rồi chỉnh thôi) ví dụ mình chỉnh giờ hệ thống về thời điểm 2013-04-05 , lùi hẳn 1 năm đi cho ăn chắc. Và đây là kết quả sau khi chỉnh thời gian. ( Nhớ là khi chỉnh time xong, bạn phải tắt phần mềm TexturePacker đang chạy rồi chạy lại nhé, mới có hiệu quả )


Giờ thì dùng thoải mái rồi, không bị đóng dấu, ko bị giới hạn tính năng, không giới hạn thời gian. Mình test được mấy ngày mà chưa bị vấn đề gì. Mọi người cùng Test nhé.

Việc chỉnh thời gian có thể khiến 1 số phần mềm gặp lỗi khi chạy đó, các bạn chú ý nhé. Tuy có hơi phiền 1 chút khi chỉnh thời gian nhưng giá trị mang lại khá lớn nhỉ, 800K, hehe. Khi nào làm game có tiền Donate sau cũng được. Chúc các bạn thành công.

Bên lề 1 chút: 

+ Bộ TexturePacker + Physics Editor của CodeAndWeb thực sự là 1 bộ đôi hoàn hảo để làm game 2D, thậm chí 3D, nhưng tiếc là Physics Editor chưa có bản C.Rack nào trên mạng hết.
+ Bạn nào có quen cao nhân Hack, nhờ Crack giúm nhé, nếu không có thể mua chỉ với 448K/năm, hỗ trợ thanh toán bằng tiền Việt tại đây http://sites.fastspring.com/codeandweb/product/all

Ngoài ra các bạn có thể tìm hiểu phần mềm tạo Sprite Sheet khác là ShoeBox, cũng rất hay, có thêm cả mấy chức năng tách ảnh từ Sprite Sheet, các bạn hãy tìm hiểu thêm.

Về phần mềm Physics, mình giới thiệu 1 số phần mềm sau

+ R.U.B.E ( có lẽ là hay nhất ) 30$ mua bằng Paypal
+ Physics Editor ( hay thứ nhì ) 448K
+ Physics Body Editor ( open Source )
+ TS4 ( free, online ) http://ts4.us/tools/box2deditor.html

Các bạn lựa chọn nhé.

ĐÃ CÓ BẢN PATCH cho TP 3.5.3 + PE 1.0.9

Cảm ơn bạn vn kakalat đã cung cấp bản Patch tốt nhé

TexturePacker 3.5.3 nhé, DOWNLOAD tại đây ( Down về quẳng vào thư mục cài đặt ) - Chú ý là win 64 hay win 32 đều dùng bản cài TexturePacker 3.5.3 cho win32 nhé

Physics Editor 1.09, tại đây ( Down về sửa tên lại thành PhysicsEditor rồi quẳng vào thư mục cài đặt là xong )

Trọn bộ Phần mềm TP 3.5.3 + PE 1.0.9 và Patch tại đây



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

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)