Bài trước: Học làm game (5) - Lập trình với Unity
-----
1.1
Xây dựng các chức năng
Một số xử lý quan trọng
Với game Tetris, chúng ta cần viết các hàm để thực hiện các việc
sau:
– Kiểm tra các khối vuông (block) có nằm trong khung chữ nhật
không
– Kiểm tra các khối vuông có nằm trên đường biên ngang không
(y = 0)
– Kiểm tra một khối hình (group) có được di chuyển tới một vị
trí không
– Kiểm tra một hàng đã đầy các khối vuông chưa
– Xóa một hàng
– Giảm tọa độ của một hàng theo trục Oy
Cấu trúc dữ liệu
Chúng ta sẽ tạo ra một grid (khung lưới) là ma trận 2 chiều,
tương ứng với màn hình chơi (khung chữ nhật), gồm 20 hàng và 10 cột. Mỗi ô của
ma trận ứng với một ô vuông trên màn hình chơi.
Ma trận 20 x 10.
|
0 |
1 |
2 |
… |
9 |
0 |
O |
X |
X |
|
|
1 |
O |
X |
O |
|
|
2 |
X |
X |
O |
|
|
… |
|
|
|
|
|
19 |
|
|
|
|
|
Trong ma trận, ô nào có kí hiệu O là không có khối vuông, ô
nào có kí hiệu X là có khối vuông. Ví dụ, ở ma trận trên, tọa độ (0, 0) không có
khối vuông, tọa độ (0, 1) có khối vuông.
Nhờ vào ma trận, chúng ta dễ dàng truy cập và kiểm tra một vị
trí cụ thể. Ví dụ:
//
tại vị trí (3,4) có khối vuông không?
if (grid[3,4] != null)
{
//
các xử lý
}
Tạo lớp Playfield
Để cho đơn giản, chúng ta sẽ viết lớp Playfield ở cuối tập tin Spawner.cs.
// class Playfield
public class Playfield : MonoBehaviour
{
//
định nghĩa grid
public static int w = 10;
public static int h = 20;
public static Transform[,] grid = new Transform[w, h];
}
Ở đoạn mã trên, chúng ta đã khai báo biến grid là một mảng hai chiều 10 x 20 phần
tử, mỗi phần tử là một đối tượng kiểu Transform.
Đây là đối tượng có sẵn của Unity, do vậy chúng ta sẽ tận dụng được các thuộc
tính và phương thức có sẵn của Transform
để thực hiện các xử lý.
Viết hàm làm tròn
Trong lớp Playfield,
chúng ta viết hàm roundVec2 để làm
tròn một tọa độ. Vì trong quá trình xoay hình (rotate) có thể làm tọa độ có dạng
số thập phân. Ví dụ tọa độ (1.0001, 2)
sẽ được làm tròn thành (1,2).
//
hàm làm tròn một tọa độ
public static Vector2 roundVec2(Vector2 v)
{
return new Vector2(Mathf.Round(v.x),
Mathf.Round(v.y));
}
Viết hàm kiểm tra một
ví trí (x, y) có nằm trong khung chữ nhật không?
Một ví trí nằm trong khung chữ nhật, nếu giá trị x thuộc [0,
9] và y >= 0.
//
hàm kiểm tra 1 vị trí nó nằm trong khung chữ nhật không?
public static bool insideBorder(Vector2 pos)
{
return ((int)pos.x >= 0 &&
(int)pos.x < w) &&
(int)pos.y >= 0);
}
Viết hàm xóa một hàng
Khi người chơi lấp đầy các khối vuông theo một hàng ngang,
thì hàng đó sẽ được xóa khỏi khung chữ nhật.
//
hàm xóa một hàng khối vuông
public static void deleteRow(int y)
{
for (int x = 0; x < w; ++x)
{
Destroy(grid[x, y].gameObject);
grid[x, y] = null;
}
}
Đoạn mã trên sẽ duyệt lần lượt các cột từ 0 tới w -1; duyệt
trên hàng y; tại một vị trí sẽ xóa đối tượng gameObject và thiết đặt lại grid tại
vị trí [x,y] là chưa có khối vuông.
Viết hàm giảm các khối
vuông trong khung chữ nhật xuống một hàng
Sau khi xóa các khối vuông của một hàng ngang, chúng ta sẽ
giảm toàn bộ các khối vuông ở phía trên (của hàng vừa bị xóa) xuống một hàng.
//
hàm hạ các khối vuông xuống một hàng
public static void decreaseRow(int y)
{
for (int x = 0; x < w; ++x)
{
if (grid[x, y] != null)
{
// hạ xuống 1 hàng
grid[x, y -1] = grid[x, y];
grid[x, y] = null;
// cập nhật vị trí của khối vuông
grid[x, y - 1].position += new Vector3(0, -1, 0);
}
}
}
Viết hàm giảm nhiều hàng xuống một mức
Sau khi xóa một hàng mà người chơi đã xếp đầy, chúng ta phải
dịch chuyển toàn bộ các khối vuông phía trên xuống một mức.
//
giảm các hàng xuống một mức
public static void decreaseRowsAbove(int y)
{
for (int i = y; i < h; ++i)
{
decreaseRow(i);
}
}
Viết hàm kiểm tra xem
một hàng được xếp đầy chưa
Ở phía trên chúng ta đã viết hàm xóa một hàng, tuy nhiên chỉ
khi nào hàng đó đã được xếp đầy thì mới xóa.
//
hàng đã xếp đầy chưa?
public static bool inRowFull(int y)
{
for (int x = 0; x < w; ++x)
if (grid[x, y] == null)
return false;
return true;
}
Viết hàm xóa mọi hàng
đã được xếp đầy
Trong khi chơi, sẽ có trường hợp có nhiều hơn 1 hàng được xếp
đầy. Do vậy, chúng ta cần viết hàm để xóa mọi hàng đã được xếp đầy.
//
xóa mọi hàng đã được xếp đầy
public static void deleteFullRows()
{
for (int y = 0; y < h; ++y)
{
if (isRowFull(y))
{
deleteRow(y);
decreaseRowsAbove(y + 1);
--y;
}
}
}
Trong lệnh if, có
lệnh giảm y đi một đơn vị (--y), để xét hàng ngang đã bị hạ xuống một
mức do lệnh decreaseRowsAbove(y
+ 1);
Vậy là chúng ta đã hoàn thành các xử lý cho lớp Playfield. Gồm
các xử lý liên quan đến khung lưới (grid). Chúng ta đã tiếp cận theo cách lập
trình Bottom-up. Bằng cách viết các hàm đơn giản nhất trước, sau đó viết các
hàm phức tạp hơn, trong đó có sử dụng các hàm đơn giản đã viết.
-----
Cập nhật: 6/4/2023
Bài sau: Học làm game (7) - Lập trình với Unity (Hoàn thành game Tetris)