Cho hệ phương trình tuyến tính dạng tam giác dưới:
Nghiệm của hệ trên được xác định như sau:
Cài đặt chương trình:
#include <conio.h>
#include <iostream>
#define max 100
using namespace std;
/* Nhập ma trận tam giác dưới */
void Nhap(float A[max][max],int n) {
for(int i = 0; i<n; i++)
for(int j = 0; j<=i; j++) {
cout<<"a["<<i<<"]["<<j<<"] = ";
cin>>A[i][j];
}
}
/* Nhập ma trận hệ số tự do */
void Nhap(float B[max],int n) {
for(int i = 0; i<n; i++) {
cout<<"b["<<i<<"] = ";
cin>>B[i];
}
}
/* Xuất ma trận hệ số tự do */
void Xuat(float B[max],int n) {
cout<<"(";
for(int i = 0; i<n-1; i++)
cout<<B[i]<<",";
cout<<B[n-1]<<")";
}
/* Xuất ma trận */
void Xuat(float A[max][max], int n) {
cout<<"\n";
for(int i=0 ; i<n; i++){
cout<<endl;
for(int j=0 ; j<n; j++)
if(i<j)
cout<<"0\t";
else
cout<<A[i][j]<<"\t";
}
}
/* Xuất nghiệm */
void XuatNghiem(float X[], int n, char * s){
cout<<"\nNghiem cua he A.X = B\n";
for(int i=0; i<n; i++)
cout<<s<<i+1<<" = "<<X[i]<<endl;
}
/* Hệ tam giác dưới */
char HeTamGiacDuoi (float A[max][max], float X[max], float B[max], int n ) {
for(int i = 0; i<n; i++) {
if (A[i][i]!=0) {
if (i==0)
X[i] = B[i]/A[i][i];
else {
X[i] = B[i];
for(int j=0; j<i; j++)
X[i]=X[i]-A[i][j]*X[j];
X[i] = X[i]/A[i][i];
}
}
else
return 0;
}
return 1;
}
/* Chương trình chính */
int main() {
int n;
float A[max][max],B[max],X[max];
cout<<"So phuong trinh n = "; cin>>n;
cout<<"Nhap vao ma tran tam giac duoi A\n";
Nhap(A,n);
cout<<"\nNhap vao ma tran he so B\n";
Nhap(B,n);
cout<<"Ma tran A";
Xuat(A,n);
cout<<"\nMa tran B\n";
Xuat(B,n);
if(HeTamGiacDuoi(A,X,B,n))
XuatNghiem(X,n,"x");
else
cout<<"\nHe phuong trinh tuyen tinh vo nghiem";
getch();
return 0;
}
Kết quả chạy chương trình:
Tag: C, C++, Hệ phương trình tuyến tính, tam giác trên, tam giác dưới, tam giác trên
Thứ Hai, 16 tháng 6, 2014
Code C++: Giải hệ phương trình tuyến tính dạng tam giác trên
Cho hệ phương trình tuyến tính dạng tam giác trên:
Nghiệm của hệ trên được xác định như sau:
Cài đặt chương trình:
#include <conio.h>
#include <iostream>
#define max 100
using namespace std;
/* Nhập ma trận tam giác trên */
void Nhap(float A[max][max],int n) {
for(int i = 0; i<n; i++)
for(int j = i; j<n; j++) {
cout<<"a["<<i<<"]["<<j<<"] = ";
cin>>A[i][j];
}
}
/* Nhập ma trận hệ số tự do */
void Nhap(float B[max],int n){
for(int i = 0; i<n; i++) {
cout<<"b["<<i<<"] = ";
cin>>B[i];
}
}
/* Xuất ma trận hệ số tự do */
void Xuat(float B[max],int n) {
cout<<"(";
for(int i = 0; i<n-1; i++)
cout<<B[i]<<",";
cout<<B[n-1]<<")";
}
/*Xuất ma trận */
void Xuat(float A[max][max], int n){
cout<<"\n";
for(int i=0 ; i<n; i++){
cout<<endl;
for(int j=0 ; j<n; j++)
if(i>j)
cout<<"0\t";
else
cout<<A[i][j]<<"\t";
}
}
/* Xuất nghiệm */
void XuatNghiem(float X[], int n, char * s){
cout<<"\nNghiem cuahe A.X = B\n";
for(int i=0; i<n; i++)
cout<<s<<i+1<<" = "<<X[i]<<endl;
}
/* Hệ tam giác trên */
char HeTamGiacTren (float A[max][max], float X[max], float B[max], int n ) {
for(int i = n-1; i>=0; i--) {
if (A[i][i]!=0) {
if (i==n-1)
X[i] = B[i]/A[i][i];
else {
X[i] = B[i];
for(int j=i+1; j<n; j++)
X[i]=X[i]-A[i][j]*X[j];
X[i] = X[i]/A[i][i];
}
}
else
return 0;
}
return 1;
}
/* Chương trình chính */
int main(){
int n;
float A[max][max],B[max],X[max];
cout<<"So phuong trinh n = "; cin>>n;
cout<<"Nhap vao ma tran tam giac tren A\n";
Nhap(A,n);
cout<<"\nNhap vao ma tran he so B\n";
Nhap(B,n);
cout<<"Ma tran A";
Xuat(A,n);
cout<<"\nMa tran B\n";
Xuat(B,n);
if(HeTamGiacTren(A,X,B,n))
XuatNghiem(X,n,"x");
else
cout<<"\nHe phuong trinh tuyen tinh vo nghiem";
getch();
return 0;
}
Kết quả chạy chương trình:
Tag: C, C++, Hệ phương trình tuyến tính, tam giác trên, tam giác dưới
Nghiệm của hệ trên được xác định như sau:
Cài đặt chương trình:
#include <conio.h>
#include <iostream>
#define max 100
using namespace std;
/* Nhập ma trận tam giác trên */
void Nhap(float A[max][max],int n) {
for(int i = 0; i<n; i++)
for(int j = i; j<n; j++) {
cout<<"a["<<i<<"]["<<j<<"] = ";
cin>>A[i][j];
}
}
/* Nhập ma trận hệ số tự do */
void Nhap(float B[max],int n){
for(int i = 0; i<n; i++) {
cout<<"b["<<i<<"] = ";
cin>>B[i];
}
}
/* Xuất ma trận hệ số tự do */
void Xuat(float B[max],int n) {
cout<<"(";
for(int i = 0; i<n-1; i++)
cout<<B[i]<<",";
cout<<B[n-1]<<")";
}
/*Xuất ma trận */
void Xuat(float A[max][max], int n){
cout<<"\n";
for(int i=0 ; i<n; i++){
cout<<endl;
for(int j=0 ; j<n; j++)
if(i>j)
cout<<"0\t";
else
cout<<A[i][j]<<"\t";
}
}
/* Xuất nghiệm */
void XuatNghiem(float X[], int n, char * s){
cout<<"\nNghiem cuahe A.X = B\n";
for(int i=0; i<n; i++)
cout<<s<<i+1<<" = "<<X[i]<<endl;
}
/* Hệ tam giác trên */
char HeTamGiacTren (float A[max][max], float X[max], float B[max], int n ) {
for(int i = n-1; i>=0; i--) {
if (A[i][i]!=0) {
if (i==n-1)
X[i] = B[i]/A[i][i];
else {
X[i] = B[i];
for(int j=i+1; j<n; j++)
X[i]=X[i]-A[i][j]*X[j];
X[i] = X[i]/A[i][i];
}
}
else
return 0;
}
return 1;
}
/* Chương trình chính */
int main(){
int n;
float A[max][max],B[max],X[max];
cout<<"So phuong trinh n = "; cin>>n;
cout<<"Nhap vao ma tran tam giac tren A\n";
Nhap(A,n);
cout<<"\nNhap vao ma tran he so B\n";
Nhap(B,n);
cout<<"Ma tran A";
Xuat(A,n);
cout<<"\nMa tran B\n";
Xuat(B,n);
if(HeTamGiacTren(A,X,B,n))
XuatNghiem(X,n,"x");
else
cout<<"\nHe phuong trinh tuyen tinh vo nghiem";
getch();
return 0;
}
Kết quả chạy chương trình:
Tag: C, C++, Hệ phương trình tuyến tính, tam giác trên, tam giác dưới
Thứ Bảy, 14 tháng 6, 2014
Code C++: Phép toán cộng, nhân hai ma trận
- Cộng hai ma trận
Cho A=(aij) và B=(bij) là hai ma trận cùng cấp m x n. Khi đó, C=A ± B cũng là ma trận cấp m x n và được xác định bởi cij = aij ± bij, 1 ≤ i ≤ m, 1 ≤ j ≤ n.
- Nhân hai ma trận
Cho A=(aij) là ma trận trận cấp m x n và B=(bjk) là ma trận cấp n x p. Khi đó, C=A.B = (cik) là ma trận cấp m x p.
Cài đặt mã nguồn
#include <conio.h>
#include <iostream>
#define max 100
using namespace std;
/*Hàm nhập ma trận hệ số*/
void NhapMaTran(float A[max][max], int m, int n, char ch) {
for(int i = 0; i<m; i++)
for(int j = 0; j<n; j++) {
cout<<ch<<"["<<i<<"]["<<j<<"] = ";
cin>>A[i][j];
}
}
/* Hàm xuất ma trận*/
void XuatMaTran(float A[max][max], int m, int n) {
for(int i=0 ; i<m; i++){
cout<<endl;
for(int j=0 ; j<n; j++)
cout<<A[i][j]<<"\t";
}
}
/*C = A+B*/
void CongMaTran(float A[max][max], float B[max][max], float C[max][max], int m, int n) {
for(int i = 0; i<m; i++)
for(int j = 0; j<n; j++)
C[i][j] = A[i][j]+B[i][j];
}
/*A cap mxn * B cap nxp = C cap mxp*/
void NhanMaTran(float A[max][max],float B[max][max], float C[max][max],int m,int n,int p){
for(int i = 0; i<m; i++)
for(int k = 0; k<p; k++) {
C[i][k]=0;
for(int j = 0; j<n; j++)
C[i][k] = C[i][k]+A[i][j]*B[j][k];
}
}
/*Chuong trinh chinh*/
int main() {
int m,n,p;
float A[max][max],B[max][max],C[max][max],D[max][max];
cout<<"Nhap m = "; cin>>m;
cout<<"Nhap n = "; cin>>n;
cout<<"Nhap p = "; cin>>p;
cout<<"Nhap ma tran A cap "<<m<<"x"<<n<<endl;
NhapMaTran(A,m,n,'A');
cout<<"Nhap ma tran B cap "<<m<<"x"<<n<<endl;
NhapMaTran(B,m,n,'B');
cout<<"Nhap ma tran C cap "<<n<<"x"<<p<<endl;
NhapMaTran(C,n,p,'C');
cout<<"Ma tran A";
XuatMaTran(A,m,n);
getch();
cout<<"\n\nMa tran B";
XuatMaTran(B,m,n);
getch();
cout<<"\n\nMa tran C";
XuatMaTran(C,n,p);
getch();
cout<<"\n\nMa tran D = A+B";
CongMaTran(A,B,D,m,n);
XuatMaTran(D,m,n);
getch();
cout<<"\n\nMa tran D = A.C";
NhanMaTran(A,C,D,m,n,p);
XuatMaTran(D,n,p);
getch();
return 0;
}
Kết quả khi chạy chương trình
Tag: C, C++, ma trận, matrix, array, cộng hai ma trận, nhân hai ma trận
Thứ Năm, 12 tháng 6, 2014
Bài 22: Học làm game thứ 3: Sushi Crush - Like Candy Crush or Bejewer ( Part 3 )
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
Thứ Ba, 3 tháng 6, 2014
Bài 21: Học làm game thứ 3: Sushi Crush - Like Candy Crush or Bejewer ( Part 2 )
Hi mọi người!
Chúng ta cùng tiếp tục bài học về game Sushi Crush nhé. Tổng kết lại bài trước 1 chút:
+ Tạo Class mới ( Sushi )
+ Tạo ma trận Sushi
+ Tạo Action rơi các sushi xuống ở màn chơi
Trước tiên, Mở file PlayLayer.cpp, thay đổi một số đoạn Code sau
Chúng ta cùng tiếp tục bài học về game Sushi Crush nhé. Tổng kết lại bài trước 1 chút:
+ Tạo Class mới ( Sushi )
+ Tạo ma trận Sushi
+ Tạo Action rơi các sushi xuống ở màn chơi
Trong bài này chúng ta sẽ tập trung nghiên cứu 1 số vấn đề sau đây:
+ Kiểm tra và "ăn" 1 dãy ( 3-4-5 ) Sushi cùng loại
+ Kiểm tra và "ăn" 1 dãy ( 3-4-5 ) Sushi cùng loại
+ Tạo hiệu ứng "nổ" khi ăn Sushi
+ Lấp đầy khoảng trống do các Sushi đã bị ăn để lại trên ma trận
Tất cả các vấn đề trên đây đều xảy ra trong lớp PlayLayer nhé các bạn, lớp SushiSprite tạm thời sẽ không phải động tới Code.
(Bài này khá dài do code cũng dài, nên chỗ nào sai chính tả thì bỏ qua vậy)
(Bài này khá dài do code cũng dài, nên chỗ nào sai chính tả thì bỏ qua vậy)
Bắt đầu thôi!
Mở file PlayLayer.h. thêm code như sau
Phần Public:
virtual void update(float dt) override; // Hàm này update game Scene theo thời gian dt ( 1/60 ở file AppDelegate.cpp đó)
Phần Private:
bool m_isAnimationing; // biến kiểm tra việc đang ăn, rơi, hay hành động khác của Sushi hay không
void checkAndRemoveChain(); // Kiểm tra và ăn dãy Sushi
void getColChain(SushiSprite *sushi, std::list<SushiSprite *> &chainList); // Kiểm tra tồn tại dãy Sushi theo cột hay không? - Lấy ra 1 List Sushi giống nhau ( kiểu &chainList là kiểu tham chiếu trong C+, dùng để thay đổi tham số truyền vào hàm thông qua việc lấy địa chỉ. Tuy giống con trỏ, nhưng nó có điểm khác con trỏ là ko phải dùng dấu *tên biến để thao tác mà dùng trực tiếp tên biến )
void getRowChain(SushiSprite *sushi, std::list<SushiSprite *> &chainList); // Kiểm tra tồn tại dãy Sushi theo hàng hay không, Lấy ra bởi List
void removeSushi(std::list<SushiSprite *> &sushiList); // Xóa bỏ List Sushi, Ăn chuỗi Sushi
void explodeSushi(SushiSprite *sushi); // Hiệu ứng nổ khi ăn Sushi
void fillVacancies(); // Điền đầy khoảng trống do dãy Sushi đã bị ăn mất
B1 - Kiểm tra và ăn dãy Sushi cùng loại theo hàng và cột
// Kích thước ma trận 7x9
#define MATRIX_WIDTH (7)
#define MATRIX_HEIGHT (9)
PlayLayer::PlayLayer()
: spriteSheet(NULL)
, m_matrix(NULL)
, m_width(0)
, m_height(0)
, m_matrixLeftBottomX(0)
, m_matrixLeftBottomY(0)
, m_isAnimationing(true) // Thêm vào hàm tạo
{
}
Trong hàm init(), thêm lệnh
scheduleUpdate(); // Update Scene theo thời gian
ở cuối trước return true;
Có lệnh scheduleUpdate(); thì phải có Hàm này để update theo dt
void PlayLayer::update(float dt)
{
// Kiểm tra giá trị lần đầu của m_isAnimationing, mỗi bước thời gian dt, sẽ lại kiểm tra m_isAnimationing là true hay flase
if (m_isAnimationing) { // nếu True
// Gán = false
m_isAnimationing = false;
// Duyệt trong toàn ma trận
for (int i = 0; i < m_height * m_width; i++) {
SushiSprite *sushi = m_matrix[i];
// Nếu tồn tại 1 Sushi mà đang có "Action" thì m_isAnimationing = true, và thoát vòng lặp
if (sushi && sushi->getNumberOfRunningActions() > 0) {
m_isAnimationing = true;
break;
}
}
}
// Đến khi không có Action nào của Sushi tại bước thời gian dt nào đó, thì kiểm tra việc "Ăn" dãy Sushi nếu tồn tại
if (!m_isAnimationing) {
checkAndRemoveChain();
}
}
Kiểm tra việc ăn dãy Sushi giống nhau
void PlayLayer::checkAndRemoveChain()
{
// Duyệt ma trận
for (int i = 0; i < m_height * m_width; i++) {
SushiSprite *sushi = m_matrix[i];
if (!sushi) { // Rỗng thì bỏ qua
continue;
}
// Đếm số lượng Sushi tạo thành chuỗi
// Tạo 1 List để chứa các Sushi giống nhau
std::list<SushiSprite *> colChainList; // Chuỗi Sushi theo cột
getColChain(sushi, colChainList); // Lấy ra Chuỗi Sushi giống nhau theo cột, chú ý chỗ này ko còn dấu & giống như ở phần khai báo trong file .h, đây là cách dùng biến tham chiếu trong C++
std::list<SushiSprite *> rowChainList; // Chuỗi Sushi theo hàng
getRowChain(sushi, rowChainList); // Lấy ra Chuỗi Sushi giống nhau theo hàng
// &longerList = biến tham chiếu
// So sánh chuỗi dọc và chuỗi ngang, gán chuỗi lớn hơn cho longerList, tại sao lại có chỗ này, vì chỗ này vẫn trong vòng lặp với thứ tự Sushi thứ i có thể sẽ tồn tại 1 dấu CỘNG tạo bởi 2 chuỗi dọc và ngang cùng chứa Sushi i . Chơi candy, bejewer là biết
// So sánh chuỗi dọc và chuỗi ngang, gán chuỗi lớn hơn cho longerList, tại sao lại có chỗ này, vì chỗ này vẫn trong vòng lặp với thứ tự Sushi thứ i có thể sẽ tồn tại 1 dấu CỘNG tạo bởi 2 chuỗi dọc và ngang cùng chứa Sushi i . Chơi candy, bejewer là biết
std::list<SushiSprite *> &longerList = colChainList.size() > rowChainList.size() ? colChainList : rowChainList;
// Chuỗi longer có 3 Sushi thì xóa bỏ chuỗi đó
// Chuỗi longer có 3 Sushi thì xóa bỏ chuỗi đó
if (longerList.size() == 3) {
removeSushi(longerList); // Ăn thôi
return;
}
if (longerList.size() > 3) {
// Tạo 1 Sushi Đặc biệt ở đây
removeSushi(longerList);
return;
}
}
}
Ta cùng tìm và kiểm tra sự tồn tại của Chuỗi các Sushi giống nhau theo hàng và cột trong ma trận qua 2 hàm sau đây
void PlayLayer::getColChain(SushiSprite *sushi, std::list<SushiSprite *> &chainList)
{
chainList.push_back(sushi); // Thêm vào dãy Sushi đầu tiên, tại vị trí thứ i đang xét trong vòng lặp FOR của hàm checkAndRemoveChain
int neighborCol = sushi->getCol() - 1; // Xét cột bên trái
while (neighborCol >= 0) { // Tồn tại cột bên trái
// Tạo 1 pointer Sushi "bên trái" trỏ vào Sushi tại vị trí (Hàng * width + neighborCol ), đây là cách quy ma trận cấp 2 về mảng 1 chiều nhé
SushiSprite *neighborSushi = m_matrix[sushi->getRow() * m_width + neighborCol];
// Nếu tồn tại sushi bên trái và cùng imgIndex (cùng loại Sushi) với sushi đang xét thì..
if (neighborSushi && (neighborSushi->getImgIndex() == sushi->getImgIndex())) {
// Thêm sushi trái này vào list
chainList.push_back(neighborSushi);
neighborCol--; // Xét tiếp Sushi bên trái đến khi ko còn Sushi nào, cột 0
} else {
break; // Ko thỏa mãn đk if ở trên, Phá vòng while
}
}
neighborCol = sushi->getCol() + 1; // Xét Sushi bên phải
while (neighborCol < m_width) { // Xét đến cột cuối cùng, cột cuối = m_width - nhé
// Tương tự trên tìm ông sushi cùng loại bên trái
SushiSprite *neighborSushi = m_matrix[sushi->getRow() * m_width + neighborCol];
if (neighborSushi && (neighborSushi->getImgIndex() == sushi->getImgIndex())) {
chainList.push_back(neighborSushi); // Nhét vào List
neighborCol++;
} else {
break; // Phá vòng while
}
}
}
// Giải thích Tương tự getColChain nhỉ
void PlayLayer::getRowChain(SushiSprite *sushi, std::list<SushiSprite *> &chainList)
{
chainList.push_back(sushi);
int neighborRow = sushi->getRow() - 1; // Xét sushi bên dưới
while (neighborRow >= 0) {
SushiSprite *neighborSushi = m_matrix[neighborRow * m_width + sushi->getCol()];
if (neighborSushi && (neighborSushi->getImgIndex() == sushi->getImgIndex())) {
chainList.push_back(neighborSushi);
neighborRow--;
} else {
break;
}
}
neighborRow = sushi->getRow() + 1; // Xét sushi bên trên
while (neighborRow < m_height) {
SushiSprite *neighborSushi = m_matrix[neighborRow * m_width + sushi->getCol()];
if (neighborSushi && (neighborSushi->getImgIndex() == sushi->getImgIndex())) {
chainList.push_back(neighborSushi);
neighborRow++;
} else {
break;
}
}
}
Và đây là hàm ĂN Sushi ( đọc lại hàm checkAndRemoveChain() để hiểu rõ cơ chế)
void PlayLayer::removeSushi(std::list<SushiSprite *> &sushiList)
{
m_isAnimationing = true;
std::list<SushiSprite *>::iterator itList; // Con trỏ duyệt trong List
// Cú pháp vòng For duyệt 1 List trong C++ nâng cao
for (itList = sushiList.begin(); itList != sushiList.end(); itList++) {
// Chỗ này có vẻ hơi khó hiểu do nhiều dấu * nhỉ, Thế này nhé SushiSprite *sushi là tạo ra 1 con trỏ kiểu SushiSprite, (SushiSprite *) là ép kiểu con trỏ, *itList là giá trị chứa trong con trỏ, vì giá trị này lại là 1 con trỏ nên mới có việc ép kiểu con trỏ (SushiSprite *). iList là 1 con trỏ lại duyệt 1 mảng con trỏ nên có vẻ phức tạp thế này. Bạn hãy đọc lại về mảng con trỏ trong C++ là có thể hiểu
SushiSprite *sushi = (SushiSprite *)*itList;
// Loại bỏ sushi i ra khỏi ma trận
m_matrix[sushi->getRow() * m_width + sushi->getCol()] = NULL;
explodeSushi(sushi); // Tạo hiệu ứng nổ
}
// Rơi xuống để lấp đầy chỗ trống tạo bởi Sushi đã bị ăn
fillVacancies();
}
Các hạm này sử dụng để kiểm tra và ăn các chuỗi Sushi giống nhau ( >3 ). Ta hãy cùng nghiên cứu 2 phần nhỏ sau
B2 - Tạo hiệu ứng nổ khi ăn Sushi
Vẫn trong PlayLayer.cpp, Bạn thêm 1 hàm này
void PlayLayer::explodeSushi(SushiSprite *sushi)
{
// Thời gian hiệu ứng 0,3 giây
float time = 0.3;
// Thực hiện 2 hành động tuần tự, Co Sushi về kích thước, 0, 0, sau đó tự remove khỏi Contener cha
sushi->runAction(Sequence::create(
ScaleTo::create(time, 0.0), // Co kích thước về 0 trong thời gian 0.3
CallFunc::create(CC_CALLBACK_0(Sprite::removeFromParent, sushi)),
NULL));
// Action của Sprite tròn, mô phỏng vụ nổ
auto circleSprite = Sprite::create("circle.png"); // Tạo mới sprite tròn
addChild(circleSprite, 10);
circleSprite->setPosition(sushi->getPosition()); // Vị trí = vị trí Sushi
circleSprite->setScale(0); // Kích thước đầu =0
// Thực hiện hành động tuần tự sau, Tăng kích thước lên tỷ lệ 1.0 trong thời gian 0,3 giây, sau đó xóa khỏi Layer
circleSprite->runAction(Sequence::create(ScaleTo::create(time, 1.0),
CallFunc::create(CC_CALLBACK_0(Sprite::removeFromParent, circleSprite)),
NULL));
// 3. Tạo hiệu ứng particleStars, CHÚ Ý
auto particleStars = ParticleSystemQuad::create("stars.plist"); // Tạo mới
particleStars->setAutoRemoveOnFinish(true); // Tự động remove khi xong việc
particleStars->setBlendAdditive(false); // Thiết lập sự pha trộn thêm vào = false
particleStars->setPosition(sushi->getPosition()); // Đặt vị trí tại Sushi nổ
particleStars->setScale(0.3); // Thiết lập tỉ lệ 0.3
addChild(particleStars, 20); // Thêm vào Layer Play
}
Vâng hiệu ứng nổ và 1 chút màu mè đẹp mắt chỉ có vậy thôi. Tiếp theo là phần cho các Sushi rơi xuống điền đầy vào chỗ trống của các Sushi đã bị ăn
B3 - Lấp đầy khoảng trống do Sushi bị ăn để lại trên Ma trận
void PlayLayer::fillVacancies()
{
Size size = CCDirector::getInstance()->getWinSize();
// Chỗ này nhìn có vẻ phức tạp nhưng chẳng có gì đâu, chỉ là khai báo con trỏ, cấp phát bộ nhớ cho nó thôi, dùng như mảng 1 chiều
int *colEmptyInfo = (int *)malloc(sizeof(int) * m_width);
memset((void *)colEmptyInfo, 0, sizeof(int) * m_width); // set giá trị là 0 hết
// Rơi Sushi đang có xuống khoảng trống
SushiSprite *sushi = NULL; // Tạo 1 con trỏ Sushi = Null,
// Duyệt ma trận. Lưu ý ở đây 1 chút, chúng ta thường duyệt mảng 2 chiều theo thứ tự hàng, rồi đến cột, nhưng ở đây, hơi ngược 1 tý là cột rồi đến hàng. Và lưu ý rằng Cột 0, và Hàng 0 nằm ở vị trí bên Dưới phía Trái nhé. khi tạo ma trận ta cho viên Sushi 0,0 rơi xuống trước tiên mà
for (int col = 0; col < m_width; col++) { // Duyệt theo cột, từ trái sang phải
int removedSushiOfCol = 0;
// Duyệt theo hàng, từ dưới lên trên
for (int row = 0; row < m_height; row++) {
sushi = m_matrix[row * m_width + col]; // Sushi tại vị trí hàng, cột
if (NULL == sushi) { // Nếu rỗng
removedSushiOfCol++; // Đếm số Sushi đã bị "ăn"
} else { // Nếu ko rỗng
if (removedSushiOfCol > 0) { // Nếu bên dưới nó có ô trống = số Sushi bị ăn
// Làm rơi xuống
int newRow = row - removedSushiOfCol; //Vị trí hàng mới ( giảm xuống )
// Trong ma trận ta bỏ sushi ở hàng row, và chuyển nó xuống dưới qua removedSushiOfCol ô rỗng
m_matrix[newRow * m_width + col] = sushi;
m_matrix[row * m_width + col] = NULL;
//Di chuyển
Point startPosition = sushi->getPosition();
Point endPosition = positionOfItem(newRow, col);
float speed = (startPosition.y - endPosition.y) / size.height; // Tốc độ
sushi->stopAllActions(); // Dừng mọi chuyển động trước đó của Sushi
sushi->runAction(MoveTo::create(speed, endPosition)); // Di chuyển = rơi xuống
// set hàng mới cho Sushi tại vị trí mới này
sushi->setRow(newRow);
}
}
}
// Mảng lưu trữ số lượng Sushi bị ăn tại vị trí Cột xác định
colEmptyInfo[col] = removedSushiOfCol;
}
// 2. Tạo mới và làm rơi các Sushi xuống khoảng trống , lấp đầy ma trận
for (int col = 0; col < m_width; col++) { // Duyệt cột từ trái sang phải
// Duyệt hàng, chỉ xét từ vị trí rỗng trở lên
for (int row = m_height - colEmptyInfo[col]; row < m_height; row++) {
createAndDropSushi(row, col); // Tạo Sushi và rơi xuống vị trí Row, Col
}
}
free(colEmptyInfo); // Giải phóng con trỏ
}
Để dễ hình dung về việc rơi và điền đầy khoảng trống, hãy xem hình ảnh sau đây
Ta cùng tìm và kiểm tra sự tồn tại của Chuỗi các Sushi giống nhau theo hàng và cột trong ma trận qua 2 hàm sau đây
void PlayLayer::getColChain(SushiSprite *sushi, std::list<SushiSprite *> &chainList)
{
chainList.push_back(sushi); // Thêm vào dãy Sushi đầu tiên, tại vị trí thứ i đang xét trong vòng lặp FOR của hàm checkAndRemoveChain
int neighborCol = sushi->getCol() - 1; // Xét cột bên trái
while (neighborCol >= 0) { // Tồn tại cột bên trái
// Tạo 1 pointer Sushi "bên trái" trỏ vào Sushi tại vị trí (Hàng * width + neighborCol ), đây là cách quy ma trận cấp 2 về mảng 1 chiều nhé
SushiSprite *neighborSushi = m_matrix[sushi->getRow() * m_width + neighborCol];
// Nếu tồn tại sushi bên trái và cùng imgIndex (cùng loại Sushi) với sushi đang xét thì..
if (neighborSushi && (neighborSushi->getImgIndex() == sushi->getImgIndex())) {
// Thêm sushi trái này vào list
chainList.push_back(neighborSushi);
neighborCol--; // Xét tiếp Sushi bên trái đến khi ko còn Sushi nào, cột 0
} else {
break; // Ko thỏa mãn đk if ở trên, Phá vòng while
}
}
neighborCol = sushi->getCol() + 1; // Xét Sushi bên phải
while (neighborCol < m_width) { // Xét đến cột cuối cùng, cột cuối = m_width - nhé
// Tương tự trên tìm ông sushi cùng loại bên trái
SushiSprite *neighborSushi = m_matrix[sushi->getRow() * m_width + neighborCol];
if (neighborSushi && (neighborSushi->getImgIndex() == sushi->getImgIndex())) {
chainList.push_back(neighborSushi); // Nhét vào List
neighborCol++;
} else {
break; // Phá vòng while
}
}
}
// Giải thích Tương tự getColChain nhỉ
void PlayLayer::getRowChain(SushiSprite *sushi, std::list<SushiSprite *> &chainList)
{
chainList.push_back(sushi);
int neighborRow = sushi->getRow() - 1; // Xét sushi bên dưới
while (neighborRow >= 0) {
SushiSprite *neighborSushi = m_matrix[neighborRow * m_width + sushi->getCol()];
if (neighborSushi && (neighborSushi->getImgIndex() == sushi->getImgIndex())) {
chainList.push_back(neighborSushi);
neighborRow--;
} else {
break;
}
}
neighborRow = sushi->getRow() + 1; // Xét sushi bên trên
while (neighborRow < m_height) {
SushiSprite *neighborSushi = m_matrix[neighborRow * m_width + sushi->getCol()];
if (neighborSushi && (neighborSushi->getImgIndex() == sushi->getImgIndex())) {
chainList.push_back(neighborSushi);
neighborRow++;
} else {
break;
}
}
}
Và đây là hàm ĂN Sushi ( đọc lại hàm checkAndRemoveChain() để hiểu rõ cơ chế)
void PlayLayer::removeSushi(std::list<SushiSprite *> &sushiList)
{
m_isAnimationing = true;
std::list<SushiSprite *>::iterator itList; // Con trỏ duyệt trong List
// Cú pháp vòng For duyệt 1 List trong C++ nâng cao
for (itList = sushiList.begin(); itList != sushiList.end(); itList++) {
// Chỗ này có vẻ hơi khó hiểu do nhiều dấu * nhỉ, Thế này nhé SushiSprite *sushi là tạo ra 1 con trỏ kiểu SushiSprite, (SushiSprite *) là ép kiểu con trỏ, *itList là giá trị chứa trong con trỏ, vì giá trị này lại là 1 con trỏ nên mới có việc ép kiểu con trỏ (SushiSprite *). iList là 1 con trỏ lại duyệt 1 mảng con trỏ nên có vẻ phức tạp thế này. Bạn hãy đọc lại về mảng con trỏ trong C++ là có thể hiểu
SushiSprite *sushi = (SushiSprite *)*itList;
// Loại bỏ sushi i ra khỏi ma trận
m_matrix[sushi->getRow() * m_width + sushi->getCol()] = NULL;
explodeSushi(sushi); // Tạo hiệu ứng nổ
}
// Rơi xuống để lấp đầy chỗ trống tạo bởi Sushi đã bị ăn
fillVacancies();
}
Các hạm này sử dụng để kiểm tra và ăn các chuỗi Sushi giống nhau ( >3 ). Ta hãy cùng nghiên cứu 2 phần nhỏ sau
B2 - Tạo hiệu ứng nổ khi ăn Sushi
Vẫn trong PlayLayer.cpp, Bạn thêm 1 hàm này
void PlayLayer::explodeSushi(SushiSprite *sushi)
{
// Thời gian hiệu ứng 0,3 giây
float time = 0.3;
// Thực hiện 2 hành động tuần tự, Co Sushi về kích thước, 0, 0, sau đó tự remove khỏi Contener cha
sushi->runAction(Sequence::create(
ScaleTo::create(time, 0.0), // Co kích thước về 0 trong thời gian 0.3
CallFunc::create(CC_CALLBACK_0(Sprite::removeFromParent, sushi)),
NULL));
// Action của Sprite tròn, mô phỏng vụ nổ
auto circleSprite = Sprite::create("circle.png"); // Tạo mới sprite tròn
addChild(circleSprite, 10);
circleSprite->setPosition(sushi->getPosition()); // Vị trí = vị trí Sushi
circleSprite->setScale(0); // Kích thước đầu =0
// Thực hiện hành động tuần tự sau, Tăng kích thước lên tỷ lệ 1.0 trong thời gian 0,3 giây, sau đó xóa khỏi Layer
circleSprite->runAction(Sequence::create(ScaleTo::create(time, 1.0),
CallFunc::create(CC_CALLBACK_0(Sprite::removeFromParent, circleSprite)),
NULL));
// 3. Tạo hiệu ứng particleStars, CHÚ Ý
auto particleStars = ParticleSystemQuad::create("stars.plist"); // Tạo mới
particleStars->setAutoRemoveOnFinish(true); // Tự động remove khi xong việc
particleStars->setBlendAdditive(false); // Thiết lập sự pha trộn thêm vào = false
particleStars->setPosition(sushi->getPosition()); // Đặt vị trí tại Sushi nổ
particleStars->setScale(0.3); // Thiết lập tỉ lệ 0.3
addChild(particleStars, 20); // Thêm vào Layer Play
}
Vâng hiệu ứng nổ và 1 chút màu mè đẹp mắt chỉ có vậy thôi. Tiếp theo là phần cho các Sushi rơi xuống điền đầy vào chỗ trống của các Sushi đã bị ăn
B3 - Lấp đầy khoảng trống do Sushi bị ăn để lại trên Ma trận
void PlayLayer::fillVacancies()
{
Size size = CCDirector::getInstance()->getWinSize();
// Chỗ này nhìn có vẻ phức tạp nhưng chẳng có gì đâu, chỉ là khai báo con trỏ, cấp phát bộ nhớ cho nó thôi, dùng như mảng 1 chiều
int *colEmptyInfo = (int *)malloc(sizeof(int) * m_width);
memset((void *)colEmptyInfo, 0, sizeof(int) * m_width); // set giá trị là 0 hết
// Rơi Sushi đang có xuống khoảng trống
SushiSprite *sushi = NULL; // Tạo 1 con trỏ Sushi = Null,
// Duyệt ma trận. Lưu ý ở đây 1 chút, chúng ta thường duyệt mảng 2 chiều theo thứ tự hàng, rồi đến cột, nhưng ở đây, hơi ngược 1 tý là cột rồi đến hàng. Và lưu ý rằng Cột 0, và Hàng 0 nằm ở vị trí bên Dưới phía Trái nhé. khi tạo ma trận ta cho viên Sushi 0,0 rơi xuống trước tiên mà
for (int col = 0; col < m_width; col++) { // Duyệt theo cột, từ trái sang phải
int removedSushiOfCol = 0;
// Duyệt theo hàng, từ dưới lên trên
for (int row = 0; row < m_height; row++) {
sushi = m_matrix[row * m_width + col]; // Sushi tại vị trí hàng, cột
if (NULL == sushi) { // Nếu rỗng
removedSushiOfCol++; // Đếm số Sushi đã bị "ăn"
} else { // Nếu ko rỗng
if (removedSushiOfCol > 0) { // Nếu bên dưới nó có ô trống = số Sushi bị ăn
// Làm rơi xuống
int newRow = row - removedSushiOfCol; //Vị trí hàng mới ( giảm xuống )
// Trong ma trận ta bỏ sushi ở hàng row, và chuyển nó xuống dưới qua removedSushiOfCol ô rỗng
m_matrix[newRow * m_width + col] = sushi;
m_matrix[row * m_width + col] = NULL;
//Di chuyển
Point startPosition = sushi->getPosition();
Point endPosition = positionOfItem(newRow, col);
float speed = (startPosition.y - endPosition.y) / size.height; // Tốc độ
sushi->stopAllActions(); // Dừng mọi chuyển động trước đó của Sushi
sushi->runAction(MoveTo::create(speed, endPosition)); // Di chuyển = rơi xuống
// set hàng mới cho Sushi tại vị trí mới này
sushi->setRow(newRow);
}
}
}
// Mảng lưu trữ số lượng Sushi bị ăn tại vị trí Cột xác định
colEmptyInfo[col] = removedSushiOfCol;
}
// 2. Tạo mới và làm rơi các Sushi xuống khoảng trống , lấp đầy ma trận
for (int col = 0; col < m_width; col++) { // Duyệt cột từ trái sang phải
// Duyệt hàng, chỉ xét từ vị trí rỗng trở lên
for (int row = m_height - colEmptyInfo[col]; row < m_height; row++) {
createAndDropSushi(row, col); // Tạo Sushi và rơi xuống vị trí Row, Col
}
}
free(colEmptyInfo); // Giải phóng con trỏ
}
Để dễ hình dung về việc rơi và điền đầy khoảng trống, hãy xem hình ảnh sau đây
Xong rồi, giờ bạn có thể Build và run code được rồi đó.
Tuy nhiên, không có điều gì xảy ra cả, kết quả ra vẫn gần giống bài trước thôi ( trừ trường hợp nào vào màn chơi mà đã ăn được Sushi tự nhiên )
Tổng kết lại trong bài này chúng ta học được 1 số điều thú vị sau đây:
+ Kiểm tra dãy Sushi cùng loại
+ Ăn khi thỏa mãn điều kiện dãy đó >=3 Sushi
+ Tạo hiệu ứng nổ khi ăn
+ Rơi các Sushi lấp đầy khoảng trống
+ Làm việc với List
+ Ma trận 2 chiều, 1 chiều, cách quy đổi 2 chiều sang 1 chiều
+ Làm quen với hệ thống trang trí particle trong game, sẽ tìm hiểu ở các bài sau
Download
+ Class
+ Resource
Ở bài 3 chúng ta sẽ học cách di chuyển các Sushi, khi đó việc ăn Sushi, hiệu ứng nổ, rơi Sushi sẽ dễ dàng nhìn thấy hơn. Bài này chỉ là bài chuẩn bị cho bài sau thôi mà.
Chào và hẹn gặp lại ở bài sau
Đăng ký:
Bài đăng (Atom)









