TIL

[멋쟁이사자처럼 부트캠프 TIL회고] 41일차 : 테트리스

Cadi 2025. 1. 1. 01:59

 

 

테트리스 구현하기

 

어제에 이어서 테트리스를 구현해보았다.

지금까지 했던 코드들 중 가장 긴 코드가 될 것 같다.

천천히 해석하면서 다시 볼 것이다.

using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using AYellowpaper.SerializedCollections;
    using Unity.VisualScripting;
    using UnityEngine;
    using Random = UnityEngine.Random;

    public enum TetrominoType : byte
    {
        None,
        I,
        O,
        Z,
        S,
        J,
        L,
        T,
        Max
    }

    public class TetrisManager : Singleton<TetrisManager>
    {
        const float X_OFFSET = 4.5f;
        const float Y_OFFSET = 8.5f;
        private const int LINE_MAX_INDEX = 10;
        
        [SerializeField] private Transform spawnPoint;
        [SerializeField] private SerializedDictionary<TetrominoType, string> tetrominoDatas;
        [SerializeField] private float dropTime = 1.0f;

        private TetrominoData _currentTetrominoData;
        private float currentDropTime = 0.0f;

        private int[][] grid = null;
        private Block[][] gridBlock = null;

        public override void OnAwake()
        {
            base.OnAwake();

            grid = new int[25][];
            gridBlock = new Block[25][];
            for (int i = 0; i < grid.Length; i++)
            {
                grid[i] = new int[10];
                gridBlock[i] = new Block[10];
            }
        }

        void Start()
        {
            currentDropTime = dropTime;
            SpawnTetromino();
        }

        private void OnDrawGizmos()
        {
            if (grid == null)
                return;
            
            for (int y = 0; y < grid.Length; ++y)
            {
                for (int x = 0; x < grid[y].Length; ++x)
                {
                    if (grid[y][x] == 1)
                    {
                        int a = 10;
                    }
                    
                    Color color = grid[y][x] == 1 ? Color.green : Color.red;
                    Gizmos.color = color;
                    Gizmos.DrawSphere(new Vector3(x - X_OFFSET, y - Y_OFFSET, 0), 0.3f);
                }
            }
        }

        void Update()
        {
            if (_currentTetrominoData.IsUnityNull())
                return;

            if (Input.GetKeyDown(KeyCode.A))
            {
                SetGridState(0);
                _currentTetrominoData.transform.position += Vector3.left;
                
                if (checkBlockCollision() || GridOverlapCheck())
                {
                    _currentTetrominoData.transform.position -= Vector3.left;
                }
                SetGridState(1);
            }

            if (Input.GetKeyDown(KeyCode.D))
            {
                SetGridState(0);
                _currentTetrominoData.transform.position += Vector3.right;
                if (checkBlockCollision() || GridOverlapCheck())
                {
                    _currentTetrominoData.transform.position -= Vector3.right;
                }
                SetGridState(1);
            }

            if (Input.GetKeyDown(KeyCode.W))
            {
                SetGridState(0);
                _currentTetrominoData.transform.Rotate(new Vector3(0, 0, -90));
                
                var (minX, minY, maxX, maxY) = GetGridState();
                
                if (checkBlockCollision()|| GridOverlapCheck() || 0 > minY)
                {
                    _currentTetrominoData.transform.Rotate(new Vector3(0, 0, 90));
                }
                SetGridState(1);
            }

            currentDropTime -= Time.deltaTime;
            if (currentDropTime <= 0.0f)
            {
                SetGridState(0);
                
                _currentTetrominoData.transform.position += Vector3.down;
                
                if (GridOverlapCheck())
                {
                    _currentTetrominoData.transform.position -= Vector3.down;
                    SetGridState(1);
                    SpawnTetromino();
                }
                else
                {
                    SetGridState(1);

                    var (minX, minY, maxX, maxY) = GetGridState();
                    
                    if (minY == -1)
                    {
                        
                        SetGridState(0);
                        _currentTetrominoData.transform.position -= Vector3.down;                    
                        SetGridState(1);

                        foreach (var block in _currentTetrominoData.Blocks)
                        {
                            block.transform.SetParent(null);
                            var (x, y) = GetXYIndex(block);
                            gridBlock[y][x] = block.GetComponent<Block>();
                        }

                        
                        int yIndex = 0;
                        int count = grid[yIndex].Count(e => e == 1);
                        Debug.Log(count);

                        if (LINE_MAX_INDEX == count)
                        {
                            for (var i = 0; i < grid[yIndex].Length; i++)
                            {
                                grid[yIndex][i] = 0;
                                Destroy(gridBlock[yIndex][i].gameObject);
                                gridBlock[yIndex][i] = null;
                            }

                            for (int i = 0; i < grid.Length - 1; ++i)
                            {
                                grid[i] = grid[i + 1];
                                for (int x = 0; x < gridBlock[i].Length; ++x)
                                {
                                    if (gridBlock[i][x])
                                        gridBlock[i][x].transform.position += Vector3.down;
                                }
                                gridBlock[i] = gridBlock[i + 1];
                            }
                            
                            for (var i = 0; i < grid[^1].Length; i++)
                            {
                                grid[^1][i] = 0;
                                gridBlock[^1][i] = null;
                            }
                        }
                        
                        SpawnTetromino();
                    }
                }
                
                currentDropTime = dropTime;
            }
        }

        private (int, int, int, int) GetGridState()
        {
            int minX = Int32.MaxValue;
            int minY = Int32.MaxValue;
            int maxX = Int32.MinValue;
            int maxY = Int32.MinValue;

            foreach (var block in _currentTetrominoData.Blocks)
            {
                var (x, y) = GetXYIndex(block);
                ;
                minX = Mathf.Min(minX, x);
                minY = Mathf.Min(minY, y);
                maxX = Mathf.Max(maxX, x);
                maxY = Mathf.Max(maxY, y);
            }

            return (minX, minY, maxX, maxY);
        }
        
        private bool GridOverlapCheck()
        {
            foreach (var block in _currentTetrominoData.Blocks)
            {
                var (x, y) = GetXYIndex(block);

                if (y >= 0 && x >= 0 && x < grid[y].Length && grid[y][x] == 1)
                {
                    return true;
                }
            }

            return false;
        }
        
        private void SetGridState(int state)
        {
            foreach (var block in _currentTetrominoData.Blocks)
            {
                var (x, y) = GetXYIndex(block);

                if (y >= 0 && x >= 0 && x < grid[y].Length) 
                    grid[y][x] = state;
            }
        }

        private static (int, int) GetXYIndex(Transform block)
        {
            int y = Mathf.RoundToInt(block.transform.position.y + Y_OFFSET);
            int x = Mathf.RoundToInt(block.transform.position.x + X_OFFSET);
            return (x, y);
        }


        private void SpawnTetromino()
        {
            GameObject Tetromino_Prefab = null;
            TetrominoType nextBlockIndex = TetrominoType.I;//(TetrominoType)Random.Range(0, (int)TetrominoType.Max - 1) + 1;
            Tetromino_Prefab = Resources.Load<GameObject>($"Prefab/{tetrominoDatas[nextBlockIndex]}");

            GameObject spawndTetromino = Instantiate(Tetromino_Prefab, spawnPoint.position, Quaternion.identity);
            spawndTetromino.TryGetComponent(out _currentTetrominoData);
        }


        bool checkBlockCollision()
        {
            foreach (var block in _currentTetrominoData.Blocks)
            {
                var (x, y) = GetXYIndex(block);
                if (x < 0 || x >= grid[0].Length)
                {
                    return true;
                }
            }

            return false;
        }
    }

 

 

우선은 enum 타입으로 우리가 생성하게 될 테트로미노타입을 선언해준다. 총 7 가지이며,

None은 Null 처리에, Max는 길이를 체크하기 위해 습관적으로 쓰는 편이라고 하셨다. 

굳이 저렇게 쓰지 않고도, 

int length = Enum.GetValues(typeof(TetrominoType)).Length - 2; // None과 Max 제외

이와 같은 방법으로 구할 수도 있다. 

 

 const float X_OFFSET = 4.5f;
        const float Y_OFFSET = 8.5f;
        private const int LINE_MAX_INDEX = 10;
        
        [SerializeField] private Transform spawnPoint;
        [SerializeField] private SerializedDictionary<TetrominoType, string> tetrominoDatas;
        [SerializeField] private float dropTime = 1.0f;

        private TetrominoData _currentTetrominoData;
        private float currentDropTime = 0.0f;

        private int[][] grid = null;
        private Block[][] gridBlock = null;

 

다음은 변수 선언 부분이다 X와 Y의 Offset은 기본적으로 0,0을 그리드의 원점으로 잡고 좌표와 일치시키기 위해 설정해 주었다.

블럭의 중심점이 중심 좌표가 아니라 좌측 아래 코너를 기준으로 잡기 위해 오프셋을 설정한 것이다.

LineMaxIndex는 배열을 하나하나 검사하며, 그 배열에 들어 있는 숫자가 10이라면, 즉 그 줄이 꽉 차 있다면 삭제하기 위해 설정해둔 숫자이다.

 

스폰포인트는 생성 위치이고,

tertrominoDates는 새로운 에셋인 SerialzeDictionary를 사용하기위해 만들어둔 데이터이다.

 

dropTime은 한 칸 내려오는데 걸리는 시간이다.

currentDropTime을 초기화 해준다.

 

TetrominData(트랜스폼 타입으로 블럭들을 배열에 저장했던 클래스) 타입으로 현재 테트로미노의 데이터를 선언한다.

 

grid (격자)로 존재와 삭제를 계산할 것이기에 테트리스 판의 배열에 맞게 선언해주고 초기화 해 준다.

gridBlock은 tetrominoDates를 분해하고, 분해한 Block들을 각각 넣을 위치이다. \

 

grid는 블록의 존재 여부를 나타내는 2차원 배열을 저장한 것이고

gridBlcok은 실제 Block 데이터를 각 위치에 저장한 것이다. 

논리적 상태인 grid와, 시각적/물리적 상태인 gridBlock을 따로 관리해 로직과 렌더링을 독립적으로 한다.

 

 

public override void OnAwake()
        {
            base.OnAwake();

            grid = new int[25][];
            gridBlock = new Block[25][];
            for (int i = 0; i < grid.Length; i++)
            {
                grid[i] = new int[10];
                gridBlock[i] = new Block[10];
            }
        }

 

 

OnAwake() 에서 (Awake 아님에 주의)

grid의 배열과 gridBlcok의 다차원 배열을 선언해준다. 

 

 

void Start()
        {
            currentDropTime = dropTime;
            SpawnTetromino();
        }

 

시작하면 시간을 초기화해주고 첫 테트로미노를 스폰해준다. 

 

  private void OnDrawGizmos()
        {
            if (grid == null)
                return;
            
            for (int y = 0; y < grid.Length; ++y)
            {
                for (int x = 0; x < grid[y].Length; ++x)
                {
                    if (grid[y][x] == 1)
                    {
                        int a = 10;
                    }
                    
                    Color color = grid[y][x] == 1 ? Color.green : Color.red;
                    Gizmos.color = color;
                    Gizmos.DrawSphere(new Vector3(x - X_OFFSET, y - Y_OFFSET, 0), 0.3f);
                }
            }
        }

 

기즈모를 그려서 확인을 쉽게 한다. grid가 있으면 green 없으면 red로 화면에 그려진다.

 

 

다음부터는 업데이트 문 안에서 작업하는 것들이다.

우선 예외처리(null처리)를 해준다. 

 

if (_currentTetrominoData.IsUnityNull())
                return;

 

*IsUnityNull()은 아직 잘 모른다.  ==null과 비슷하다고 생각

 

이동을 처리해줄 것인데, 우리는  이동을 할 때마다 grid에 있는 0과 1 값을 바꿔주어야 한다.

블럭이 있으면 1, 없으면 1이 되어야 한다.

 

이를 처리하기 위한 함수가 바로 SetGridState()와 GetXYInex()이다.

    private void SetGridState(int state)
        {
            foreach (var block in _currentTetrominoData.Blocks)
            {
                var (x, y) = GetXYIndex(block);

                if (y >= 0 && x >= 0 && x < grid[y].Length) 
                    grid[y][x] = state;
            }
        }

        private static (int, int) GetXYIndex(Transform block)
        {
            int y = Mathf.RoundToInt(block.transform.position.y + Y_OFFSET);
            int x = Mathf.RoundToInt(block.transform.position.x + X_OFFSET);
            return (x, y);
        }

 

우선 GetXYIndex(Transform block)은 block의 위치 정보를 오프셋을 더한 x, y의 값으로 만들어준다.

그리고 이 x, y 값을 받아 grid에서[y][x]의 위치에 있는 배열에 매개변수로 받아온 state를 할당해준다.

 

그렇게 되면 예를 들어, SetGridState(1)이라면 현재 블럭이 있는 위치의 격자배열이 모두 1로 바뀌게 된다. 

 

그래서 예를 들어 다음과 같은 코드가 있을 때 

   if (Input.GetKeyDown(KeyCode.A))
            {
                SetGridState(0);
                _currentTetrominoData.transform.position += Vector3.left;
                
                if (checkBlockCollision() || GridOverlapCheck())
                {
                    _currentTetrominoData.transform.position -= Vector3.left;
                }
                SetGridState(1);
            }

 

 자기 위치의 배열의 숫자를 0으로 변경하고, 위치를 옮겨 보고, 옮겨지면 자신 위치의 배열에 1을 넣는다.

(옮겨지지 않으면 다시 돌아와서 1을 넣는다) 

 

이제 위치 변경의 가능성에 대해 알아보기 위해 다음과 같은 두 코드를 사용했다.

 private bool GridOverlapCheck()
        {
            foreach (var block in _currentTetrominoData.Blocks)
            {
                var (x, y) = GetXYIndex(block);

                if (y >= 0 && x >= 0 && x < grid[y].Length && grid[y][x] == 1)
                {
                    return true;
                }
            }
                 return false;
        }

 코드는 현재 블록의 데이터를 받아 GetXYIndex로 위치를 계산한 후, 그 위치에 1이 있다면 (블럭이 존재한다면)

참값을 반환한다. 즉 참값을 반환했으면 이동이 불가능하다는 뜻이다.

*y >= 0 && x >= 0 && x < grid[y].Length 는 전체 맵이다. 

 

  bool checkBlockCollision()
        {
            foreach (var block in _currentTetrominoData.Blocks)
            {
                var (x, y) = GetXYIndex(block);
                if (x < 0 || x >= grid[0].Length)
                {
                    return true;
                }
            }

            return false;
        }
    }

 

이것도 비슷한 코드로 좌우 범위를 넘어가면 참값을 반환한다(넘어가지 못하게 한다)

 

이제, 이동부분을 볼 차례이다. 

이동 부분은 우리가 위에서 정의했던 함수들을 사용해서

현재 위치의 Grid를 지우고 일단 위치를 옮겨 보고, 옮길 수 없다면 다시 돌아가서 Grid를 세팅한다.

 if (Input.GetKeyDown(KeyCode.A))
            {
                SetGridState(0);
                _currentTetrominoData.transform.position += Vector3.left;
                
                if (checkBlockCollision() || GridOverlapCheck())
                {
                    _currentTetrominoData.transform.position -= Vector3.left;
                }
                SetGridState(1);
            }

            if (Input.GetKeyDown(KeyCode.D))
            {
                SetGridState(0);
                _currentTetrominoData.transform.position += Vector3.right;
                if (checkBlockCollision() || GridOverlapCheck())
                {
                    _currentTetrominoData.transform.position -= Vector3.right;
                }
                SetGridState(1);
            }

            if (Input.GetKeyDown(KeyCode.W))
            {
                SetGridState(0);
                _currentTetrominoData.transform.Rotate(new Vector3(0, 0, -90));
                
                var (minX, minY, maxX, maxY) = GetGridState();
                
                if (checkBlockCollision()|| GridOverlapCheck() || 0 > minY)
                {
                    _currentTetrominoData.transform.Rotate(new Vector3(0, 0, 90));
                }
                SetGridState(1);
            }

 

checkBlockCollsion이거나 GridOVerlapCheck()가 참이라면, 즉 그 자리에 이미 블럭이 있어 격자가 1로 표시되어 있다면

원래 했던 이동 행동을 반대로 취해 주어 원래 상태로 돌아가고 격자를 재세팅한다.

 

 

테트리스는 자동으로 한 칸씩 내려가며 진행된다. 이는 전에 정의해두었던 dropTIme과 currentDropTime을 사용할 것이다.

 

맨 밑 칸 ( Y = 0)보다 밑으로 내려가지 않게 하기 위해, currentTetriomo의 블럭들의 transform데이터를 구해줘야 한다.

 private (int, int, int, int) GetGridState()
        {
            int minX = Int32.MaxValue;
            int minY = Int32.MaxValue;
            int maxX = Int32.MinValue;
            int maxY = Int32.MinValue;

            foreach (var block in _currentTetrominoData.Blocks)
            {
                var (x, y) = GetXYIndex(block);
                ;
                minX = Mathf.Min(minX, x);
                minY = Mathf.Min(minY, y);
                maxX = Mathf.Max(maxX, x);
                maxY = Mathf.Max(maxY, y);
            }

            return (minX, minY, maxX, maxY);
        }

 

GetGridState()는 튜플을 사용하여 4개의 int 값을 반환하는 함수이다.

minX = Int32.MaxValue는 어떤 수와 비교해도 minX를 비교한 숫자로 세팅하기 위해서 정의했다.

 

현재 테트로미노데이터에 있는 모든 블럭을 검사하여 각각 위치 x,y 값을 구하고

그 값을 minX, maxX, minY, maxY에 할당해준다.

 currentDropTime -= Time.deltaTime;
            if (currentDropTime <= 0.0f)
            {
                SetGridState(0);
                
                _currentTetrominoData.transform.position += Vector3.down;
                
                if (GridOverlapCheck())
                {
                    _currentTetrominoData.transform.position -= Vector3.down;
                    SetGridState(1);
                    SpawnTetromino();
                }
                else
                {
                    SetGridState(1);

                    var (minX, minY, maxX, maxY) = GetGridState();
                    
                    if (minY == -1)
                    {
                        
                        SetGridState(0);
                        _currentTetrominoData.transform.position -= Vector3.down;                    
                        SetGridState(1);

                        foreach (var block in _currentTetrominoData.Blocks)
                        {
                            block.transform.SetParent(null);
                            var (x, y) = GetXYIndex(block);
                            gridBlock[y][x] = block.GetComponent<Block>();
                        }

 

한 칸씩 내리다가, GirdOverllapCheck가 참이라면 (이미 존재하는 블럭을 통과하려 한다면)

내린 것을 취소하고, 그 위치에 grid를 세팅하고 새로운 Termino를 생상한다.

그렇지 않다면, 한 칸 내려간 위치에 그리드를 세팅하고 현재 그리드 상태를 구한다.

현재 그리드 중 가장 낮은 값이 -1 이라면(y =0보다 밑이라면) 다시 그리드의 세팅을 0으로 돌리고

한 칸 올려서 그리드를 세팅한다.

그리고 마지막 칸까지 내려온 것이므로, 이제 블럭들이 프리팹 아래에서 자식 관계로 있지 않게 해재해준다.

(줄 삭제를 할 때에 한 번에 삭제되는 것을 막기 위함)

그리고 위치의 X,Y좌표를 구한 뒤 gridBlock[y][x]좌표에 block이라는 컴포넌트를 추가한다.

 

 int yIndex = 0;
                        int count = grid[yIndex].Count(e => e == 1);
                        Debug.Log(count);

                        if (LINE_MAX_INDEX == count)
                        {
                            for (var i = 0; i < grid[yIndex].Length; i++)
                            {
                                grid[yIndex][i] = 0;
                                Destroy(gridBlock[yIndex][i].gameObject);
                                gridBlock[yIndex][i] = null;
                            }

                            for (int i = 0; i < grid.Length - 1; ++i)
                            {
                                grid[i] = grid[i + 1];
                                for (int x = 0; x < gridBlock[i].Length; ++x)
                                {
                                    if (gridBlock[i][x])
                                        gridBlock[i][x].transform.position += Vector3.down;
                                }
                                gridBlock[i] = gridBlock[i + 1];
                            }
                            
                            for (var i = 0; i < grid[^1].Length; i++)
                            {
                                grid[^1][i] = 0;
                                gridBlock[^1][i] = null;
                            }
                        }
                        
                        SpawnTetromino();
                    }
                }
                
                currentDropTime = dropTime;
            }
        }

 

다음은 1줄 삭제이다.

y inedx라는 변수를 정의하고, count로 y열의 1 갯수를 센다.(존재하는 블럭의 갯수를 센다)

만일 맥스 카운트라면, 즉 꽉 차 있다면

y열의 모든 grid를 0으로 만들고 모든 블럭들도 삭제하고 null을 할당한다.

 

이제 한 칸씩 내려야 한다.

맨 밑부터 한 칸씩, 다음줄을 참조해서 gird를 바꿔주고

존재하는 그리드 블럭들도girdBlock도 한 칸씩 내려준다.

 

현재 맨 윗 줄은 참조할 곳이 없고, 그대로 남겨진 상태이므로 지워준다.

 

이 과정이 끝나면 Spawn을 해준다. 

그리고 한 사이클이 지났으니 currentDropTime을 초기화 해준다. 

 

 

이렇게 전체 코드 끝 ! 

 

 

 

스스로 만들어본 Line 삭제 코드

 

 

bool LineCheck(int[] line)
{
    for (int j = 0; j < line.Length; j++)
    {
        if (line[j] == 0)
        {
            return false;
        }
    }
    return true;
}

private void LineDelete(int[] line)
{
    for (int j = 0; j < line.Length; j++)
    {
        line[j] = 0;
    }
}

private void BlockDelete(int line)
{
    foreach (var termino in FindObjectsOfType<TetrominoData>())
    {
        var blockToRomove = new List<Transform>();

        foreach (var blcok in termino.Blocks)
        {
            int y = Mathf.RoundToInt(blcok.transform.position.y + 8.5f);
            if (y == line)
            {
                blockToRomove.Add(blcok);
            }
        }

        foreach (var blcok in blockToRomove)
        {
            termino.Blocks.Remove(blcok);
            Destroy(blcok.gameObject);
        }
    }
}

void CheckandClear()
{
    for (int y = 0; y < grid.Length; y++)
    {
        if (LineCheck(grid[y]))
        {
            LineDelete(grid[y]);
            BlockDelete(y);
            
        }
    }
}

private void LateUpdate()
{
    CheckandClear();
}

 

 

using System;
using System.Collections;
using System.Collections.Generic;
using AYellowpaper.SerializedCollections;
using Unity.VisualScripting;
using UnityEditor;
using UnityEngine;
using Random = UnityEngine.Random;

public enum TetrominoType : byte
{
    None,
    I,
    O,
    Z,
    S,
    J,
    L,
    T,
    Max
}

public class TetrisManager : Singleton<TetrisManager>
{
    const float X_OFFSET = 4.5f;
    const float Y_OFFSET = 8.5f;
    
    [SerializeField] private Transform spawnPoint;
    [SerializeField] private SerializedDictionary<TetrominoType, string> tetrominoDatas;
    [SerializeField] private float dropTime = 1.0f;

    private TetrominoData _currentTetrominoData;
    private float currentDropTime = 0.0f;

    private int[][] grid = null;

    public override void OnAwake()
    {
        base.OnAwake();

        grid = new int[25][];

        for (int i = 0; i < grid.Length; i++)
        {
            grid[i] = new int[10];
        }
    }

    void Start()
    {
        currentDropTime = dropTime;
        SpawnTetromino();
    }

    void Update()
    {
        if (_currentTetrominoData.IsUnityNull())
            return;

        if (Input.GetKeyDown(KeyCode.A))
        {
            SetGridState(0);
            _currentTetrominoData.transform.position += Vector3.left;
            
            if (checkBlockCollision() || GridOverlapCheck())
            {
                _currentTetrominoData.transform.position -= Vector3.left;
            }
            SetGridState(1);
        }

        if (Input.GetKeyDown(KeyCode.D))
        {
            SetGridState(0);
            _currentTetrominoData.transform.position += Vector3.right;
            if (checkBlockCollision() || GridOverlapCheck())
            {
                _currentTetrominoData.transform.position -= Vector3.right;
            }
            SetGridState(1);
        }

        if (Input.GetKeyDown(KeyCode.W))
        {
            SetGridState(0);
            _currentTetrominoData.transform.Rotate(new Vector3(0, 0, -90));
            
            var (minX, minY, maxX, maxY) = GetGridState();
            
            if (checkBlockCollision()|| GridOverlapCheck() || 0 > minY)
            {
                _currentTetrominoData.transform.Rotate(new Vector3(0, 0, 90));
            }
            SetGridState(1);
        }

        currentDropTime -= Time.deltaTime;
        if (currentDropTime <= 0.0f)
        {
            SetGridState(0);
            
            _currentTetrominoData.transform.position += Vector3.down;
            
            if (GridOverlapCheck())
            {
                _currentTetrominoData.transform.position -= Vector3.down;
                SetGridState(1);
                SpawnTetromino();
            }
            else
            {
                SetGridState(1);

                var (minX, minY, maxX, maxY) = GetGridState();
                
                if (minY == -1)
                {
                    SetGridState(0);
                    _currentTetrominoData.transform.position -= Vector3.down;                    
                    SetGridState(1);
                    SpawnTetromino();
                }
            }
            
            currentDropTime = dropTime;
        }
        CheckandClear();

    }

    private (int, int, int, int) GetGridState()
    {
        int minX = Int32.MaxValue;
        int minY = Int32.MaxValue;
        int maxX = Int32.MinValue;
        int maxY = Int32.MinValue;

        foreach (var block in _currentTetrominoData.Blocks)
        {
            int y = Mathf.RoundToInt(block.transform.position.y + Y_OFFSET);
            int x = Mathf.RoundToInt(block.transform.position.x + X_OFFSET);
            minX = Mathf.Min(minX, x);
            minY = Mathf.Min(minY, y);
            maxX = Mathf.Max(maxX, x);
            maxY = Mathf.Max(maxY, y);
        }

        return (minX, minY, maxX, maxY);
    }
    
    private bool GridOverlapCheck()
    {
        foreach (var block in _currentTetrominoData.Blocks)
        {
            int y = Mathf.RoundToInt(block.transform.position.y + Y_OFFSET);
            int x = Mathf.RoundToInt(block.transform.position.x + X_OFFSET);

            if (y >= 0 && x >= 0 && x < grid[y].Length && grid[y][x] == 1)
            {
                Debug.Log($"Hit{x}, {y}");
                return true;
            }
        }

        return false;
    }
    
    private void SetGridState(int state)
    {
        foreach (var block in _currentTetrominoData.Blocks)
        {
            int y = Mathf.RoundToInt(block.transform.position.y + Y_OFFSET);
            int x = Mathf.RoundToInt(block.transform.position.x + X_OFFSET);

            if (y >= 0 && x >= 0 && x < grid[y].Length) 
                grid[y][x] = state;
        }
    }
    

    private void SpawnTetromino()
    {
        GameObject Tetromino_Prefab = null;
        TetrominoType nextBlockIndex = (TetrominoType)Random.Range(0, (int)TetrominoType.Max - 1) + 1;
        Tetromino_Prefab = Resources.Load<GameObject>($"Prefab/{tetrominoDatas[nextBlockIndex]}");
        Debug.Assert(Tetromino_Prefab);

        GameObject spawndTetromino = Instantiate(Tetromino_Prefab, spawnPoint.position, Quaternion.identity);
        spawndTetromino.TryGetComponent(out _currentTetrominoData);
    }


    bool checkBlockCollision()
    {
        foreach (var block in _currentTetrominoData.Blocks)
        {
            int y = (int)(block.transform.position.y + Y_OFFSET);
            int x = (int)(block.transform.position.x + X_OFFSET);
            Debug.Log(x);
            if (x < 0 || x >= grid[0].Length)
            {
                return true;
            }
        }

        return false;
    }
    bool LineCheck(int[] line)
    {
        for (int j = 0; j < line.Length; j++)
        {
            if (line[j] == 0)
            {
                return false;
            }
        }
        return true;
    }

    private void LineDelete(int[] line)
    {
        for (int j = 0; j < line.Length; j++)
        {
            line[j] = 0;
        }
    }

    private void BlockDelete(int line)
    {
        foreach (var termino in FindObjectsOfType<TetrominoData>())
        {
            var blockToRomove = new List<Transform>();

            foreach (var blcok in termino.Blocks)
            {
                int y = Mathf.RoundToInt(blcok.transform.position.y + 8.5f);
                if (y == line)
                {
                    blockToRomove.Add(blcok);
                }
            }

            foreach (var blcok in blockToRomove)
            {
                termino.Blocks.Remove(blcok);
                Destroy(blcok.gameObject);
            }
        }
    }

    void CheckandClear()
    {
        for (int y = 0; y < grid.Length; y++)
        {
            if (LineCheck(grid[y]))
            {
                LineDelete(grid[y]);
                BlockDelete(y);
        
            }
        }
    }

    private void LateUpdate()
    {
    }

    void OnDrawGizmos()
    {
        if (grid == null) return; // grid가 null일 경우 아무것도 그리지 않음

        for (int x = 0; x < grid.Length; x++)
        {
            for (int y = 0; y < grid[x].Length; y++)
            {
                // grid 상태에 따라 색상 설정
                Gizmos.color = (grid[x][y] == 0) ? Color.yellow : Color.green;

                // 그리드 좌표 계산
                Vector3 gridPosition = new Vector3(y - 4.5f, x - 8.5f, 0); // x와 y의 좌표 체계 주의
                Gizmos.DrawCube(gridPosition, Vector3.one * 0.3f); // 큐브 크기를 0.3으로 설정
            }
        }
    }

}

 

 

이정도...내일은 일어나서 스스로 테트리스를 구현해보고, (노베이스) 

기능들을 추가할 수 있으면 추가해보고 ( 줄내리기)

시간이 남는다면 전에 만들었던 프로젝트를 좀 바꾸기 !