Học làm game (6) - Lập trình với Unity (LT chức năng)

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)