테트리스 강좌

초등학생도 쉽게 따라할 수 있는 Unity 테트리스 게임 개발 강좌 자료

코드 리뷰 1단계: GameBoard

8단계 이후 최종 GameBoard.cs 전체 코드와 설명·복습입니다.

역할: 게임 보드 그리드(width×height)를 2차원 배열로 관리하고, 블록 고정(AddToGrid), 행 제거(ClearFullRows), 게임 오버 판단(IsGameOver), 칸 비었는지(IsCellEmpty)를 담당합니다.

관련 단계 복기: 2단계 - 게임 보드 만들기(width/height/grid, Awake, IsValidPosition) → 6단계 - 행 제거 시스템(IsCellEmpty, AddToGrid, ClearFullRows) → 8단계 - 게임 완성(IsGameOver 추가).


전체 코드

using UnityEngine;

/// <summary>
/// 게임 보드 그리드를 관리하는 클래스.
/// width×height 2차원 배열로 고정된 블록을 저장하고,
/// 블록 고정(AddToGrid), 행 제거(ClearFullRows), 게임 오버 판단(IsGameOver) 등을 담당.
/// </summary>
public class GameBoard : MonoBehaviour
{
    public int width = 10;      // 가로 칸 수 (기본 10)
    public int height = 20;     // 세로 칸 수 (기본 20)
    private Transform[,] grid;  // 2차원 배열: grid[x,y]에 해당 칸에 고정된 블록 조각(Transform) 저장, 비면 null

    /// <summary>씬 로드 시 한 번 실행. 그리드 배열을 생성하고 로그 출력.</summary>
    void Awake()
    {
        grid = new Transform[width, height];
        Debug.Log($"게임 보드 생성: {width}x{height}");
    }

    /// <summary>주어진 (x,y)가 보드 범위 안이면 true, 밖이면 false 반환.</summary>
    public bool IsValidPosition(Vector2Int position)
    {
        return position.x >= 0 && position.x < width &&
               position.y >= 0 && position.y < height;
    }

    /// <summary>
    /// 블록을 그리드에 고정. 블록의 각 자식(큐브)을 보드 칸에 배치하고 grid에 등록.
    /// 역순 순회: SetParent 시 childCount가 줄어들므로 정순이면 일부만 처리됨.
    /// </summary>
    public void AddToGrid(TetrisBlock block)
    {
        Vector3 parentPos = block.transform.position;
        for (int i = block.transform.childCount - 1; i >= 0; i--)   // 역순 필수
        {
            Transform child = block.transform.GetChild(i);
            Vector3 childWorld = parentPos + block.transform.TransformDirection(child.localPosition);
            Vector3Int cell = Vector3Int.RoundToInt(childWorld);
            if (cell.x >= 0 && cell.x < width && cell.y >= 0 && cell.y < height)
            {
                child.SetParent(transform);
                grid[cell.x, cell.y] = child;
                child.position = new Vector3(cell.x, cell.y, 0f);
            }
        }
    }

    /// <summary>스폰 영역(맨 위 2줄, row 18~19)에 블록이 하나라도 있으면 true. 게임 오버 판정용.</summary>
    public bool IsGameOver()
    {
        for (int y = height - 2; y < height; y++)
        {
            for (int x = 0; x < width; x++)
            {
                if (grid[x, y] != null)
                    return true;
            }
        }
        return false;
    }

    /// <summary>해당 칸이 비어 있으면 true. 보드 밖이거나 블록이 있으면 false. 이동 가능 여부 검사용.</summary>
    public bool IsCellEmpty(int x, int y)
    {
        if (x < 0 || x >= width || y < 0 || y >= height)
            return false;
        return grid[x, y] == null;
    }

    /// <summary>해당 행이 모두 채워졌는지 검사. 한 칸이라도 null이면 false.</summary>
    bool IsRowFull(int row)
    {
        for (int x = 0; x < width; x++)
        {
            if (grid[x, row] == null)
                return false;
        }
        return true;
    }

    /// <summary>해당 행의 모든 칸 오브젝트를 Destroy하고 grid를 null로 초기화.</summary>
    void ClearRow(int row)
    {
        for (int x = 0; x < width; x++)
        {
            if (grid[x, row] != null)
            {
                Destroy(grid[x, row].gameObject);
                grid[x, row] = null;
            }
        }
    }

    /// <summary>지운 행(clearedRow) 위쪽의 모든 행을 한 칸씩 아래로 내림. grid 참조와 position 갱신.</summary>
    void MoveRowsDown(int clearedRow)
    {
        for (int y = clearedRow + 1; y < height; y++)
        {
            for (int x = 0; x < width; x++)
            {
                if (grid[x, y] != null)
                {
                    grid[x, y - 1] = grid[x, y];
                    grid[x, y] = null;
                    grid[x, y - 1].position = new Vector3(x, y - 1, 0f);
                }
            }
        }
    }

    /// <summary>아래부터 가득 찬 행을 찾아 지우고, 위 행들을 내린 뒤 지운 줄 개수 반환. 점수 계산에 사용.</summary>
    public int ClearFullRows()
    {
        int linesCleared = 0;
        for (int y = 0; y < height; y++)
        {
            if (IsRowFull(y))
            {
                ClearRow(y);
                MoveRowsDown(y);
                y--;           // 같은 인덱스가 다시 채워졌을 수 있으므로 한 번 더 검사
                linesCleared++;
            }
        }
        return linesCleared;
    }
}

코드 설명

멤버 변수

Awake()

IsValidPosition(Vector2Int position)

AddToGrid(TetrisBlock block)

IsGameOver()

IsCellEmpty(int x, int y)

IsRowFull(int row) / ClearRow(int row) / MoveRowsDown(int clearedRow)

ClearFullRows()


복습

아래는 2단계·6단계·8단계에서 배운 내용을 정리한 복기 표입니다.

구분 내용 관련 단계
Awake vs Start Awake는 씬 로드 시 가장 먼저 한 번, Start는 그 다음 한 번. 보드처럼 “다른 스크립트보다 먼저 준비”할 때 Awake에서 grid 생성. 2단계
2차원 배열 Transform[,] grid로 [x, y] 칸에 고정된 블록 조각을 저장. null이면 빈 칸. 2단계
AddToGrid 역순 SetParent 하면 childCount가 줄어들므로, 자식을 돌 때 역순으로 돌아야 4개 모두 처리됨. 6단계
IsGameOver 맨 위 2줄(row 18, 19)만 검사. 스폰 위치가 그 근처이므로, 그곳에 블록이 있으면 더 스폰할 수 없음. 8단계
ClearFullRows 흐름 IsRowFull → ClearRow(지우기) → MoveRowsDown(위 행들 내리기) → 지운 줄 수 반환. 6단계

← 이전: 8단계 - 게임 완성 다음: 코드 리뷰 2단계 (GameController) →