Thursday, July 3, 2014

Bài 25 - Tìm hiểu về hiệu ứng Parallax Scrolling trong Cocos2d-x 3.x

Hi các bạn! 

Lang thang trên trang chủ của cocos2d-x gặp được 1 bài TUT về Parallax Crolling khá hay, mình mang về Blog này để mọi người cùng ngâm cứu. Có thể nhiều bạn sẽ nghĩ mình đang "ăn cắp" bài viết của người khác làm bài của mình. Oh no, biết nói thế nào nhỉ, ngay từ ban đầu blog ( bài 1,2 gì đó) mình đã nói rõ, Code, Resource trong Blog này mình ko hề Code ra, mà chỉ nhặt nhạnh trên mạng, rồi đem về trình bày và diễn giải lại trên Blog để tự học và mọi người cùng đọc. Có nhiều bài mình thực hành code xong viết lại thành TUT, hoặc có 1 vài bài mình lấy Tut người khác, nhưng sẽ trình bày lại theo cách hiểu của mình sao cho dễ hiểu và gần gũi với cả người mới bắt đầu tìm hiểu về Engine, và đây là 1 bài như thế. Sẽ không có gì quá đáng phải không, mà thôi, ai đánh giá thế nào thì kệ chứ, việc của chúng ta là HỌC, là NGÂM CỨU làm game chứ ko phải VIẾT SÁCH, VIẾT TUT! 

OK, trước tiên chúng ta cùng tìm hiểu 1 hiện tượng vật lý cực kỳ hay gặp trong cuộc sống, đó là Parallax Scrolling

Parallax Scrolling  là gì? Dùng trong trường hợp nào?

Từ Parallax có nghĩa là Thị sai trong tiếng Việt đó
Bạn gõ Parallax hoặc Thị sai lên Google là sẽ có kết quả trả về lập tức, đây là trang Wiki

Hiện tượng Parallax Scrolling là hệ quả được tạo ra bởi thị sai ( Parallax ) của mắt với các vật thể xa, gần, Vật càng xa, góc nhìn càng nhỏ, vật càng gần thì góc nhìn càng rộng, nêu khi 2 vật xa gần di chuyển cùng 1 vận tốc ( hoặc khác vận tốc ), thì ta sẽ thấy vật ở gần (có vẻ ) di chuyển nhanh hơn vật ở xa.

Chúng ta có thể hiểu Hiệu ứng Parallax Scrolling là hiệu ứng các vật chuyển động tương đối với vận tốc tỷ lệ nghịch với khoảng cách từ vật tới mắt người quan sát, Vật gần di chuyển nhanh, vật xa di chuyển chậm

Ví dụ về Prallax trên HTML5 
Ví dụ trong cuộc sống hàng ngày
+ Khi bạn đi tàu, xe, nhìn ra ngoài cửa sổ sẽ thấy, Cột điện "lùi lại rất nhanh", cánh đồng "lùi lại vừa phải", Mây "lùi lại chậm" Mặt trời "lùi lại rất chậm". Nghĩa là vật càng gần mắt người quan sát thì chuyển động càng nhanh, càng ở xa thì chuyển động càng chậm. ( mặc dù các vật trên có thể coi là cùng vận tốc - đứng im - so với mắt người đang đứng im ) đừng ai bắt bẻ về "sự đứng im" và chuyển động nhé. cũng chỉ tương đối thôi, quan trọng là mắt người quan sát thấy thế nào thôi.

Ứng dụng trong game:

Hiệu ứng Parallax Scrolling hay dùng trong game 2D để tạo chiều sâu cho game, bạn hãy vào trang web bên trên trải nghiệm nhé, 

Tạo hiệu ứng này như thế nào?

Dựa vào định nghĩa trên, về lý thuyết thì ta có thể làm như sau trong game

+ Ta tạo ra Các layer chồng lên nhau
+ Layer nào ta định nghĩa ở xa thì ta cho dịch chuyển với tốc độ chậm
+ Layer nào định nghĩa ở gần thì ta dịch chuyển với tốc độ nhanh hơn
+ Các vận tốc này thường cùng phương cùng chiều, độ lớn tỷ lệ với khoảng cách đến mắt quan sát

Lý thuyết có vẻ đơn giản, thực tế Code thì sao?

Chúng ta cùng bắt đầu nào!


Bạn tạo 1 Project mới đặt tên Parallax

Xây dựng Class InfiniteParallaxNode như sau

InfiniteParallaxNode.h

class InfiniteParallaxNode : public ParallaxNode
{
public:
    static InfiniteParallaxNode* create(); // Tạo InfiniteParallaxNode
    void updatePosition(); // Update lại vị trí
};

InfiniteParallaxNode.cpp

class PointObject : public Ref // Dựng 1 lớp PointObject
{
public:

    // Các hàm inline thiết lập, và lấy về các thuộc tính
    inline void setRation(Point ratio) {_ratio = ratio;}
    inline void setOffset(Point offset) {_offset = offset;}
    inline void setChild(Node *var) {_child = var;}
    inline Point getOffset() const {return _offset;}
    inline Node* getChild() const {return _child;}

private:
    Point _ratio; // Tỉ lệ
    Point _offset; // Độ lệch
    Node* _child; // Node con
};

InfiniteParallaxNode* InfiniteParallaxNode::create()
{
    // Tạo mới 1 đối tượng InfiniteParallaxNode*
    InfiniteParallaxNode* node = new InfiniteParallaxNode();
    if(node) {
        node->autorelease();
    } else {
        delete node;
        node = 0;
    }
    return node;
}

void InfiniteParallaxNode::updatePosition()
{
    int safeOffset = -10;

    Size visibleSize = Director::getInstance()->getVisibleSize();
    // 1. Duyệt các con của chuỗi parallax

    for(int i = 0; i < _children.size(); i++)
    {
        auto node = _children.at(i); // Node i
        // 2. Kiểm tra node đó có ra ngoài màn hình ko, convertToWorldSpace bạn tham khảo tại đây http://www.cocos2d-x.org/wiki/Coordinate_System.
        if(convertToWorldSpace(node->getPosition()).x + node->getContentSize().width < safeOffset)
            // 3. Tìm PointObject tương ứng với node hiện tại
            for(int j = 0; j < _parallaxArray->num; j++)
            {
                auto po = (PointObject*)_parallaxArray->arr[j];
                // Nếu có thì tăng thêm độ lệch ra thêm 1 màn hình, tức xuất hiện lại thêm lần nữa
                if(po->getChild() == node)
                    po->setOffset(po->getOffset() +
                                  Point(visibleSize.width + node->getContentSize().width,0));
            }
    }
}

Trong file HelloWorldScene.h

Thêm include

    #include "InfiniteParallaxNode.h"
    InfiniteParallaxNode* _backgroundElements; // 1 con trỏ InfiniteParallaxNode
    float randomValueBetween(float low, float high); // random trong 1 khoảng
    void update(float delta); 

Trong file HelloWorldScenecpp

Phần Include

#include <climits>
#include <cstdlib>
#include <iostream>
using namespace std;

2 Hàm phụ

void HelloWorld::update(float delta)
{
    Point scrollDecrement = Point(5, 0); // Tốc độ Scroll, càng lớn cuộn càng nhanh
    _backgroundElements->setPosition(_backgroundElements->getPosition() - scrollDecrement);
    _backgroundElements->updatePosition();
}

// Giá trị ngẫu nhiên trong 1 khoảng
float HelloWorld::randomValueBetween(float low, float high)
{
    return (((float) rand() / RAND_MAX) * (high - low)) + low;
}

Hàm init()

    // Tạo màu nền cho background
    auto backgroundColor = LayerColor::create(
                Color4B(173, 255, 250, 255),
                visibleSize.width,
                visibleSize.height);
    backgroundColor->setPosition(Point::ZERO);
    addChild(backgroundColor, 0);
    // Ảnh Background
    auto ground = Sprite::create("ground.png");
    ground->setAnchorPoint(Point(0,0));
    ground->setPosition(Point::ZERO);
    addChild(ground, 1);
    
    // Tạo 1 đối tượng Parallax
    _backgroundElements = InfiniteParallaxNode::create();

    // Thêm 7 đối tượng (núi tuyết) vào Parallax
    unsigned int rocksQuantity = 7;
    for(unsigned int i = 0; i < rocksQuantity; i++)
    {
        auto rock = Sprite::create("rock.png");
        rock->setAnchorPoint(Point::ZERO);
        // Đặt độ lớn theo tỷ lệ ngẫu nhiên
        rock->setScale(randomValueBetween(0.6, 0.75));
        // Thêm vào Parallax
        _backgroundElements->addChild(rock,
                                     // Z-index, index lớn thì nằm chồng lên index nhỏ
                                     randomValueBetween(-10, -6),
                                     // Tỉ lệ tốc độ ( sẽ nhân với tốc độ Paralax)
                                     Point(0.05, 1),
                                     // Đặt vị trí của đối tượng, các đối tượng sẽ đặt cách nhau 1 khoảng tính theo công thức như bên dưới
                                     Point(
                                         (visibleSize.width / 5) * (i + 1) + randomValueBetween(0, 100),
                                         ground->getContentSize().height - 5));
    }

    // Thêm 35 cái cây vào Parralax
    unsigned int treesQuantity = 35;
    for(unsigned int i = 0; i < treesQuantity; i++)
    {
        auto tree = Sprite::create("tree.png");
        tree->setAnchorPoint(Point::ZERO);
        tree->setScale(randomValueBetween(0.5, 0.75));
        _backgroundElements->addChild(
                    tree,
                    randomValueBetween(-5, -1),
                    Point(0.5, 1),
                    Point(visibleSize.width / (treesQuantity - 5) * (i + 1) + randomValueBetween(25,50),
                          ground->getContentSize().height - 8));
    }

    // Thêm Parallax vào :Layer, update Scene
    addChild(_backgroundElements, 2);

    schedule(schedule_selector(HelloWorld::update), 0.01);

    // Thêm cha mặt trời vào 
    auto sun = Sprite::create("sun.png");
    sun->setAnchorPoint(Point(0.5,0.5));
    sun->setScale(0.25);
    sun->setPosition(Point(visibleSize.width * 2.0 / 3, visibleSize.height * 6.0 / 7));
    addChild(sun, 3);


OK rồi, giờ build và chạy thử xem thế nào



Vậy là trong bài này, chúng ta đã cùng nhau tìm hiểu về hiệu ứng Parallax Scrolling rồi đó. Bạn có thể áp dụng vào các game Running..X nào đó để làm game có chiều sâu hơn.

Một số hạn chế: 
+ Bạn phải ước lượng tỉ lệ vận tốc giữa các "Layer" Parallax ( vì thực sự đâu có nhiều Layer đâu - Chỉ có 1 Paralax ) do không biết được "khoảng cách" giữa các lớp này. 
+ Khi mô phỏng bạn làm sao cho các đối tượng gần thì chuyển động nhanh, các đối tượng xa thì chuyển động chậm hơn. Vậy thôi.

Dowload Resource, Code

Mình kết thúc bài này ỏ đây nhé, hẹn gặp lại trong các bài sau

Bài 26 : Học làm game 4 - Đập chuột ( Part 1 )

6 comments:

  1. Bạn ơi mình làm nó bị lỗi mình sửa hoài mà ko được nhờ bạn xem giúp mình với, lỗi nó thế này:
    **************************************************
    1>HelloWorldScene.obj : error LNK2019: unresolved external symbol "public: stati
    c class InfiniteParallaxNode * __cdecl InfiniteParallaxNode::create(void)" (?cre
    ate@InfiniteParallaxNode@@SAPAV1@XZ) referenced in function "public: virtual boo
    l __thiscall HelloWorld::init(void)" (?init@HelloWorld@@UAE_NXZ)
    1>HelloWorldScene.obj : error LNK2019: unresolved external symbol "public: void
    __thiscall InfiniteParallaxNode::updatePosition(void)" (?updatePosition@Infinite
    ParallaxNode@@QAEXXZ) referenced in function "public: virtual void __thiscall He
    lloWorld::update(float)" (?update@HelloWorld@@UAEXM@Z)
    1>D:\CCPRO\parallax\proj.win32\Debug.win32\parallax.exe : fatal error LNK1120: 2
    unresolved externals
    ========== Build: 0 succeeded, 1 failed, 3 up-to-date, 0 skipped ==========
    Error running command, return code: 1

    ReplyDelete
  2. tks you! Bài viết rất hữu ích

    ReplyDelete
  3. trời,copy code với resoure chạy thử mà lỗi tung chảo,tìm k ra.chán k muốn code

    ReplyDelete
    Replies
    1. chỉ copy + pase thì ... =)) ăn sẵn nhưng cũng phải biết cách ăn cho đúng bạn ơi =))

      Delete