Hi mọi người!
Cả tuần nay bận quá chả viết được bài. Nhưng mình chắc chắn rằng sau khi đọc xong 21 bài TUT trong này, các bạn có thể tự đọc các bài Tut khác trên mạng tại những trang nước ngoài rồi đó. Có mấy bạn đã Build xong game Sushi này rồi đấy, Hi. Nay mình sẽ hướng dẫn nốt cho những người chưa xong game này nha.
Qua 2 bài trước chúng ta đã từng bước học được cách làm game Sushi Crush giống game Candy Crush nổi tiếng nhé. Tuy nhiên bạn vẫn chưa thể di chuyển các Sushi, cũng như ăn các chuỗi Sushi được. Ở trong bài này, chúng ta sẽ cùng nhau đi nốt phần còn lại của game này, với các công việc cụ thể sau đây:
+ Xây dựng mode chơi, Bổ sung thêm các loại Sushi đặc biệt
+ Di chuyển Sushi nhờ sự kiện Touch
+ Tạo các hiệu ứng nổ đặc biệt khi ăn các Sushi đặc biệt
Công việc chỉ có vậy thôi, nhưng chắc code sẽ hơi dài nên có thể mình sẽ chia bài này ra làm 2 phần để mọi người tiện theo dõi.
Bắt đầu nhé! Trình bày nhiều quá.
B1 - Thêm mode chơi, bổ sung thêm các loại Sushi đặc biệt
Các bạn mởi file SushiSprite.h, ta khai báo thêm 1 loại dữ liệu sau, ngay trước Class nhé
typedef enum {
DISPLAY_MODE_NORMAL = 0,
DISPLAY_MODE_HORIZONTAL,
DISPLAY_MODE_VERTICAL,
} DisplayMode;
Và trong phần Public của Class SushiSprite, thêm đoạn code sau
CC_SYNTHESIZE(bool, m_isNeedRemove, IsNeedRemove); // Cờ đánh dấu cần loại bỏ
CC_SYNTHESIZE(bool, m_ignoreCheck, IgnoreCheck); // Cờ bỏ qua kiểm tra
CC_SYNTHESIZE_READONLY(DisplayMode, m_displayMode, DisplayMode); // Mode hiển thị
void setDisplayMode(DisplayMode mode); // Thiết lập mode
Các bạn mởi file SushiSprite.cpp, ta khai báo thêm 2 mảng SushiSprite nữa cho các loại Sushi đặc biệt ( Sọc dọc, Sọc ngang)
static const char *sushiVertical[TOTAL_SUSHI] = {
"sushi_1v.png",
"sushi_2v.png",
"sushi_3v.png",
"sushi_4v.png",
"sushi_5v.png",
"sushi_6v.png"
};
static const char *sushiHorizontal[TOTAL_SUSHI] = {
"sushi_1h.png",
"sushi_2h.png",
"sushi_3h.png",
"sushi_4h.png",
"sushi_5h.png",
"sushi_6h.png"
};
Hàm tạo thay đổi lại 1 chút ( do có thêm biến mới)
SushiSprite::SushiSprite()
: m_col(0)
, m_row(0)
, m_imgIndex(0)
, m_isNeedRemove(false)
, m_ignoreCheck(false)
, m_displayMode(DISPLAY_MODE_NORMAL)
{
}
Xây dựng hàm setDisplayMode
void SushiSprite::setDisplayMode(DisplayMode mode)
{
m_displayMode = mode;
SpriteFrame *frame;
// Tùy theo các trường hợp của mode, mà tạo ra loại Sushi tương ứng mode đó
switch (mode) {
case DISPLAY_MODE_VERTICAL:
frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(sushiVertical[m_imgIndex]);
break;
case DISPLAY_MODE_HORIZONTAL:
frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(sushiHorizontal[m_imgIndex]);
break;
default:
return;
}
setDisplayFrame(frame); // Hàm của lớp Sprite
}
Lớp SushiSprite chỉ có thêm 1 số bổ sung nhỏ vậy thôi, bây giờ chúng ta sẽ đi vào lớp chính của game là lớp PlayLayer
B2 - Di chuyển Sushi bằng Sự kiện Touch
Bạn mở file PlayLayer.h, bổ sung thêm code như sau
Phần Public:
virtual bool onTouchBegan(Touch *touch, Event *unused) override;
virtual void onTouchMoved(Touch *touch, Event *unused) override;
Phần Private:
bool m_isTouchEnable; // Cờ cho phép Touch hoặc ko
SushiSprite *m_srcSushi; // Pointer: Sushi nguồn
SushiSprite *m_destSushi; // Pointer: Sushi đích
bool m_isNeedFillVacancies; // Cờ điền đầy khoảng trống
bool m_movingVertical; // Cờ di chuyển theo chiều dọc
void actionEndCallback(Node *node); // Dừng Action ?
void explodeSpecialH(Point point); // Nổ theo chiều ngang
void explodeSpecialV(Point point); // Nổ theo chiều dọc
SushiSprite *sushiOfPoint(Point *point); // Sushi ở vị trí tọa độ Point
void swapSushi(); // Đảo 2 Sushi
void markRemove(SushiSprite *sushi); // Đánh dấu loại bỏ
Bạn mở file PlayLayer.cpp, bổ sung thêm code như sau
+ Sửa lại hàm tạo ( do thêm thuộc tính mới )
PlayLayer::PlayLayer()
: spriteSheet(NULL)
, m_matrix(NULL)
, m_width(0)
, m_height(0)
, m_matrixLeftBottomX(0)
, m_matrixLeftBottomY(0)
, m_isNeedFillVacancies(false)
, m_isAnimationing(true) // Đặt cờ cho Animate
, m_isTouchEnable(true) // Cho phép Touch
, m_srcSushi(NULL)
, m_destSushi(NULL)
, m_movingVertical(true) // Rơi Sushi
{
}
+ Trong hàm init() thêm code bắt sự kiện Touch
auto touchListener = EventListenerTouchOneByOne::create();
touchListener->onTouchBegan = CC_CALLBACK_2(PlayLayer::onTouchBegan, this);
touchListener->onTouchMoved = CC_CALLBACK_2(PlayLayer::onTouchMoved, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener, this);
+ Dựng hàm phụ , trả về SushiSprite tại 1 vị trí Point
SushiSprite *PlayLayer::sushiOfPoint(Point *point)
{
SushiSprite *sushi = NULL;
Rect rect = Rect(0, 0, 0, 0); // Hình chữ nhật kích thước 0,0 tại Point 0,0
// Duyệt ma trận Sushi
for (int i = 0; i < m_height * m_width; i++) {
sushi = m_matrix[i];
// Tính kích thước hình chữ nhật bao quanh Sushi
if (sushi) {
rect.origin.x = sushi->getPositionX() - (sushi->getContentSize().width / 2);
rect.origin.y = sushi->getPositionY() - (sushi->getContentSize().height / 2);
rect.size = sushi->getContentSize();
// Nếu hình chữ nhật đó chứa Point ( chắc là point của điểm Touch )
if (rect.containsPoint(*point)) {
return sushi; // trả lại Sushi
}
}
}
return NULL; // Trả lại Null nếu Touch ra ngoài ma trận, điểm Touch ko thuộc 1 Sushi nào
}
+ Hàm bắt sự kiện Touch
bool PlayLayer::onTouchBegan(Touch *touch, Event *unused)
{
m_srcSushi = NULL; // Sushi nguồn
m_destSushi = NULL; // Sushi dích, dùng để Swap cho nhau
if (m_isTouchEnable) { // cho phép Touch, khi chưa ăn thì cho phép Touch
auto location = touch->getLocation(); // lấy điểm Touch
m_srcSushi = sushiOfPoint(&location); // Trả về Sushi tại điểm Touch
}
return m_isTouchEnable;
}
// Di chuyển Sushi
void PlayLayer::onTouchMoved(Touch *touch, Event *unused)
{
if (!m_srcSushi || !m_isTouchEnable) { // Nếu Touch ra ngoài ( ko chứa Sushi nào ) hoặc ko được phép Touch
return;
}
// Lấy vị trí Row, Col của Sushi của Sushi nguồn
int row = m_srcSushi->getRow();
int col = m_srcSushi->getCol();
auto location = touch->getLocation();
// 1/2 Chiều rộng và 1/2 chiều cao
auto halfSushiWidth = m_srcSushi->getContentSize().width / 2;
auto halfSushiHeight = m_srcSushi->getContentSize().height / 2;
// Hướng di chuyển
// Khung chữ nhật "phía trên Sushi nguồn"
auto upRect = Rect(m_srcSushi->getPositionX() - halfSushiWidth,
m_srcSushi->getPositionY() + halfSushiHeight,
m_srcSushi->getContentSize().width,
m_srcSushi->getContentSize().height);
// Nếu khung này chứa điểm Touch, nghĩa là ta sẽ di chuyển 1 Sushi đi lên trên
if (upRect.containsPoint(location)) {
row++; // Hàng trên của Sushi Nguồn
if (row < m_height) {
m_destSushi = m_matrix[row * m_width + col]; // Lấy Sushi đích
}
m_movingVertical = true; // Di chuyển dọc = true
swapSushi(); // Đảo 2 Sushi nguồn và đích cho ngau
return; // Kết thúc hàm
}
// Khung chữ nhật "phía dưới Sushi nguồn", vì sao có halfSushiHeight * 3, bạn hãy vẽ hình ra cho dễ hình dung là nhớ là tọa độ gốc của hình Rectang là điểm Left - Bottom nhé, chiều cao + rộng sẽ dựng lên theo trục X ( sang phải ), và trục Y ( lên trên ). OK??
auto downRect = Rect(m_srcSushi->getPositionX() - halfSushiWidth,
m_srcSushi->getPositionY() - (halfSushiHeight * 3),
m_srcSushi->getContentSize().width,
m_srcSushi->getContentSize().height);
// Chứa Touch
if (downRect.containsPoint(location)) {
row--; // Hàng dưới
if (row >= 0) {
m_destSushi = m_matrix[row * m_width + col];
}
m_movingVertical = true;
swapSushi();
return;
}
// Các bước di chuyển sang trái, sang phải, ở đoạn code bên dưới cũng giải thích như trên các bạn nhé
auto leftRect = Rect(m_srcSushi->getPositionX() - (halfSushiWidth * 3),
m_srcSushi->getPositionY() - halfSushiHeight,
m_srcSushi->getContentSize().width,
m_srcSushi->getContentSize().height);
if (leftRect.containsPoint(location)) {
col--;
if (col >= 0) {
m_destSushi = m_matrix[row * m_width + col];
}
m_movingVertical = false;
swapSushi();
return;
}
auto rightRect = Rect(m_srcSushi->getPositionX() + halfSushiWidth,
m_srcSushi->getPositionY() - halfSushiHeight,
m_srcSushi->getContentSize().width,
m_srcSushi->getContentSize().height);
if (rightRect.containsPoint(location)) {
col++;
if (col < m_width) {
m_destSushi = m_matrix[row * m_width + col];
}
m_movingVertical = false;
swapSushi();
return;
}
}
+ Hàm đảo 2 Sushi
void PlayLayer::swapSushi()
{
m_isAnimationing = true; // cho phép Animation
m_isTouchEnable = false; // Dừng Touch
if (!m_srcSushi || !m_destSushi) { // Ko tồn tại 1 trong 2 Sushi để đảo nhau
m_movingVertical = true;
return;
}
// Lấy tọa độ Point của 2 loại Sushi được đảo
Point posOfSrc = m_srcSushi->getPosition();
Point posOfDest = m_destSushi->getPosition();
float time = 0.2;
// 1.Hoán vị hàng, cột 2 Sushi trong ma trận, tham số quan trọng nhất là Row và Col của Sushi
m_matrix[m_srcSushi->getRow() * m_width + m_srcSushi->getCol()] = m_destSushi;
m_matrix[m_destSushi->getRow() * m_width + m_destSushi->getCol()] = m_srcSushi;
int tmpRow = m_srcSushi->getRow();
int tmpCol = m_srcSushi->getCol();
m_srcSushi->setRow(m_destSushi->getRow());
m_srcSushi->setCol(m_destSushi->getCol());
m_destSushi->setRow(tmpRow);
m_destSushi->setCol(tmpCol);
// 2.Kiểm tra xem có dãy >= 3 Sushi giống nhau được tạo ra bởi 2 Sushi sau hoán đổi này ko
std::list<SushiSprite *> colChainListOfFirst;
getColChain(m_srcSushi, colChainListOfFirst);
std::list<SushiSprite *> rowChainListOfFirst;
getRowChain(m_srcSushi, rowChainListOfFirst);
std::list<SushiSprite *> colChainListOfSecond;
getColChain(m_destSushi, colChainListOfSecond);
std::list<SushiSprite *> rowChainListOfSecond;
getRowChain(m_destSushi, rowChainListOfSecond);
if (colChainListOfFirst.size() >= 3
|| rowChainListOfFirst.size() >= 3
|| colChainListOfSecond.size() >= 3
|| rowChainListOfSecond.size() >= 3) {
// Animation đảo vị trí cho nhau
m_srcSushi->runAction(MoveTo::create(time, posOfDest));
m_destSushi->runAction(MoveTo::create(time, posOfSrc));
return;
}
// 3.Không tạo được chuỗi, Đảo trở lại vị trí cũ
m_matrix[m_srcSushi->getRow() * m_width + m_srcSushi->getCol()] = m_destSushi;
m_matrix[m_destSushi->getRow() * m_width + m_destSushi->getCol()] = m_srcSushi;
tmpRow = m_srcSushi->getRow();
tmpCol = m_srcSushi->getCol();
m_srcSushi->setRow(m_destSushi->getRow());
m_srcSushi->setCol(m_destSushi->getCol());
m_destSushi->setRow(tmpRow);
m_destSushi->setCol(tmpCol);
// Di chuyển 2 bước, đảo vị trí, rồi trở lại vị trí cũ
m_srcSushi->runAction(Sequence::create(
MoveTo::create(time, posOfDest),
MoveTo::create(time, posOfSrc),
NULL));
m_destSushi->runAction(Sequence::create(
MoveTo::create(time, posOfSrc),
MoveTo::create(time, posOfDest),
NULL));
}
B3 - Tạo các hiệu ứng nổ đặc biệt
Trước hết ta cần sửa đổi lại một sô hàm sau
+ Hàm Update
Thay đoạn code
if (!m_isAnimationing) {
checkAndRemoveChain();
}
Thành =>
// Thiết lập cờ cho phép Touch khi không còn chuyển động, và ngược lại
m_isTouchEnable = !m_isAnimationing;
//Nếu ko có chuyển động
if (!m_isAnimationing) {
// Xét xem phải điền đầy ô trống không
if (m_isNeedFillVacancies) {
fillVacancies(); // điền đầy
m_isNeedFillVacancies = false;
} else {
checkAndRemoveChain(); // Kiểm tra và ăn các chuỗi
}
}
+ Hàm getColChain, và getRowChain bạn bổ sung code như sau
Tìm các đoạn code
if (neighborSushi && (neighborSushi->getImgIndex() == sushi->getImgIndex()))
Sửa thành =>
// Tồn tại Sushi đứng cạnh : cùng loại + chưa gắn cờ Remove và cờ Ignorecheck
if (neighborSushi
&& (neighborSushi->getImgIndex() == sushi->getImgIndex())
&& !neighborSushi->getIsNeedRemove()
&& !neighborSushi->getIgnoreCheck())
+ Hàm fillVacancies, bổ sung thêm
// Cho phép Animation và rơi
m_movingVertical = true;
m_isAnimationing = true;
+ Hàm removeSushi, loại bỏ tham số truyền, các bạn nhớ sửa trong PlayLayer.h
void PlayLayer::removeSushi() // Không cần truyền tham số
{
m_isAnimationing = true;
// Duyệt toàn ma trận
for (int i = 0; i < m_height * m_width; i++) {
SushiSprite *sushi = m_matrix[i];
if (!sushi) { // Bỏ qua Sushi rỗng
continue;
}
if (sushi->getIsNeedRemove()) { // Sushi cần xóa bỏ
m_isNeedFillVacancies = true; // Cần điền đầy
// Nổ các Sushi đặc biệt
if(sushi->getDisplayMode() == DISPLAY_MODE_HORIZONTAL) // Loại Sushi sọc ngang
{
explodeSpecialH(sushi->getPosition()); // Gọi hàm nổ theo chiều ngang
}
else if (sushi->getDisplayMode() == DISPLAY_MODE_VERTICAL) // Loại Sushi sọc dọc
{
explodeSpecialV(sushi->getPosition()); // Gọi hàm nổ theo chiều dọc
}
explodeSushi(sushi); // Nổ sushi bình thường
}
}
}
+ Hàm explodeSushi, sửa 1 chút
sushi->runAction(Sequence::create(
ScaleTo::create(time, 0.0),
CallFunc::create(CC_CALLBACK_0(Sprite::removeFromParent, sushi)),
NULL));
Thành
sushi->runAction(Sequence::create(
ScaleTo::create(time, 0.0),
CallFuncN::create(CC_CALLBACK_1(PlayLayer::actionEndCallback, this)),
NULL));
+ Hàm actionEndCallback như này
void PlayLayer::actionEndCallback(Node *node)
{
// Loại bỏ Sushi khỏi ma trận và Layer
SushiSprite *sushi = (SushiSprite *)node;
m_matrix[sushi->getRow() * m_width + sushi->getCol()] = NULL;
sushi->removeFromParent();
}
Lớp SushiSprite chỉ có thêm 1 số bổ sung nhỏ vậy thôi, bây giờ chúng ta sẽ đi vào lớp chính của game là lớp PlayLayer
B2 - Di chuyển Sushi bằng Sự kiện Touch
Bạn mở file PlayLayer.h, bổ sung thêm code như sau
Phần Public:
virtual bool onTouchBegan(Touch *touch, Event *unused) override;
virtual void onTouchMoved(Touch *touch, Event *unused) override;
Phần Private:
bool m_isTouchEnable; // Cờ cho phép Touch hoặc ko
SushiSprite *m_srcSushi; // Pointer: Sushi nguồn
SushiSprite *m_destSushi; // Pointer: Sushi đích
bool m_isNeedFillVacancies; // Cờ điền đầy khoảng trống
bool m_movingVertical; // Cờ di chuyển theo chiều dọc
void actionEndCallback(Node *node); // Dừng Action ?
void explodeSpecialH(Point point); // Nổ theo chiều ngang
void explodeSpecialV(Point point); // Nổ theo chiều dọc
SushiSprite *sushiOfPoint(Point *point); // Sushi ở vị trí tọa độ Point
void swapSushi(); // Đảo 2 Sushi
void markRemove(SushiSprite *sushi); // Đánh dấu loại bỏ
Bạn mở file PlayLayer.cpp, bổ sung thêm code như sau
+ Sửa lại hàm tạo ( do thêm thuộc tính mới )
PlayLayer::PlayLayer()
: spriteSheet(NULL)
, m_matrix(NULL)
, m_width(0)
, m_height(0)
, m_matrixLeftBottomX(0)
, m_matrixLeftBottomY(0)
, m_isNeedFillVacancies(false)
, m_isAnimationing(true) // Đặt cờ cho Animate
, m_isTouchEnable(true) // Cho phép Touch
, m_srcSushi(NULL)
, m_destSushi(NULL)
, m_movingVertical(true) // Rơi Sushi
{
}
+ Trong hàm init() thêm code bắt sự kiện Touch
auto touchListener = EventListenerTouchOneByOne::create();
touchListener->onTouchBegan = CC_CALLBACK_2(PlayLayer::onTouchBegan, this);
touchListener->onTouchMoved = CC_CALLBACK_2(PlayLayer::onTouchMoved, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener, this);
+ Dựng hàm phụ , trả về SushiSprite tại 1 vị trí Point
SushiSprite *PlayLayer::sushiOfPoint(Point *point)
{
SushiSprite *sushi = NULL;
Rect rect = Rect(0, 0, 0, 0); // Hình chữ nhật kích thước 0,0 tại Point 0,0
// Duyệt ma trận Sushi
for (int i = 0; i < m_height * m_width; i++) {
sushi = m_matrix[i];
// Tính kích thước hình chữ nhật bao quanh Sushi
if (sushi) {
rect.origin.x = sushi->getPositionX() - (sushi->getContentSize().width / 2);
rect.origin.y = sushi->getPositionY() - (sushi->getContentSize().height / 2);
rect.size = sushi->getContentSize();
// Nếu hình chữ nhật đó chứa Point ( chắc là point của điểm Touch )
if (rect.containsPoint(*point)) {
return sushi; // trả lại Sushi
}
}
}
return NULL; // Trả lại Null nếu Touch ra ngoài ma trận, điểm Touch ko thuộc 1 Sushi nào
}
+ Hàm bắt sự kiện Touch
bool PlayLayer::onTouchBegan(Touch *touch, Event *unused)
{
m_srcSushi = NULL; // Sushi nguồn
m_destSushi = NULL; // Sushi dích, dùng để Swap cho nhau
if (m_isTouchEnable) { // cho phép Touch, khi chưa ăn thì cho phép Touch
auto location = touch->getLocation(); // lấy điểm Touch
m_srcSushi = sushiOfPoint(&location); // Trả về Sushi tại điểm Touch
}
return m_isTouchEnable;
}
// Di chuyển Sushi
void PlayLayer::onTouchMoved(Touch *touch, Event *unused)
{
if (!m_srcSushi || !m_isTouchEnable) { // Nếu Touch ra ngoài ( ko chứa Sushi nào ) hoặc ko được phép Touch
return;
}
// Lấy vị trí Row, Col của Sushi của Sushi nguồn
int row = m_srcSushi->getRow();
int col = m_srcSushi->getCol();
auto location = touch->getLocation();
// 1/2 Chiều rộng và 1/2 chiều cao
auto halfSushiWidth = m_srcSushi->getContentSize().width / 2;
auto halfSushiHeight = m_srcSushi->getContentSize().height / 2;
// Hướng di chuyển
// Khung chữ nhật "phía trên Sushi nguồn"
auto upRect = Rect(m_srcSushi->getPositionX() - halfSushiWidth,
m_srcSushi->getPositionY() + halfSushiHeight,
m_srcSushi->getContentSize().width,
m_srcSushi->getContentSize().height);
// Nếu khung này chứa điểm Touch, nghĩa là ta sẽ di chuyển 1 Sushi đi lên trên
if (upRect.containsPoint(location)) {
row++; // Hàng trên của Sushi Nguồn
if (row < m_height) {
m_destSushi = m_matrix[row * m_width + col]; // Lấy Sushi đích
}
m_movingVertical = true; // Di chuyển dọc = true
swapSushi(); // Đảo 2 Sushi nguồn và đích cho ngau
return; // Kết thúc hàm
}
// Khung chữ nhật "phía dưới Sushi nguồn", vì sao có halfSushiHeight * 3, bạn hãy vẽ hình ra cho dễ hình dung là nhớ là tọa độ gốc của hình Rectang là điểm Left - Bottom nhé, chiều cao + rộng sẽ dựng lên theo trục X ( sang phải ), và trục Y ( lên trên ). OK??
auto downRect = Rect(m_srcSushi->getPositionX() - halfSushiWidth,
m_srcSushi->getPositionY() - (halfSushiHeight * 3),
m_srcSushi->getContentSize().width,
m_srcSushi->getContentSize().height);
// Chứa Touch
if (downRect.containsPoint(location)) {
row--; // Hàng dưới
if (row >= 0) {
m_destSushi = m_matrix[row * m_width + col];
}
m_movingVertical = true;
swapSushi();
return;
}
// Các bước di chuyển sang trái, sang phải, ở đoạn code bên dưới cũng giải thích như trên các bạn nhé
auto leftRect = Rect(m_srcSushi->getPositionX() - (halfSushiWidth * 3),
m_srcSushi->getPositionY() - halfSushiHeight,
m_srcSushi->getContentSize().width,
m_srcSushi->getContentSize().height);
if (leftRect.containsPoint(location)) {
col--;
if (col >= 0) {
m_destSushi = m_matrix[row * m_width + col];
}
m_movingVertical = false;
swapSushi();
return;
}
auto rightRect = Rect(m_srcSushi->getPositionX() + halfSushiWidth,
m_srcSushi->getPositionY() - halfSushiHeight,
m_srcSushi->getContentSize().width,
m_srcSushi->getContentSize().height);
if (rightRect.containsPoint(location)) {
col++;
if (col < m_width) {
m_destSushi = m_matrix[row * m_width + col];
}
m_movingVertical = false;
swapSushi();
return;
}
}
+ Hàm đảo 2 Sushi
void PlayLayer::swapSushi()
{
m_isAnimationing = true; // cho phép Animation
m_isTouchEnable = false; // Dừng Touch
if (!m_srcSushi || !m_destSushi) { // Ko tồn tại 1 trong 2 Sushi để đảo nhau
m_movingVertical = true;
return;
}
// Lấy tọa độ Point của 2 loại Sushi được đảo
Point posOfSrc = m_srcSushi->getPosition();
Point posOfDest = m_destSushi->getPosition();
float time = 0.2;
// 1.Hoán vị hàng, cột 2 Sushi trong ma trận, tham số quan trọng nhất là Row và Col của Sushi
m_matrix[m_srcSushi->getRow() * m_width + m_srcSushi->getCol()] = m_destSushi;
m_matrix[m_destSushi->getRow() * m_width + m_destSushi->getCol()] = m_srcSushi;
int tmpRow = m_srcSushi->getRow();
int tmpCol = m_srcSushi->getCol();
m_srcSushi->setRow(m_destSushi->getRow());
m_srcSushi->setCol(m_destSushi->getCol());
m_destSushi->setRow(tmpRow);
m_destSushi->setCol(tmpCol);
// 2.Kiểm tra xem có dãy >= 3 Sushi giống nhau được tạo ra bởi 2 Sushi sau hoán đổi này ko
std::list<SushiSprite *> colChainListOfFirst;
getColChain(m_srcSushi, colChainListOfFirst);
std::list<SushiSprite *> rowChainListOfFirst;
getRowChain(m_srcSushi, rowChainListOfFirst);
std::list<SushiSprite *> colChainListOfSecond;
getColChain(m_destSushi, colChainListOfSecond);
std::list<SushiSprite *> rowChainListOfSecond;
getRowChain(m_destSushi, rowChainListOfSecond);
if (colChainListOfFirst.size() >= 3
|| rowChainListOfFirst.size() >= 3
|| colChainListOfSecond.size() >= 3
|| rowChainListOfSecond.size() >= 3) {
// Animation đảo vị trí cho nhau
m_srcSushi->runAction(MoveTo::create(time, posOfDest));
m_destSushi->runAction(MoveTo::create(time, posOfSrc));
return;
}
// 3.Không tạo được chuỗi, Đảo trở lại vị trí cũ
m_matrix[m_srcSushi->getRow() * m_width + m_srcSushi->getCol()] = m_destSushi;
m_matrix[m_destSushi->getRow() * m_width + m_destSushi->getCol()] = m_srcSushi;
tmpRow = m_srcSushi->getRow();
tmpCol = m_srcSushi->getCol();
m_srcSushi->setRow(m_destSushi->getRow());
m_srcSushi->setCol(m_destSushi->getCol());
m_destSushi->setRow(tmpRow);
m_destSushi->setCol(tmpCol);
// Di chuyển 2 bước, đảo vị trí, rồi trở lại vị trí cũ
m_srcSushi->runAction(Sequence::create(
MoveTo::create(time, posOfDest),
MoveTo::create(time, posOfSrc),
NULL));
m_destSushi->runAction(Sequence::create(
MoveTo::create(time, posOfSrc),
MoveTo::create(time, posOfDest),
NULL));
}
B3 - Tạo các hiệu ứng nổ đặc biệt
Trước hết ta cần sửa đổi lại một sô hàm sau
+ Hàm Update
Thay đoạn code
if (!m_isAnimationing) {
checkAndRemoveChain();
}
Thành =>
// Thiết lập cờ cho phép Touch khi không còn chuyển động, và ngược lại
m_isTouchEnable = !m_isAnimationing;
//Nếu ko có chuyển động
if (!m_isAnimationing) {
// Xét xem phải điền đầy ô trống không
if (m_isNeedFillVacancies) {
fillVacancies(); // điền đầy
m_isNeedFillVacancies = false;
} else {
checkAndRemoveChain(); // Kiểm tra và ăn các chuỗi
}
}
+ Hàm getColChain, và getRowChain bạn bổ sung code như sau
Tìm các đoạn code
if (neighborSushi && (neighborSushi->getImgIndex() == sushi->getImgIndex()))
Sửa thành =>
// Tồn tại Sushi đứng cạnh : cùng loại + chưa gắn cờ Remove và cờ Ignorecheck
if (neighborSushi
&& (neighborSushi->getImgIndex() == sushi->getImgIndex())
&& !neighborSushi->getIsNeedRemove()
&& !neighborSushi->getIgnoreCheck())
+ Hàm fillVacancies, bổ sung thêm
// Cho phép Animation và rơi
m_movingVertical = true;
m_isAnimationing = true;
+ Hàm removeSushi, loại bỏ tham số truyền, các bạn nhớ sửa trong PlayLayer.h
void PlayLayer::removeSushi() // Không cần truyền tham số
{
m_isAnimationing = true;
// Duyệt toàn ma trận
for (int i = 0; i < m_height * m_width; i++) {
SushiSprite *sushi = m_matrix[i];
if (!sushi) { // Bỏ qua Sushi rỗng
continue;
}
if (sushi->getIsNeedRemove()) { // Sushi cần xóa bỏ
m_isNeedFillVacancies = true; // Cần điền đầy
// Nổ các Sushi đặc biệt
if(sushi->getDisplayMode() == DISPLAY_MODE_HORIZONTAL) // Loại Sushi sọc ngang
{
explodeSpecialH(sushi->getPosition()); // Gọi hàm nổ theo chiều ngang
}
else if (sushi->getDisplayMode() == DISPLAY_MODE_VERTICAL) // Loại Sushi sọc dọc
{
explodeSpecialV(sushi->getPosition()); // Gọi hàm nổ theo chiều dọc
}
explodeSushi(sushi); // Nổ sushi bình thường
}
}
}
+ Hàm explodeSushi, sửa 1 chút
sushi->runAction(Sequence::create(
ScaleTo::create(time, 0.0),
CallFunc::create(CC_CALLBACK_0(Sprite::removeFromParent, sushi)),
NULL));
Thành
sushi->runAction(Sequence::create(
ScaleTo::create(time, 0.0),
CallFuncN::create(CC_CALLBACK_1(PlayLayer::actionEndCallback, this)),
NULL));
+ Hàm actionEndCallback như này
void PlayLayer::actionEndCallback(Node *node)
{
// Loại bỏ Sushi khỏi ma trận và Layer
SushiSprite *sushi = (SushiSprite *)node;
m_matrix[sushi->getRow() * m_width + sushi->getCol()] = NULL;
sushi->removeFromParent();
}
+ 2 Hàm tạo hiệu ứng nổ đặc biệt theo chiều ngang và dọc
// Nổ theo chiều ngangvoid PlayLayer::explodeSpecialH(Point point)
{
Size size = Director::getInstance()->getWinSize();
// Tham số để tạo hiệu ứng thôi
float scaleX = 4 ;
float scaleY = 0.7 ;
float time = 0.3;
Point startPosition = point; // điểm đầu
float speed = 0.6f;
auto colorSpriteRight = Sprite::create("colorHRight.png");
addChild(colorSpriteRight, 10);
Point endPosition1 = Point(point.x - size.width, point.y); // Điểm cuối
colorSpriteRight->setPosition(startPosition);
// Chỗ này thực hiện 3 hành động, kéo dãn theo X + co lại theo Y, - >chạy sang trái -> xóa khỏi layer
colorSpriteRight->runAction(Sequence::create(ScaleTo::create(time, scaleX, scaleY),
MoveTo::create(speed, endPosition1),
CallFunc::create(CC_CALLBACK_0(Sprite::removeFromParent, colorSpriteRight)),
NULL));
// Giải thích như trên
auto colorSpriteLeft = Sprite::create("colorHLeft.png");
addChild(colorSpriteLeft, 10);
Point endPosition2 = Point(point.x + size.width, point.y);
colorSpriteLeft->setPosition(startPosition);
colorSpriteLeft->runAction(Sequence::create(ScaleTo::create(time, scaleX, scaleY),
MoveTo::create(speed, endPosition2),
CallFunc::create(CC_CALLBACK_0(Sprite::removeFromParent, colorSpriteLeft)),
NULL));
}
// Giống hệt hàm explodeSpecialH, chỉ thay đổi phương X thành Y, quá dễ hiểu
void PlayLayer::explodeSpecialV(Point point)
{
Size size = Director::getInstance()->getWinSize();
float scaleY = 4 ;
float scaleX = 0.7 ;
float time = 0.3;
Point startPosition = point;
float speed = 0.6f;
auto colorSpriteDown = Sprite::create("colorVDown.png");
addChild(colorSpriteDown, 10);
Point endPosition1 = Point(point.x , point.y - size.height);
colorSpriteDown->setPosition(startPosition);
colorSpriteDown->runAction(Sequence::create(ScaleTo::create(time, scaleX, scaleY),
MoveTo::create(speed, endPosition1),
CallFunc::create(CC_CALLBACK_0(Sprite::removeFromParent, colorSpriteDown)),
NULL));
auto colorSpriteUp = Sprite::create("colorVUp.png");
addChild(colorSpriteUp, 10);
Point endPosition2 = Point(point.x , point.y + size.height);
colorSpriteUp->setPosition(startPosition);
colorSpriteUp->runAction(Sequence::create(ScaleTo::create(time, scaleX, scaleY),
MoveTo::create(speed, endPosition2),
CallFunc::create(CC_CALLBACK_0(Sprite::removeFromParent, colorSpriteUp)),
NULL));
}
Xong các bước chính, giờ chúng ta chỉnh sửa thêm và chỉnh sửa nốt 1-2 hàm nữa là chạy được
+ Hàm markRemove
void PlayLayer::markRemove(SushiSprite *sushi)
{
if (sushi->getIsNeedRemove()) {
return;
}
if (sushi->getIgnoreCheck()) {
return;
}
// Set true
sushi->setIsNeedRemove(true);
// Các sushi loại sọc dọc
if (sushi->getDisplayMode() == DISPLAY_MODE_VERTICAL) {
for (int row = 0; row < m_height; row++) {
SushiSprite *tmp = m_matrix[row * m_width + sushi->getCol()];
if (!tmp || tmp == sushi) {
continue; //Bỏ qua loại sọc dọc
}
if (tmp->getDisplayMode() == DISPLAY_MODE_NORMAL) {
tmp->setIsNeedRemove(true); // Đánh dấu loại Sushi thường
} else {
markRemove(tmp); // Đệ quy,
}
}
// Các sushi loại sọc ngang, tương tự
} else if (sushi->getDisplayMode() == DISPLAY_MODE_HORIZONTAL) {
for (int col = 0; col < m_width; col++) {
SushiSprite *tmp = m_matrix[sushi->getRow() * m_width + col];
if (!tmp || tmp == sushi) {
continue;
}
if (tmp->getDisplayMode() == DISPLAY_MODE_NORMAL) {
tmp->setIsNeedRemove(true);
} else {
markRemove(tmp);
}
}
}
}
+ Sửa lại hàm Ăn Sushi checkAndRemoveChain
void PlayLayer::checkAndRemoveChain()
{
SushiSprite *sushi;
// Thiết lập cờ IgnoreCheck = false
for (int i = 0; i < m_height * m_width; i++) {
sushi = m_matrix[i];
if (!sushi) {
continue;
}
sushi->setIgnoreCheck(false);
}
// 2. Kiểm lại
for (int i = 0; i < m_height * m_width; i++) {
sushi = m_matrix[i];
if (!sushi) {
continue;
}
if (sushi->getIsNeedRemove()) {
continue; // Bỏ qua Sushi đã gắn cờ "cần loại bỏ"
}
if (sushi->getIgnoreCheck()) {
continue; // Bỏ qua Sushi đã gắn cờ "bỏ qua kiểm tra"
}
// Đếm cuỗi
std::list<SushiSprite *> colChainList;
getColChain(sushi, colChainList);
std::list<SushiSprite *> rowChainList;
getRowChain(sushi, rowChainList);
std::list<SushiSprite *> &longerList = colChainList.size() > rowChainList.size() ? colChainList : rowChainList;
if (longerList.size() < 3) {
continue;// Bỏ qua
}
std::list<SushiSprite *>::iterator itList;
bool isSetedIgnoreCheck = false;
for (itList = longerList.begin(); itList != longerList.end(); itList++) {
sushi = (SushiSprite *)*itList;
if (!sushi) {
continue;
}
if (longerList.size() > 3) {
// Sushi đặc biệt khi chuỗi có 4 hoặc 5 Sushi
if (sushi == m_srcSushi || sushi == m_destSushi) {
isSetedIgnoreCheck = true;
sushi->setIgnoreCheck(true);
sushi->setIsNeedRemove(false);
// Tùy theo hướng di chuyển mà tạo ra loại Sushi sọc dọc hay ngang
sushi->setDisplayMode(m_movingVertical ? DISPLAY_MODE_VERTICAL : DISPLAY_MODE_HORIZONTAL);
continue;
}
}
markRemove(sushi); // Đánh dấu cần loại bỏ sushi
}
// Chuỗi đặc biệt, khi Sushi rơi, sinh ra tự nhiên
if (!isSetedIgnoreCheck && longerList.size() > 3) {
sushi->setIgnoreCheck(true);
sushi->setIsNeedRemove(false);
sushi->setDisplayMode(m_movingVertical ? DISPLAY_MODE_VERTICAL : DISPLAY_MODE_HORIZONTAL);
}
}
// 3.Loại bỏ
removeSushi();
}
Vậy là Xong, chạy và build thử xem thế nào nhé
OK, chạy ngon lành cành đào.
Mình xin kết thúc bài này ở đây nhé. Tổng kết lại trong bài này ta học được 1 số thứ sau:
+ Đảo 2 Sushi bằng sự kiện Touch
+ Tạo ra các Sushi đặc biệt
+ Hiệu ứng khi ăn các Sushi đặc biệt
Hãy cài vào điện thoại và chơi thử nhé.
P/S: Vì đây chỉ là Demo cho 1 dạng game kiểu Candy Crush, nên chắc chắn sẽ còn nhiều thiếu sót lớn như:
+ Màn chơi
+ Gợi ý + Check hết nước đi
+ không ăn được nước kép dạng 2 chuỗi vuông góc
+ Khi tạo ra Sushi đặc biệt, 1 trong 2 Sushi swap sẽ trở lại vị trí cũ mà ko bị remove đi.
+ Khi ăn liên tiếp các chuỗi, có delay, không mượt cho lắm
+ Tính điểm, âm thanh,
v..v...
Game Demo chỉ vậy thôi là ổn rồi. để phát triển nó cần phải có nhiều thời gian hơn. Trong khuôn khổ bài học, thì như vậy cũng chấp nhận được. Ai có tâm huyết game kiểu này có thể phát triển tiếp nhé. Bài này code cũng khá dài. Để hiểu kỹ, sâu, hoặc sửa lỗi, bổ sung, phát triển tiếp, các bạn nên đọc lại nhiều lần cho thấm nhé
Chào và hẹn gặp lại các bạn ở bài sau!
Download
cam on ban nhieu nhe :)
ReplyDeleteBạn cho mình hỏi làm sao để chuyển màn hình landscape sang portrait vậy.
ReplyDeletethanks & regards
Bạn thử làm theo Hướng dẫn trong này có OK ko?
Deletehttp://www.cocos2d-x.org/wiki/Device_Orientation
cảm ơn vì những chia sẽ của bạn nhé :)
ReplyDeleteCảm ơn nhiều nha
ReplyDeletechạy trên điện thoại hơi có vấn đề
ReplyDeletelink ảnh
https://drive.google.com/file/d/0B-oinVGxaTX4VGRsMVZNZWY1Uzg/edit?usp=sharing
-hiển thị backgound có vấn đề
-hiển thị không đầy đủ số hàng và số cột
-các hiệu ứng đặc biệt nổ ngang va nổ dọc làm mất luôn ảnh
https://drive.google.com/file/d/0B-oinVGxaTX4LUdfTXd0OE9YQVE/edit?usp=sharing
các vấn đề này giải quyết như thế nào?
bác admin ơi, em là newbie, nhìu cái còn gà quá.
ReplyDeleteBac clone giúp e cái game này ra cocos2dx giúp e nha.
https://github.com/lizhi5753186/MyFlappyBird
Đây chả là Cocos2d-x là gì, nhưng bản 2.x
DeleteGame này nhiều code 3.x lắm, bạn tìm trong github.com, rất nhiều
các bác bít cách port version 2. sang 3. khong các bác ? chỉ giáo e với
DeleteHere: http://laptrinhgamecocos2dx.blogspot.com/2014/07/huong-dan-cach-port-code-tu-cocos2d-x-2-sang-cocos2d-x-3x.html
DeleteTuy ko phải là tất cả nhưng rất hữu ích
thank bác nhìu nha, e thử ngay
Deletesao hàm removeSushi của mình lại không xóa được sushi nhỉ ? mình dùng v2.2.3
ReplyDeleteđoạn code đảo vị trí khi không tạo thành 3 sushi giống nhau t thấy thua cho gán lại vị trí cho vị trí gốc và vị trí đích
ReplyDeleteThanks so much.
ReplyDeleteSau mấy tháng làm viêc. Đã có game:
https://play.google.com/store/apps/details?id=com.yellow.ant.FunnyJewel