TIL

[멋쟁이사자처럼 부트캠프TIL회고] 42일차 : 테트리스 기능 추가

Cadi 2025. 1. 1. 23:17

 

 

이 중, 1, 2,3,6은 도전하다보면 되겠는데.. ? 라는 생각이 들어 구현을 시작했다.

그러나 도중 4줄 삭제 아이템은 굳이.. ? 3번을 고쳐쓰면 되겠는데라는 생각이 들어 일단 1,2,3 번만 구현해 보기로 했다.

 

 

2번 3번은 금방 끝났다. 

중간 라인이 끝나도 삭제되게 하기

지금은 minY가 -1이 되었을 때, 즉 마지막 칸에 닿았을 때만 부모를 분리하고 gridBlock 배열에 넣어준다.

그리고 삭제하는 로직도 minY가 -1이 되었을 때만 작동한다. 이를 바꿔주면 된다.

 

else
{
    SetGridState(1);

    var (minX, minY, maxX, maxY) = GetGridState();
    
    if (minY == -1)
    {
        SetGridState(0);
        _currentTetrominoData.transform.position -= Vector3.down;                    
        SetGridState(1);
        SeparateBlock();
        //int yIndex = 0; 이 친구는 0만 검사하니까 이걸 전체적으로
        //근데 이게 아래로 내려갔을때만 검사하면 안되니까 minY == -1 조건을 벗어나야함.
        LineCheckDeleteRearrange();
        
        SpawnTetromino();
    }
}

 

 

SeprateBlock()은 다음과 같다. 

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

 

LineCheckDeleteRearrange()는 라인을 체크하고, 삭제하고, 재정렬하는 기능을 가진 함수이다. 

전의 기능을 그대로 쓰면 중간 줄이 삭제 되었을 때도 전체 줄을 내리기 때문에 수정이 필요하다. 

일단 Overlap되어서 멈췄을 때도 SeprateBlock()을 해 주었다. 

 

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

이러면 중간 줄을 삭제하기 위한 조건이 완료되었다.

 

이제 삭제하고 나서 그 윗줄부터 한 칸씩 내리기 위해 작업을 해 주었다. 

private void LineCheckDeleteRearrange()
{
    for (int yIndex = 0; yIndex < grid.Length; ++yIndex)
    {
        int count = grid[yIndex].Count(e => e == 1);

        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 = yIndex; 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;
            }
            yIndex--;

        }


    }

 

yIndex는 몇 번째 줄이 꽉 차 있는지 검사하기 위한 것이다. 

grid[yIndex].count가 10이면 (꽉 차 있으면)

그 줄에 들어가 모든 grid의[yIndex][i]를 0으로 만들고, 들어있는 게임 오브젝트를 부시고,

안에 들어있는 데이터도 없에버린다. (블럭을 표시하기 위해 있던 gridBlock데이터)

 

그리고 yIndex줄부터 맨 윗줄까지 한 칸씩 땡겨온다. 

이 때에도 gridBlock을 옮기고, gridBlock 데이터도 따로 옮겨와야 한다. 

왜냐하면 gridBlcok은 실제로 존재하는 게임오브젝트이고, gridBlcok[i]는 참조하는 것이기 때문이다.

 

추가 ++ 

: girdBlock  : 유니티의 게임 오브젝트(Block)를 참조하는 2D 배열

예 ) y = 5 , x = 3 위치의 블럭을 girdBlock[5][3] dptj ckawh

: gridBlock 데이터 : 실제 게임 오브젝트의 데이터를 나타냄

예) gridBlock[5][3].transform.position 은 거기 있는 블럭의 위치

그래서 둘이 순서를 바꾸면 올바르게 동작하지 않는다. 

 

맨 윗줄은 그대로 남아 있으므로 없에준다.

 

여기서 주의할 점이, 우리는 한 칸씩 모든 데이터와 블럭을 내렸다. 그래서 바로 yIndex+1로 넘어가 검사하게 된다면

내려진 첫 줄은 검사하지 못하게 된다. 그래서 지금 줄, yIndex번 째 줄을 다시 검사하기 위해 yIndex--를 해 준다.

*스코프 밖으로 나가면 무한루프에 빠지지 않게 주의 . 

 

 

 

 

한 칸씩 내리기 (아래 방향키)

 

생각나는 방법은 두 가지였다.

1. 시간을 0으로 만들기 : 테트리스 게임처럼 두 칸이 한 번에 움직이는 것 같은 동작은 설명 불가.

2. transform을 옮겨줌 :  자연스러운 동작

 

2번으로 실행했다. 

 

if (Input.GetKeyDown(KeyCode.S))
{
    SetGridState(0);
    _currentTetrominoData.transform.position += Vector3.down;
    if (checkBlockCollision() || GridOverlapCheck())
    {
        _currentTetrominoData.transform.position += Vector3.up;
    }
    SetGridState(1);
}

 

똑같이 검사를 해 준다. checkBlockCollision()은 좌우 검사이기에 필요하지 않을 수도 있으나

혹시 A,D,W와 같이 누르는 상황을 위해 남겨두었다. 

 

 

맨 밑으로 내리기 (스페이스)

 

1.  시간을 계속해서 0으로 만들기 : 해 보니 시간이 0보다 작아지면  dropTime으로 초기화 하는 로직이 있어 작동하지 않음

2. minY를 구해서 그만큼 transform.position을 Vector3.down 시키기 

if (Input.GetKeyDown(KeyCode.Space))
{
    var (minX, minY, maxX, maxY) = GetGridState();
    if (minY >= 1)
    {
        SetGridState(0);
        for (int i = 0; i < minY; i++)
        {
            _currentTetrominoData.transform.position += Vector3.down;
        }
        SetGridState(1);
    }
}

 

생각이 짧았던 것이, 맨 밑으로 내린다는 것은 이런 방식이 아니라 다른 블럭과 겹치거나 minY가 == -1이 될 때까지 내려주어야 했다.

 

살짝 수정해서 바꿔준다면 

if (Input.GetKeyDown(KeyCode.Space))
{
    var (minX, minY, maxX, maxY) = GetGridState();
    if (minY >= 0)
    {
        SetGridState(0);
        for (int i = 0; i < minY; i++)
        {
            if (!GridOverlapCheck())
            _currentTetrominoData.transform.position += Vector3.down;
            else
            {
                _currentTetrominoData.transform.position += Vector3.up;

            }
        }
        SetGridState(1);
        
    }
}

 

다음과 같이 바꿔줄 수 있다.

다만 다음 코드는 문제가 몇 가지 있었다.

0번째 줄에 무언가가 있는 채로 내리면 겹쳐지는 문제와 , 바닥까지 내렸을 때 0번째 칸에 들어가기 때문에 그 순간에는 위치를 바꿀 수 있다. (즉 스페이스 바를 누른다고 바닥에 바로 안착하지 않고 조작할 시간이 있다)

 

 

 

 

그렇다고 , 조건을 바꾸자니 minY가 -1보다 작아지는 상태가 발생해 계속해서 오류가 발생했다.

이를 다음과 같이 바꿔주어 해결했다.

if (Input.GetKeyDown(KeyCode.Space))
{
    var (minX, minY, maxX, maxY) = GetGridState();
    if (minY >= 0)
    {
        SetGridState(0);
        for (int i = 0; i < minY; i++)
        {
            _currentTetrominoData.transform.position += Vector3.down;
            if (GridOverlapCheck())
            {
                _currentTetrominoData.transform.position += Vector3.up;
                break;
            }
        }
        SetGridState(1);
        SeparateBlock();
        LineCheckDeleteRearrange();
        SpawnTetromino();
    }
    currentDropTime = dropTime;

}

 

 

그냥 계속해서 내리고 다른 물체에 닿거나 minY만큼 내렸을 때 (Block이 0번째 칸에 갔을 때)

블럭을 분해해주고, 라인체크를해서 없에고 재배열을 해주고 새로운 블럭을 생성해주고 시간까지 초기화를 해준다.

 

정상 작동한다. 

 

 

사실 아직도 왜 아까 코드에서 맨 밑줄이 겹쳐지는 문제가 왜 해결되었는지는 모르겠다. 

 

++ 다시 구현해보니 , 맨 밑까지 내려갈 만큼만 반복문을 돌린다, 그러니까 다시 체크하고 올리는 과정이 생략되었고 그대로 데이터가 겹치게 되어 발생하는 문제였다.