Wednesday, July 16, 2014

Hướng dẫn cách Port code từ Cocos2d-x 2 sang Cocos2d-x 3.x ( Update liên tục ...)

Hi mọi người!

Lâu lâu lười viết bài thế, không phải là không còn gì để viết, không còn nguồn để tham khảo. Mà là vì những bài code về sau dung lượng code lớn quá, up lên Blog khá là mất thời gian vì diễn giải khá dài, dù có chia nhỏ từng phần ra cũng hơi oải các bạn ạ. Thời gian để viết Blog cũng đủ cho mình chiến được 1 project tầm trung rồi, vì thế nên các bạn hết sức thông cảm nếu mình có ra bài chậm.

Vậy giải pháp cho vấn đề này như thế nào? Mình xin đưa ra với các bạn 1 vài giải pháp như sau

1/ Nếu bài viết trên Blog, vẫn chia Part 1-2-3 cho tiện, và mình sẽ Copy + Paste chỉ giải thích ( comment ) những đoạn code, phương pháp nào thật sự khó hiểu, hoặc mới chưa từng có trên Blog. Còn lại các bạn nghiên cứu trong từng file .h, .cpp mình sẽ up lên. Trong code cũng chỉ comment đoạn quan trọng, kẻo rối mắt nhé

2/ Code cho phiên bản V3 ngày càng nhiều lên, các bạn có thể tìm trên Github.com bằng các key thích hợp. Tuy nhiên chúng ta không thể bỏ qua 1 nguồn tài nguyên khá lớn từ phiên bản Engine Cocos2dx -2.x được. Sẽ có bạn bảo, code đó làm sao chạy được với Engine 3.x, tất nhiên rồi ( Code 3.1 nhiều khi còn chả chạy được trên V3.2 đó bạn ). Đó là lý do mình viết bài này, chúng ta sẽ Port code từ phiên bản cũ lên phiên bản 3.x rồi Biên dịch = phiên bản mới. Cũng hay đấy chứ.

Lợi ích của việc Port Code này là gì? Theo mình thì có 1 vài lợi ích sau

+ Mở rộng nguồn tài nguyên, chúng ta sẽ có nhiều dự án để thực hành hơn
+ Mã nguồn cũng phong phú hơn do 2.x ra khá lâu rồi
+ Ôn lại được những kiến thức đã học của 3.x. Giúp bạn nắm chắc hơn về cú pháp lệnh của 3.x, sướng nhé
+ Học thêm được một số giải thuật, phương pháp giải quyết bài toán, cấu trúc chương trình mà những người đi trước đã share ( nhiều bộ code full nhé ).
+ Còn gì nữa không nhỉ? tạm thời thế đã :-))

Những ai có thể Port Code?

Những người đã đọc qua 40 bài viết ( có 29 bài học và mấy chú ý ) trong blog này hoàn toàn có thể làm được nhé. Bạn không tự tin ư? Với chính bản thân mình lúc đầu khi bắt tay vào Port code cũng cảm thấy hơi e dè, nhưng chỉ cần qua 1 project thôi là bạn sẽ cảm thấy tự tin lên nhiều. Mình khẳng định chắc chắn đấy.

Như vậy nhé, mình khẳng định lại lần cuối, mọi người hoàn toàn có thể làm được theo những lưu ý dưới đây
( Bài viết sẽ được bổ sung dần dần nhé )

PORT CODE

B1 - Tạo mới Project bằng Engine 3.x, dễ như ăn kẹo

B2 - Không nên copy Class của 2.x đập thẳng vào Class 3.x rồi sửa, rồi build nhé, làm vậy sẽ nhanh chóng bị rối vì rất nhiều lỗi mà ko biết vì sao.

B3 - Ngó nghiêng file AppDelegate.cpp, .h của 2 phiên bản, có đôi chút khác ở phần glview, và phần định hướng Resource, và hàm run scene đầu tiên bạn chỉnh lại 1 chút là được. Phần này ko quá quan trọng, bạn có thể để nguyên của phiên bản 3.x nhé. Có gì mình fix sau

B4 - Trong thư mục Class của 2.x, Tìm 2 file Quan trọng nhất của game - có nhiệm vụ quản lý toàn bộ game, đó là 2 file sẽ được chạy đầu tiên, bạn chỉ cần nhìn trong AppDelegate.cpp là thấy được scene đầu tiên được chạy là của lớp nào thì lớp đó sẽ nằm trong 2 file kia. Thường là thế, không tính đến những cấu trúc file loằng ngoằng do sở thích của người lập trình.

B5 - Trong 2 file quan trọng nhất này, bạn sẽ thấy rất nhiều #Inlude từ các file khác, rất nhiều thuộc tính, nhiều hàm, rối tinh rối mù đúng không. Hãy xem qua để biết được cấu trúc liên kết của các lớp, các hàm trong Project là như nào. Rối quá thì dùng giấy ghi lại thôi.

B6 - Bắt đầu làm việc. Trong Class của 3.x, bạn mở file HelloWorldScene.h, nó là file quan trọng đầu tiên đấy. Bắt đầu copy các thuộc tính, các hàm từ bên file .h quan trọng nhất bên 2.x sang nhé ( cứ copy y nguyên nhé, phần sửa đổi cú pháp lệnh, hàm để dành Bước cuối cùng ).

Sau đó trong file HelloWorldScene.cpp,  bạn tạo ra các khai báo hàm rỗng - kế cả hàm hủy và hàm tạo. Rồi build thử, nếu chạy được nghĩa là OK, nếu không được, lỗi có thể do :

+ Lỗi cú pháp, thư viện hàm đã "lỗi thời". Cách sửa trình bày sau
+ Code bị lỗi do thừa thiếu, hoặc có sử dụng đối tượng của lớp khác mà ko #include, hãy comment nó lại để tránh lỗi đã.
+ Lỗi không giải thích nổi, do VS ( mình toàn xài VS ) báo rất khó hiểu, nên phải dựa vào kinh nghiệm để phán đoán và sửa lỗi. Với 3-4 tháng kinh nghiệm là đủ dùng.

B7 - Khi build được thành công với cặp file này ( .h, .cpp) nghĩa là file .h đã OK ( có thể lỗi về cú pháp thì bạn nhảy đến bước cuối cùng - xem rồi sửa theo ) file .cpp thì toàn hàm rỗng OK là đúng rồi. Giờ làm công việc khó khăn hơn là, bắt đầu xử lý các hàm rỗng đó nhé ( Trong Class 3.x)

+ Phần hàm tạo và hàm hủy, khá dễ. Hàm tạo thì khởi tạo giá trị cho các đối tượng, nếu là con trỏ thì cho nó là Null. Hàm hủy thì giải phóng các biến con trỏ ( những biến mà trong project có sử dụng hàm new nhé )

+ Hàm createScene() đển nguyên, trừ phi bên 2.x có thêm cú pháp khác
2.x nó thế này
CCScene* GameLayer::scene()
{
    CCScene * scene = NULL;
    do 
    {
    } while (0);
    return scene;
// Thường ko copy gì cả nhé

+ Hàm init (), bạn để nguyên như vậy, copy thêm các câu lệnh và hàm nằm trong khối lệnh sau ( trong file 2.x )

bool GameLayer::init() // init của v 2.x
{
   bool bRet = false;
    do 
    {
    CC_BREAK_IF(! CCLayer::init()); // không copy

// Copy các hàm, lệnh ở đoạn này
    bRet = true; // Không copy

    } while (0);
    return bRet;
}

B8 - Rồi, build thử xem có lỗi gì không, khả năng là có lỗi cao, vì trong hàm init sẽ có gọi đến 1 số hàm khác của project, thậm chí gọi tới đối tượng của lớp khác. Bạn hãy comment hết lại những hàm, code phát sinh lỗi. Build cho tới khi thành công thì bắt đầu công đoạn tiếp theo.

B9 - Khi đã build thành công ở bước 8, bạn mở comment từng hàm 1 trong hàm init ( Các hàm này đểu được khai báo rỗng cả rồi nên ko sợ lỗi ) Khi mở hàm nào, hãy copy code từ hàm tương ứng bên 2x chuyển sang, và chú ý xem trong hàm mới copy đó, có gọi tới hàm nào khác không thì bước tiếp theo là copy code của Hàm được gọi đó vào.

Cứ theo từng bước như thế, bạn sẽ mở hết các hàm.

B10 - Trong quá trình mở hàm, bạn sẽ gặp phải các đối tượng của lớp khác, khi này bạn sẽ làm như sau. Copy 2 file .h, .cpp của lớp đó từ 2.x sang 3.x của mình, Nhớ comment lại code, hàm, của 2 file đó rồi thực hiện mở comment như các bước trên. ( Nhớ phải thêm Class mới = VS hoặc Android.MK nhé )

Cứ làm tuần tự như vậy cho đến khi copy mở hết code, hàm từ 2.x sang 3.x. Tất nhiên không chỉ đơn giản như vậy, vì trong code, hàm của 2.x có cú pháp đã lỗi thời không phù hợp với 3.x nên báo lỗi ngay. Vậy thì bạn chỉ việc làm theo các lưu ý của bước cuối cùng sau đây là OK thôi

B11 - Chuyển đổi cú pháp code, cập nhật tham số hàm. QUAN TRỌNG

Vì sang 3.x thư viện có sửa đổi nhiều nên nhiều lớp cũ, hàm cũ không thể dùng được, hãy tham khảo trong bài cpp-test để lấy đúng nguyên mẫu của hàm đó, sửa đổi là xong, Tất cả mình sẽ trình bày bên dưới:

1/ Hầu như các prefix CC đều được loại bỏ khỏi lớp nhé, nêu bạn xóa cái CC này đi cho gọn, thoáng

Ví dụ CCSprite => Sprite  ,v.v...

2/ Thay đổi tên 1 số lớp
Ví dụ cpp, hoặc Point, CCPointMake => Vec2

CCPointZero => Vec2::ZERO

3/ Hàm Touch đơn

Không nhớ 2.x nó thế nào

Chuyển thành

bool onTouchBegan(Touch* touch, Event* event);

4/ Touch đa điểm

2.x void ccTouchesBegan(CCSet* pTouches, CCEvent* event);

3.x void onTouchesBegan(const std::vector<Touch*>& touches, Event *event); 

5/ dạng share

2.x _screenSize = CCDirector::sharedDirector()->getWinSize();

3.x _screenSize = Director::getInstance()->getWinSize();

Đại loại có dạng share ở dầu lệnh static bạn có thể dùng getInstane , hãy chỉ chuột vào lệnh sharedDirector() nó sẽ hiện bảng lệnh nào dùng để thay thế ( VS + VAX nhé )

6/ Hàm callback là dạng hàm được gọi khi thực hiện event nào đó, ví dụ click chuột menu, hoặc trong action sequence hay gặp

2.x (CCObject* pSender)

3.x (Ref* pSender)

7/ Menu
2.x
CCMenuItemSprite * starGametItem = CCMenuItemSprite::create(
                                                                menuItemOff,
                                                                menuItemOn,
                                                                this,
                                                                menu_selector(GameLayer::startGame));
   
3.x
MenuItemSprite * starGametItem = MenuItemSprite::create(
                                                                menuItemOff,
                                                                menuItemOn,
                                                                CC_CALL_BACK_x(GameLayer::startGame,this));
x = 0,1,2,3 tùy thuộc vào số tham số của hàm được gọi

8/ CCArray

Chuyển sang dùng Vector, ví dụ vector<T> vec; hoặc Vector<T> Vec, hai thằng này có vẻ giống nhau nhé, nhưng hàm của chúng có khác nhau đấy.

9/ spriteFrameByName
2.x
CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName()
3.x
SpriteFrameCache::getInstance()->getSpriteFrameByName("")
10/ Create

CCSprite::spriteWithFile() -> Sprite::create();

11/ Action

runAction(CCSequence::actions(CCDelayTime::actionWithDuration(0.5f),
 CCCallFunc::actionWithTarget(this, callfunc_selector(HelloWorld::resetGame)),NULL));

3.x

runAction(Sequence::create(DelayTime::create(0.5f),
  CallFuncN::create(CC_CALL_BACK_x(HelloWorld::resetGame,this)),NULL));
CC_CALL_BACK_x, x  = 0,1,2,3  tùy vào số tham số của resetGame

=> Các action đều chuyển về ::create

12/ CGFloat

=> float
   

13/ Debug Box2D

void draw() - 2.x
virtual void draw(Renderer *renderer, const Mat4 &transform, bool transformUpdated) override; - 3.1

3.2
virtual void draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) override;
Tạm thời như vậy thôi nhỉ, còn 1 vài đoạn code đặc biệt nữa, mình không nhớ hết, vì giờ lục lại các bài code hơi bị nhiều. Có gì mình sẽ bổ sung sau. OK?

Vậy là trong bài này chúng ta đã học được cách để Port code từ phiên bản 2.x sang 3.x mà không quá khó khăn rồi.

Nếu bạn làm theo các bước trên đặc biệt chú ý bước cuối cùng quan trọng thì sẽ không Project 2.x nào làm khó được bạn, Mình đã làm 2-3 project 2.x rồi, thành công cả. Hì

Có thể tóm gọn lại như sau

+ Làm theo từng bước: Sửa cú pháp trước =>Copy file =>đóng - mở comment = > Copy code => đóng - mở comment => Debug => mở từng hàm => Debug => đóng mở comment chỗ lỗi => Debug => KẾT QUẢ

Thế thôi nhỉ, Hẹn gặp lại ở bài sau!

20 comments:

  1. Chào chủ blog..
    Sao lâu ra bài mới thế? Mong mãi không có bài mới ah?
    Cho mình hỏi một chúc được không?
    Mình code game sử dụng tilemap..Mà build ra android map không hiện hết... Mình kiểm tra trên mạng nhiều rồi, thì không nói gì hết... Có gì chia sẽ với.. Cảm ơn nhiều...
    Code của mình đơn giản thôi
    bool HelloWorld::init()
    {
    ..
    auto _test=TMXTiledMap::create("file.tmx");
    this->addChild(_test);
    ..
    }

    ==> Khi bulid không báo lổi, map vẫn hiện bình thường.. Trong logapp của eclipse không thế vẫn đề gì... Nhưng máp hiện không đủ... Cảm ơn bạn nhiều ha.. Có gì giúp mình với

    ReplyDelete
  2. Sau mấy ngày dùng tilemap không được.. Quyết định dùng image cho gọn... đở căng thẳng.. hihi

    ReplyDelete
  3. anh cho em hỏi chút ạ!
    ở v2.0 em thấy có 1 cái là sharedDirector()->getTouchDispatcher() nhưng sang v3.0 thì cái getTouchDispatcher() được thay thế bằng cái gì hay nó nằm ở đâu ạ? em thử đổi cái sharedDirector() sang getInstance() nhưng không được.
    em cảm ơn.

    ReplyDelete
    Replies
    1. auto dispathcher = Director::getInstance()->getEventDispatcher(); // Dùng chung chung

      auto listener = EventListenerTouchOneByOne::create(); // Chia ra các loại Event

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

      dispathcher ->addEventListenerWithSceneGraphPriority(listener, this);

      Delete
  4. các bác ơi, lệnh này port thế nào, chỉ giáo em với, bên dưới là source 2x, em đã bỏ CC ròi.
    startBtn-> initWithNormalSprite (normalStart,pressStart,NULL,this,menu_selector(HelloWorld::gameStart));

    ReplyDelete
    Replies
    1. Làm theo kiểu này

      auto menuItem = MenuItemSprite::create(startButton,activeStartButton,NULL,CC_CALLBACK_1(WelcomeLayer::menuStartCallback, this));

      CC_CALLBACK_x, x là số đối số của hàm menuStartCallback.

      Delete
    2. em làm newbie, sao thấy nó phức tạp quá nhỉ, nhưng đc cái là nó có nhìu thư viện hỗ trợ vật lí

      Delete
    3. mấy cái giải pháp, các bác lấy thông tin ở đâu vậy ? hay do kinh nghiệm code thui ?

      Delete
    4. So sánh, tìm cách thay thế tương đương của những hàm đã lỗi thời trong engine cũ, tổng hợp lại đó là kinh nghiệm khi port code

      Delete
    5. This comment has been removed by the author.

      Delete
  5. bác ơi, còn cái nì nữa, 2.x :
    hightestscore->setString(hestString->m_sString.c_str());
    lỗi ở biến m_sString, biến m_sString có phải là biến nội bên trong tool ko bác ?

    ReplyDelete
    Replies
    1. Trong lệnh trên thì đoạn này có vẻ OK

      hightestscore->setString( ...

      hightestscore có vẻ là 1 đối tượng label nhỉ?

      Còn đoạn này

      hestString->m_sString.c_str()

      bạn phải xét 2 thứ:

      1/ hestString là 1 đối tượng dạng con trỏ, phải xem nó đã được khai báo, khởi tạo ở đâu chưa, nếu chưa, sẽ báo lỗi

      2/ m_sString là một thuộc tính của đối tượng hestString, hãy xem đã khai báo trong lớp tạo nên hestString chưa, nếu chưa thì nó có thể là biến có sẵn của lớp cha của hestString ( nếu có kế thừa )

      c_str(): là hàm của C, C++, đổi sang dạng chuỗi "xxxxxx"

      Delete
    2. thanks bác nha, để e mò mẫn thử

      Delete
  6. This comment has been removed by the author.

    ReplyDelete
  7. bác ơi,

    em đang port từ 2x sang 3x gặp lỗi khó xử. E liệt kê ra thế này.
    Em gọi 1 lớp con từ lớp cha.
    Lớp con:
    ////////////////////////////
    Hose.h
    ////////////////////////////
    class Hose : public Node {
    public:
    Hose();
    Array *hoseList;
    CREATE_FUNC(Hose);
    virtual void onEnter();
    void update();
    void addHose(int num);
    };

    ////////////////////////////
    Hose.cpp
    ////////////////////////////
    #include "Hose.h"
    Hose::Hose()
    {
    ....
    }
    void Hose::onEnter()
    {
    ...
    }
    void Hose::update()
    {
    ...
    }
    void Hose::addHose(int num)
    {
    ....
    }

    ////////////////////////////
    Hose.h (lớp cha)
    ////////////////////////////
    #ifndef __HELLOWORLD_SCENE_H__
    #define __HELLOWORLD_SCENE_H__
    #pragma once
    #include "cocos2d.h"
    #include "Hose.h"
    #include "SimpleAudioEngine.h"
    USING_NS_CC;
    class HelloWorld : public cocos2d::Layer
    {
    public:
    HelloWorld();
    // GameScene();
    virtual void onEnter();
    virtual bool init();
    .........
    Hose *hose;
    .........
    };
    #endif // __HELLOWORLD_SCENE_H__

    ////////////////////////////
    Hose.cpp (lớp cha)
    ////////////////////////////
    ....
    void HelloWorld::onEnter()
    {
    .........
    // hose
    hose = Hose::create();
    this ->addChild(hose);
    .........
    }
    .....

    Em gặp lỗi thế này (báo lỗi trong eclipse).
    ----------------------
    undefined reference to 'Hose::Hose()' Hose.h
    ----------------------
    lỗi ở đoạn này trong Hose.h , CREATE_FUNC(Hose);

    Cách xử lí như thế nào hả bác ? e ngâm mấy ngày chưa xử đc, hic hic
    Em viết như vậy có rõ ràng chưa ?

    ReplyDelete
    Replies
    1. kì lạ thật đó bác admin à, build cho android dùng eclipse thì có lỗi, cùng code đó, build cho win32 thì đc, aaaaaaaaaaaaaaaaaa, chẳng biết tại sao ?

      Delete
  8. ad co link nao nhieu bai hoc va game la voi cocos2d v2.2 k a.?

    ReplyDelete
  9. Lên Github search cocos2d-x, có khá nhiều game.

    ReplyDelete