회전 로직을 너무 쉽게 생각했었네요.. 생각보다 어려웠습니다. 모든 타일을 왼쪽 상단을 기준으로 배열을 잡으려고 했는데, 막상 그렇게 만들어 놓으니 기존에 보던 테트리스 게임하고 괴리감이 커서 다시 수정을 하고, 그렇게 하니 또 회전 로직이 걸리고...ㅠ  회전 로직이 맘에 좀 안들지만... (다른 분들은 어찌했나 궁금하네요..) 이전 포스팅대로 대부분의 로직은 Block에서 거의 진행을 하고, GameManager는 전체적인 진행과 키입력에 따른 요청을 하고있습니다.

 Block의 소스가 중요로직이라고 생각되므로 Block과 키입력 예시부분만 포스팅을 하도록하죱. 


1. Block 

 - 블럭의 이동과 회전을 관리.

public class Block {
    public int[,,] tile;
    public Color32 color;
    
    int direct;
    int posX;
    int posY;

    public Block(int[,,] _tile, Color32 _color)
    {
        tile = _tile;
        color = _color;
    }
    public void SetPostion(int _x,int _y,ref int[,] _map)
    {
        int posX = _x;
        int posY = _y;

        MoveTile(direct, _x, _y, ref _map);
    }

    // 좌우로 움직임을 명령하는 함수. 외부에서 호출
    public void MoveHorizon(int _x, ref int[,] _map)
    {
        if (CheckTile(_x, _map))
        {
            MoveTile(direct,_x, 0,ref _map);
        }
    }

    // _x,_y 좌표로 이동하는 함수 ( 블럭을 이동시킬 때 사용)
    void MoveTile(int _direct, int _x, int _y, ref int[,] _map)
    {
        for(int i = 0; i < 4; i++)
        {
            for(int j = 0; j < 4; j++)
            {
                if(tile[direct, i, j] == 1)
                {

                    _map[posY + i, posX + j] = 0;
                }
            }
        }
        direct = _direct;
        posX += _x;
        posY += _y;

        for (int i = 0; i < 4; i++)
        {
            for (int j = 0; j < 4; j++)
            {
                if (tile[direct, i, j] == 1)
                {

                    _map[posY + i, posX + j] = tile[direct, i, j];
                }
            }
        }

    }

    // 좌우 좌표로 이동가능한지 확인하는 함수
    bool CheckTile(int _x, int[,] _map)
    {
        int tempX = posX + _x;
        for (int i = 0; i < 4; i++)
        {
            for (int j = 0; j < 4; j++)
            {
                if (tile[direct, i, j] == 1 && !(_map[posY + i, tempX + j]==0 || _map[posY + i, tempX + j] == 1))
                {
                    return false;
                }
            }
        }
        return true;
    }


    // 블럭을 회전시키고 적용하는 함수. 회전시 맵을 삐져나갈 경우 때문에 새로 확인.
    public void RotationTile(ref int[,] _map)
    {
        // 회전 - 4가 되면 0으로 변경.

        int tempDirect = (direct +1) & 3;
        int count = CheckRotation(tempDirect,0, _map);
        if (count == -10)
        {
            return;
        }
        else if (count != 0)
        {
            // 옆 칸으로 이동해서 재확인.
            if (CheckRotation(tempDirect, count, _map)==0)
            {
                MoveTile(tempDirect, count, 0, ref _map);
            }
        }else
        {
            MoveTile(tempDirect, 0, 0, ref _map);
        }
    }

    // 블럭이 회전 가능한지 확인하는 함수. 회전했을때 가로로 겹쳐지는 길이를 반환한다.
    // 겹치는게 없다면 rotation을 하고, 겹쳐진다면 겹쳐진 길이만큼 이동시켜 재확인.
    // 1,1을 기준으로 왼쪽이 겹치면 +, 오른쪽은 -
    int CheckRotation(int _direct,int _x,int[,] _map)
    {
        int maxLeft = 0;
        int maxRight = 0;
        for (int i = 0; i < 4; i++)
        {
            int left = 0;
            int right = 0;
            for (int j = 0; j < 2; j++)
            {
                if (tile[_direct, i, j] == 1)
                {
                    int tempX = posX + _x + j;
                    if(tempX < 0)
                    {
                        left++;
                        continue;
                    }
                    if (!(_map[posY + i, tempX] == 0 || _map[posY + i, tempX] == 1))
                    {
                        left++;
                    }
                }
            }
            for (int j =3; j >=2; j--)
            {
                if (tile[_direct, i, j] == 1)
                {
                    int tempX = posX + _x + j;
                    if (tempX >= 12)
                    {
                        right++;
                        continue;
                    }
                    if (!(_map[posY + i, tempX] == 0 || _map[posY + i, tempX] == 1))
                    {
                        right ++;
                    }
                }
            }

            if (left > maxLeft)
            {
                maxLeft = left;
            }

            if(right> maxRight)
            {
                maxRight = right;
            }
        }
        if(maxRight == 0)
        {
            return maxLeft;
        }else if (maxLeft ==0)
        {
            return maxRight * -1;
        }else
        {
            return -10;
        }
    }
    // 블럭의 밑이 고정타일 또는 바닥인지 확인 후 바닥이 아니면 MoveTile을 이용해 내린다.
    // 바닥이면 applyGround 후 false를 리턴. - GM에서는 라인검색을 할 수 있도록.
    public bool MoveDown( int[,] _map)
    {
        for (int i = 0; i < 4; i++)
        {
            for (int j = 0; j < 4; j++)
            {
                if (tile[direct, i, j] == 1 && _map[posY + i+1, posX + j] == -2)
                {
                    ApplyGround(ref _map);
                    return false;
                }
            }
        }
        MoveTile(direct,0, 1, ref _map);
        return true;
    }

    // 바닥에 닿은 블럭을 고정타일로 맵에 반영하는 함수.
    public void ApplyGround(ref int[,] _map)
    {
        for (int i = 0; i < 4; i++)
        {
            for (int j = 0; j < 4; j++)
            {
                if (tile[direct, i, j] == 1 )
                {
                    _map[posY + i, posX + j] = -2;
                }
            }
        }
    }

    // 블럭을 바닥으로 한 번에 이동시키는 함수.
    // 블럭의 밑을 바닥까지 검사하여 가장 적은 거리만큼 이동 시키고 ApplyGround를 한다.
    public void DropTile(ref int[,] _map)
    {
        int min=30;
        for (int i = 0; i < 4; i++)
        {
            for (int j = 0; j < 4; j++)
            {
                if (tile[direct, i, j] == 1)
                {
                    int count = 0;
                    while (_map[posY + i + count +1, posX + j] != -2)
                    {

                        count++;
                    }
                    if (count < min)
                    {
                        min = count;
                    }
                }
            }
        }
        MoveTile(direct,0, min, ref _map);

    }
}



2. 키 입력에 따른 Block함수 사용 예시 ( GameManager의 일부)


        if (!isGameOver)
        {
            // 버튼 입력에 따른 테트리스 로직 실행.
            if (Input.GetKeyDown(KeyCode.RightArrow))
            {
                playerBlock.MoveHorizon(1, ref map);
            }
            if (Input.GetKeyDown(KeyCode.LeftArrow))
            {

                playerBlock.MoveHorizon(-1, ref map);
            }
            if (Input.GetKeyDown(KeyCode.UpArrow))
            {
                playerBlock.RotationTile(ref map);
            }
            if (Input.GetKeyDown(KeyCode.DownArrow))
            {
                if (playerBlock.MoveDown(map) == false)
                {
                    // 라인을 탐색하여, 가득차면 제거
                    // 천장을 넘어가면 게임 종료.
                    // 새로운 블럭생성.
                }
            }
            if (Input.GetKeyDown(KeyCode.Space))
            {
                playerBlock.DropTile(ref map);
                playerBlock.ApplyGround(ref map);
                // 라인을 탐색하여, 가득차면 제거
                // 천장을 넘어가면 게임 종료.
                // 새로운 블럭생성.
            }
        }




* 아래 동영상은 완성본의 일부 영상입니다.





* 지적은 언제나 환영입니다.

Posted by 검은거북

맵 타일과 유저 입력에 따른 전체적인 게임 플로우를 관리하는 GameManager 클래스,

블럭의 이동 및 회전, 동작을 관리하는 Block 클래스로 전체 로직을 관리하겠습니다.


전체적인 플로우는 Block 객체가 고유의 블럭 정보 ( 위치, 회전, 색 등)를 가지고, GameManager가 유저의 입력가 게임 진행에 따라 Block에게 동작을 요청하는 식으로 진행될 것입니다.


1. GameManager

public class GameManager : MonoBehaviour {
    // 사전에 블럭의 배열과 색을 정의. ( 생략)
    Block[] blocks = new Block[7];
    const int HEIGHT =25;
    const int WIDHT = 12;

    Block playerBlock;
    int[,] map = new int[25, 12];

    // Use this for initialization
    void Start () {
        // 초기화
    }
	
    // Update is called once per frame
    void Update () {
	// 버튼 입력에 따른 테트리스 로직 실행.
    }

    // 타일을 매 일정시간마다 내리는 함수.
    IEnumerator Tick()
    {
        yield return null;
    }
    // 맵 생성 함수
    void MakeMap(ref int[,] _map)
    {

    }

    // 4,4 위치에 블럭을 새로 생성
    Block MakeBlock(ref int[,] _map)
    {
        return null;
    }

    // 고정된 블럭이 맵 타일의 최상위를 넘었는지 확인하는 함수. 
    bool CheckEnd(int[,] _map)
    {
        return false;
    }


    // 고정된 타일의 좌우를 비교하여 가득 찬 라인을 제거하도록 요청한다.
    void CheckLine(ref int[,] _map)
    {

    }

    // line을 삭제하고, 위쪽의 타일을 내린다.
    void DeleteLine(int _line)
    {

    }

    void GameOver()
    {

    }

}



2. Block

public class Block { public int[,,] tile; public Color32 color; int direct; int posX; int posY; public Block(int[,,] _tile, Color32 _color) { tile = _tile; color = _color; } public void SetPostion(int _x,int _y) { int posX = _x; int posY = _y; } // 좌우로 움직임을 명령하는 함수. 외부에서 호출 public void MoveHorizon(int _x, ref int[,] _map) { } // _x,_y 좌표로 이동하는 함수 ( 좌우 이동과 한 칸씩 내릴때 사용) void MoveTile(int _direct,int _x, int _y, ref int[,] _map) { } // 좌우 좌표로 이동가능한지 확인하는 함수 bool CheckTile(int _x, int[,] _map) { return true; } // 블럭을 회전시키고 적용하는 함수. 회전시 맵을 삐져나갈 경우 때문에 새로 확인. public void RotationTile(ref int[,] _map) { } // 블럭의 밑이 고정타일 또는 바닥인지 확인 후 바닥이 아니면 MoveTile을 이용해 내린다. // 바닥이면 applyGround 후 false를 리턴. - GM에서는 라인검색을 할 수 있도록. public bool MoveDown( int[,] _map) { return true; } // 바닥에 닿은 블럭을 고정타일로 맵에 반영하는 함수. void ApplyGround(Block _block, ref int[,] _map) { } // 블럭을 바닥으로 한 번에 이동시키는 함수. // 블럭의 밑을 바닥까지 검사하여 가장 적은 거리만큼 이동 시키고 ApplyGround를 한다. public void DropTile(Block _block, ref int[,] _map) { } }





* 지적은 언제나 환영입니다.


Posted by 검은거북

1.기획

 타일 - 20 X 10

  1. 블럭은 테트리스의 기본 블럭 7가지 ( ㅁ,ㄴ,ㄱ,ㅣ,ㅜ,z, 반대 z)
  2. 블럭은 랜덤하게 맨위의 중앙에서 생성된다.
  3. 매 일정 시간마다 블럭은 한 칸씩 내려온다.
  4. 화살표 좌,우 에 따라 블럭은 한 칸씩이동하고, 위는 블럭을 오른쪽으로 회전, 아래는 블럭을 빠르게 내린다.  spacebar는 바로 맨 아래로 블럭을 내린다.
  5. 하나의 줄이 가득차면 제거되고, 그 위의 칸이 모양 그대로 내려온다.
  6. 바닥에 고정된 블럭이 맨 위에 닿으면 종료된다.


2. 플로우

  1.  타일 생성
    1.  테트리스는 블럭이 좌우로 계속 움직이므로 움직일 수 있는 칸을 한정할 수 있도록 기존 타일에 +2 씩 하여 막을 공간을 만든다. ( 22 X 12 )
      1. 막는 타일은 -1로 값을 배정한다. ( 바닥 타일은 -2로 고정타일 취급)
      2. 위쪽 공간은 타일이 가득찼을 경우를 대비해 최대 4만큼 여유공간을 두고, 다른 값 -3로 배정하여, 고정된 타일이 -3에 닿았는지를 보고 종료를 판단한다.
      3. 고로 최종 배열은 25 X 12
  2. 블럭 생성
    1.  블럭의 최대길이가 4 이므로 각 블럭은 4X4배열에 저장. - 구조체?
    2.  7개의 블럭을 4방향 회전했을때의 모양을 미리 7X4 배열에 각각 저장.
    3.  배열의 생성은 맵 타일의 4,4 위치에 생성.
  3. 유저 조작
    1.  좌 우 버튼
      1. 이동 할 위치의 맵 배열과 블럭배열을 비교하여 겹치지 않는다면, 이동한다.
        1. 배열을 좌우로 3씩 더 늘린다.
        2. 맵 배열을 비교할 때 -1(벽)과 만나면 다음 밑에 칸으로 넘어간다.
        3. 이동 - 맵 배열에서 현재 블럭 배열의 1(블럭값)의 위치를 비우고, 이동하여 값을 채운다.
    2.  회전 버튼 
      1.  블럭 배열에서 회전 인덱스를 +- 하여 회전한 배열을 위와 동일하게 비교하여 겹치지 않는다면 이동한다. 
      2.  회전시 블럭이 맵배열을 벗어난다면? - 맵 배열의 벽(-1)을 만났을때부터 count를 세고, 맵을 벗어난 블럭 길이만큼 count++을 하여, count만큼 반대로 이동시킨다. 
    3.  떨구기
      1. 블럭 배열의 인덱스가 존재하는 열을 맵배열에서 검색하여 고정 배열(-2)를 처음 만나는 count값을 구해 그만큼 내려주고, 고정블럭으로 변환한다.
  4. 블럭 이동 및 고정
    1. 블럭은 매 일정시간마다 내려온다.
      1. 블럭배열의 다음 이동 칸(아래)과 맵 배열을 비교하여 겹치지 않는다면 이동.
    2. 블럭 고정
      1. 만약 비교시 블럭배열과 맵배열의 -2(고정배열)이 겹친다면, 현재 위치에서 고정배열로 값을 변경하고, B로 돌아간다.
      2. 고정 후 맵 타일의 최상위를 검색해 -3이 아닌값이 있으면 종료로 판단.
    1. 라인제거
      1. 고정 배열로 변경시 고정배열들을 비교하여 행 전체가 -2(고정배열) 이면 해당 행을 제거한다. (0으로 변경)
      1. 라인이 제거되면 제거된 라인의 위 고정블럭들(-2)들은 제거된 count만큼 일정하게 내린다.
        1. 중간이 제거안될 경우는? - 라인 제거는 한 줄씩 요청하고, 가장 위 타일부터 제거를 요청한다. 하나의 함수에서 제거와 그 위의 타일을 내리는 작업까지 한다.
  5. 고정 블록이 최상위 공간을 벗어날때까지 B~D 반복





* 지적은 언제나 환영입니다.

Posted by 검은거북

블로그 이미지
프로그래밍 공부 요약 및 공부하며 발생한 궁금증과 해결과정을 포스팅합니다.
검은거북

공지사항

Yesterday
Today
Total

달력

 « |  » 2025.2
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28

최근에 올라온 글

최근에 달린 댓글

글 보관함