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
#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 )
Ohm đã có bài mới rùi thanks
ReplyDeleteLại có bài mới :v
ReplyDeletecảm ơn bạn
ReplyDeletecám ơn bạn nhiều. Bạn up thêm folder resource lên nhé
ReplyDeleteÔ, quên! :-))
DeleteMình ko hiểu lắm trong file PlayLayer.h có dòng "class SushiSprite;" các bạn giải thích dùm cho mình nhé :)
ReplyDeleteTrong bài mình có nói rồi mà, đặt class SushiSprite; ở file này cũng giống như việc bạn đặt #include"SushiSprite.h" thôi
DeleteThậm chí bạn có thể khai báo cả 1 lớp SushiSprite ở trong file PlayLayer.h, thậm chí nhiều Class hơn, nhưng sẽ rối mắt.
Kiến thức C+
Mình thường dùng #include nhiều, ít dùng kiểu này nên thấy lạ. Khi dùng #include thì nó sẽ chép toàn bộ đoạn code vào vi trí đó. Thanks bạn đã chia sẻ.
Deletebạn thêm srand(time(NULL)); vào thì rand() sẽ thay đổi
ReplyDeletethêm dòng này vào đâu bạn, mình thêm vào hàm createsushi thì nó chỉ ra 1 loại thôi
DeleteCho em hỏi alfm sao để thay đổi dổi file susi.plist để vào game hiện hình ảnh khác được ạ
ReplyDeletecảm ơn bạn
ReplyDeleteThank you.
ReplyDeleteSau mấy tháng làm viêc. Đã có game:
https://play.google.com/store/apps/details?id=com.yellow.ant.FunnyJewel