Hướng Dẫn Làm Game Pikachu Với Cocos2d-x (Phần 1)

Hướng Dẫn Làm Game Pikachu Với Cocos2d-x (Phần 1)

Xin chào các bạn, hôm nay mình sẽ giới thiệu tới các bạn những bước đầu tiên để thực hiện thử thách Code Game bằng Cocos2d-x trong ngôn ngữ lập trình C++ trong vòng 24h. Các bạn cùng theo dõi nhé.

1. Cocos2d-x là gì?

Cocos2d-x là một engine phát triển game đa nền tảng: iOS, Android, macOS, Windows, Linux. Đây là một game engine được hàng triệu lập trình viên sử dụng, phát triển trên 25 nghìn game chính thức, bao gồm nhiều tựa game nổi tiếng như: Piano Tiles - Don't Tap the White Tile, Hill Climbing Racing, FLow Free, Diamond Dash, Idle Heroes, AFK Arena…

Cocos2d-x hỗ trợ 2 ngôn ngữ C++ và Lua, nhưng chủ yếu là C++. Mình chọn cocos2d-x để làm tutorials này cũng một phần vì C++, đây có thể nói là ngôn ngữ phổ biến nhất với các bạn mới bắt đầu lập trình.

Tại sao lựa chọn Cocos2d-x?

- Dễ cho người mới bắt đầu, hỗ trợ C++11 API (auto, std::function, lambda…).

- Đa nền tảng – Với 1 source code có thể chạy trên cả desktop và mobile. Có thể test và debug trên máy tính rồi release trên nền tảng mobile.

- API phong phú với đầy đủ các tính năng (sprites, actions, animations, particles, transitions, timers, events (touch, keyboard, accelerometer, mouse), sound, file IO, persistence, skeletal animations, 3D).

- Hoàn toàn FREE.

Trong  bài viết này, mình sẽ làm một game Pikachu Onet Connect bằng Cocos2d-x. 

2. Cài đặt môi trường

Các yêu cầu:

- Python 2.7.5+ (recommend 2.7.10). Lưu ý là Python 2 chứ không phải Python 3. Download tại đây.

Thêm python 2 vào PATH. Nếu bạn có cả python 2 và python 3 thì phải để path của python 2 trước path của python 3.

- CMake 3.6+. Download tại đây.

Thêm đường dẫn đến bin của cmake vào PATH.

Download cocos2d-x phiên bản mới nhất tại đây.

Bài viết này sử dụng cocos2d-x 4.0.

Sau khi download và giải nén, hãy chạy file setup.py để update PATH:

cd folder_giải_nén

python setup.py

Nếu bạn chưa có NDK_ROOT hay ANDROID_SDK_ROOT thì không sao cả, hãy nhấn ENTER để skip.

3. Tạo project game mới:

Sau khi đã cài đặt xong, tạo project mới bằng command prompt theo cú pháp:

cocos new -l cpp -d FOLDER_PATH PROJECT_NAME

-l cpp : chọn ngôn ngữ C++

-d FOLDER_PATH : PATH đến folder bạn muốn chứa project (có thể bỏ qua nếu tạo project ở folder đang chạy command promt)

PROJECT_NAME : tên project

Nếu muốn tạo ra game màn hình dọc, thêm ‘--portrait

 Ví dụ:

            cocos new -l cpp -d D:/MyProjects --portrait Pikachu2020

4. Build và test game với Visual Studio:

Nếu bạn chưa có Visual Studio, bạn có thể download bản mới nhất tại đây.

Trong bài viết này, mình sẽ hướng dẫn sử dụng Visual Studio 2019 để build và test project.

  • Tạo project Visual Studio 2019 bằng cmake:

            Điều hướng đến thư mục project

            Tạo thư mục win32-build

            Điều hướng tới thư mục win32-build

            Dùng cmake để tạo project VS 2019 (Lưu ý là phải có -A Win32)

  • Mở solution Pikachu2020.sln trong folder win32-build vừa tạo:

  • Sau khi đã mở solution bằng VS, chọn Build -> Build Solution:

  • Đặt Pikachu2020 làm StartUp project. Set as StartUp project:

  • Chọn Local Windows Debugger ở thanh công cụ phía trên hoặc bấm F5 để chạy project:

Tham khảo thêm về cài đặt cocos2d-x tại đây

5. Điều chỉnh kích thước của sổ game:

Project được tạo ra từ câu lệnh cocos new được copy từ template sẵn. Code của chương trình chính (các file .h.cpp) sẽ được lưu trong thư mục Classes/. Những file này sẽ được sử dụng trên mọi nền tảng. Code dành riêng cho từng nền tảng sẽ được lưu ở các folder riêng (proj.android, proj.ios_mac, proj.linux, proj.win32).

Class AppDelegate (AppDelegate.h, AppDelegate.cpp) là class được gọi khi chương trình bắt đầu chạy.

Kích thước cửa sổ được định nghĩa trong file AppDelegate.cpp ở folder Classes/:

Bạn có thể thay đổi kích thước cửa sổ giao diện game bằng cách điều chỉnh các thông số (chiều ngang, chiều dọc) của designResolutionSize.

* Không nên thay đổi smallResolutionSize, mediumResolutionSize, largeResolutionSize.

* Nên để kích thước cửa sổ game có cùng tỷ lệ với kích thước màn hình thiết bị.

6. Scene và Node

Hãy tưởng tượng Scene là một cảnh của game, trên một cảnh ta có thể đặt, sắp xếp, bố trí các vật lên. Mỗi vật trên cảnh đó chính là một Node, đó có thể là Label (Chữ), Image (Ảnh), Sprite (Ảnh động), và nhiều hơn thế.

Một đối tượng thuộc lớp Scene chứa những những gì sẽ được hiển thị trên màn hình sau khi chạy lệnh director->runWithScene(scene). Các đối tượng thuộc các lớp thừa kế lớp Node sẽ được vẽ khi chúng được thêm vào một scene nào đó.

Ở trong project đã có sẵn class HelloWorld thừa kế class Scene, đây chính là cửa sổ hiển thị trên màn hình khi chạy chương trình.

Có thể thấy trong scene HelloWorld có những node sau:

- auto menu = Menu::create(…)                  

  • Đây là đối tượng kiểu Menu. Thường dùng để chứa các node dạng MenuItem, ví dụ như các nút bấm để chuyển Scene, Play, Quit, Settings, About, ...

- auto closeItem = MenuItemImage::create(…)  

  • Biểu tượng đóng ứng dụng thuộc class MenuItemImage – thừa kế class MenuItem. Các đối tượng thuộc các class thừa kế MenuItem phải được đặt trong Menu.

- auto label = Label::createWithTTF(…)          

  • Chữ tiêu đề “Hello World“

- auto sprite = Sprite::create(…)

  • Ảnh biểu tượng của cocos “HelloWorld.png”

7. Scene Graph, z-order:

Bạn có thể thắc mắc: Nếu 2 node được đặt cùng một vị trí thì sao? Chúng sẽ xếp chồng lên nhau như thế nào? Node nào ở trên, node nào ở dưới?

Việc sắp xếp bố trí các Node trong một Scene không chỉ bao gồm việc sắp đặt vị trí trên không gian tọa độ 2 chiều (Ox, Oy), bạn còn phải quan tâm đến scene graphz-order.

- Scene Graph: là một đồ thị cây biểu diễn quan hệ cha con giữa các node. Trong scene HelloWorld, menu, label và sprite được add trực tiếp vào scene nên là con của scene, nhưng closeItem được add trực tiếp vào menu nên là con của menu.

- z-order: Quyết định thứ tự những node con của cùng một node cha.

  • Những node con của một scene hay của một node cha sẽ được sắp xếp theo thứ tự z-order không giảm. Nếu 2 node có z-order bằng nhau, node được thêm vào trước sẽ đứng trước.
  • Khi thêm một node vào scene hoặc một node khác, bạn có thể thêm một tham số int vào hàm addChild với ý nghĩa là z-order của node được thêm. Z-order mặc định là 0 nếu không được định nghĩa.

Trong hàm HelloWorld::init():

this->addChild(menu, 1);                                        // menu là con của scene, z-order = 1

auto menu = Menu::create(closeItem, NULL);            // closeItem là con của menu, z-order mặc định = 0

this->addChild(label, 1);                                      // label là con của scene, z-order = 1

this->addChild(sprite, 0);                                    // sprite là con của scene, z-order = 0

Cocos2d-x vẽ các node theo thứ tự duyệt in-order: Khi duyệt đến một node cha:

            - Duyệt những node con có giá trị z-order < 0, theo thứ tự z-order tăng dần.

            - Vẽ node cha.

            - Duyệt những node con có giá trị z-order >= 0, theo thứ tự z-order tăng dần.

Thứ tự vẽ của scene HelloWorld: Scene -> sprite -> menu -> closeItem -> label .

(scene và menu không có hình dạng nên sẽ ko thấy được trên màn hình)

Khi một node được vẽ, nó có thể được vẽ đè lên các node được vẽ từ trước và cũng có thể sẽ bị đè lên bởi những node được vẽ sau.

8. Tạo thêm Node và Scene

8.1 Tạo nút PLAY

Mình sẽ xóa sprite và tạo một nút PLAY giữa cửa sổ HelloWorld, mình muốn khi bấm vào nút này màn hình sẽ chuyển sang cửa số chơi game.

Tạo nút mới trong hàm HelloWorld::init() như sau:

bool HelloWorld::init()
{

    ...

    // Add label "PLAY" with callback HelloWorld::play
    auto play = MenuItemLabel::create(Label::createWithTTF("PLAY", "fonts/Marker Felt.ttf", 40),
                                      CC_CALLBACK_1(HelloWorld::play, this));
    // Assert that play is not null
    CCASSERT(play != nullptr, "problem loading fonts/Marker Felt.ttf");

    // Position PLAY in the middle of screen
    play->setPosition((Vec2(visibleSize) - origin) / 2);

    // Add PLAY to menu
    menu->addChild(play);
    return true;
}

void HelloWorld::play(Ref *pSender) {
    auto gameScene = GameScene::createScene();
    Director::getInstance()->replaceScene(
        TransitionFade::create(0.5, gameScene, Color3B(0, 255, 255)));
}

Với các node thuộc class thừa kế MenuItem (MenuItemLabel, MenuItemImage, MenuItemSprite…), ta có thể gắn cho nó một hàm callback. Hàm này sẽ được gọi mỗi khi node được kích hoạt (click chuột, bấm, …).

Mỗi lần ta click vào chữ PLAY, hàm callback HelloWorld::play(Ref*) sẽ được gọi. Tạm thời mình để hàm này in ra 1 dòng log với nội dung “PLAY”.

8.2 Tạo GameScene

 Tiếp theo, mình sẽ tạo một Scene mới. Scene này sẽ dùng để chứa những yếu tố hiển thị khi mình đang chơi game, ví dụ như bảng chơi Pikachu, thanh thời gian chạy, nút trở về, …. Trước tiên, mình tạo class GameScene:

GameScene.h:

#pragma once
#include "cocos2d.h"

USING_NS_CC;

class GameScene : public Scene
{
public:

	static Scene* createScene();

	virtual bool init();

	CREATE_FUNC(GameScene);
};

GameScene.cpp:

#include "GameScene.h"
#include <HelloWorldScene.h>

Scene* GameScene::createScene()
{
	return GameScene::create();
}

bool GameScene::init()
{
	// super init()
	if (!Scene::init()) return false;

	return true;
}

Đây là một Scene trống, khi vẽ ra sẽ chỉ có nền đen.

8.3 Chuyển Scene  

Mình muốn khi bấm nút PLAY, màn hình sẽ chuyển từ HelloWorld sang GameScene nên mình có thể sửa lại hàm callback của nút PLAY như sau:

void HelloWorld::play(Ref* pSender) {

    Director::getInstance()->replaceScene(GameScene::createScene());

}

Hoặc, một cách cool hơn, sử dụng hiệu ứng TransitionFade (nhiều hiệu ứng hơn tại đây)

void HelloWorld::play(Ref* pSender) {

    auto gameScene = GameScene::createScene();

    Director::getInstance()->replaceScene( TransitionFade::create(0.5, gameScene, Color3B(0, 255, 255)));

}

Tương tự như nút PLAY, mình sẽ tạo một nút BACK để quay lại scene HelloWorld từ GameScene. Lần này mình sẽ sử dụng MenuItemImage.

GameScene.cpp: 

bool GameScene::init()
{
	// super init()
	if (!Scene::init()) return false;

	auto visibleSize = Director::getInstance()->getVisibleSize();
	Vec2 origin = Director::getInstance()->getVisibleOrigin();


	auto back = MenuItemImage::create("back1.png", "back2.png",
		CC_CALLBACK_1(GameScene::back, this));
	CCASSERT(back != nullptr, "Fail to load BACK images");

	auto menu = Menu::create(back, nullptr);
	this->addChild(menu);
	menu->setPosition(origin);
	back->setPosition(origin + Vec2(back->getContentSize().width / 2, 
				      visibleSize.height - back->getContentSize().height / 2));

	return true;
}

void GameScene::back(Ref* pSender) {
	auto homeScene = HelloWorld::createScene();
	Director::getInstance()->replaceScene(
		TransitionFade::create(0.5, homeScene, Color3B(0, 255, 255)));
}

Hàm khởi tạo MenuItemImage::create() như ở trên nhận vào 2 ảnh "back1.png" và "back2.png", lần lượt là ảnh ở trạng thái bình thường và ảnh ở trạng thái được chọn (khi click chuột, bấm, ...). Các bạn lưu ảnh ở folder Resource/ của project và sử dụng khi cần tạo Image, Sprite, ...

Ảnh back1.png và back2.png của mình:

Và đây là kết quả

Kết luận:

Như vậy là mình đã giới thiệu về game engine Cocos2d-x và những khái niệm, yếu tố cơ bản nhất của nó. Đây là một trong những game engine có nhiều người dùng nhất và đã góp phần tạo dựng nên nhiều tựa game nổi tiếng.

Sau bài này mình hy vọng các bạn nắm được cách tạo một scene, tạo và thêm node vào scene. Mình khuyến khích cách bạn thử tạo scene với nhiều node ở nhiều vị trí khác nhau để thử nghiệm xem chúng tương tác (chồng lên nhau, che khuất nhau…) với nhau như nào. Nếu có bất kỳ vấn đề gì hãy comment dưới bài post này.

Ở những phần sau mình sẽ nói về cách xử lý sự kiện trong cocos2d-x, tạo bảng game, …

Code phần 1 của mình:

https://github.com/s34vv1nd/Cocos2dx-Tutorials/tree/master/part1

Tài liệu tham khảo:

- https://docs.cocos2d-x.org/cocos2d-x/v4/en/