Recent Posts
Recent Comments
03-28 17:23
관리 메뉴

동글동글 라이프

Xna Tetris 본문

개발자 이야기/Programming

Xna Tetris

동글동글라이프 2009. 10. 14. 01:19





XNA 번역서를 살펴보면 Tetris 예제가 있다.

워낙 유명한 게임이라 빼놓지 않고 구현해 놓았다.


사실 이 테트리스까지만 다루어도,

2D 게임의 전반적인 부분은 다 이해할 수 있을 정도인데,

간단한 스크립트를 짜서 전체 라인수를 출력해보니

5000줄 가까이 되는것을 알 수 있었다.


말이 5000줄이지만 주석이 3분의 1정도는 되는 듯...




공개되어 있는 소스를 그대로 설명할 필요는 없을 것이고,

기존의 소스를 바꾸어 테트리스 도형을 더 추가시켜보겠다.



TetrisGrid.cs 파일을 살펴보면,


블럭의 타입을 결정하는 상수들을 먼저 설정해 주어야 하는데,

public enum BlockTypes
{
            Empty,
            Block,
            Triangle,
            Line,
            RightT,
            LeftT,
            RightShape,
            LeftShape,
           UserShape1,
          UserShape2,
          UserShape3,

} // enum BlockTypes

기존의 상수에서 UserShape 3개를 더 추가를 시켰다.

이것은 내가 설정할 도형 3개를 추가하겠다는 뜻인데,

이렇게 설정한 값에 따라,

컬러도 지정해 줘야한다.


public static readonly Color[] BlockColor = new Color[]
{
    new Color( 60, 60, 60, 128 ), // Empty, color unused
    new Color( 50, 50, 255, 255 ), // Line, blue
    new Color( 160, 160, 160, 255 ), // Block, gray
    new Color( 255, 50, 50, 255 ), // RightT, red
    new Color( 255, 255, 50, 255 ), // LeftT, yellow
    new Color( 50, 255, 255, 255 ), // RightShape, teal
    new Color( 255, 50, 255, 255 ), // LeftShape, purple
    new Color( 50, 255, 50, 255 ), // Triangle, green
    new Color( 255, 255, 255, 255 ), // 첫번째 도형 , White
   new Color( 251, 70, 88, 255 ), // 두번째 도형 , 핑크
   new Color( 10, 220, 10, 255 ), // 세번째 도형 , 초록

}; // Color[] BlockColor


색도 지정을 했으니 이제 도형의 기본타입을 설정해야 된다.



public static readonly int[][,] BlockTypeShapesNormal = new int[][,]
{
    // Empty
    new int[,] { { 0 } },
    // Line
    new int[,] { { 0, 1, 0 }, { 0, 1, 0 }, { 0, 1, 0 }, { 0, 1, 0 } },
    // Block
    new int[,] { { 1, 1 }, { 1, 1 } },
    // RightT
    new int[,] { { 1, 1 }, { 1, 0 }, { 1, 0 } },
    // LeftT
    new int[,] { { 1, 1 }, { 0, 1 }, { 0, 1 } },
    // RightShape
    new int[,] { { 0, 1, 1 }, { 1, 1, 0 } },
    // LeftShape
    new int[,] { { 1, 1, 0 }, { 0, 1, 1 } },
    // LeftShape
    new int[,] { { 0, 1, 0 }, { 1, 1, 1 }, { 0, 0, 0 } },

    // 첫번째 도형   ㄷ 도형
    new int[,] { { 1, 0, 1 }, { 1, 0, 1 }, { 1, 1, 1 }},
    // 두번째 도형   두툼한 凸 도형 밑에 한칸이 더 있는..
    new int[,] { { 0, 1, 0 }, { 1, 1, 1 }, { 1, 1, 1 }},
    // 세번째 7칸짜리 네모 도형
    new int[,] { { 0, 1, 1 }, { 1, 1, 1 }, { 1, 1, 0 }}, 
 
              

}; // BlockTypeShapesNormal

위의 코드는 아래 그림과 같은 3개의 도형을 생성시킨다.


     
     



배열의 코드만 조금 수정하면, 다른 블럭들을 더 생성할 수 있다.

여기까지는 쉽지만 이 다음 코드부터는

소스에 대한 완벽한 이해가 필요하다.


저 블럭들을 움직이거나(move) 회전시키는(rotate) 코드는 각각의 함수로 이루어져 있다.

일단 MoveBlock 이라는 함수를 살펴보고 분석하여 주석을 달아보았다.

for (int x = 0; x < GridWidth; x++)
        for (int y = 0; y < GridHeight; y++)
                if (floatingGrid[x, y])
                      grid[x, y] = BlockTypes.Empty;

// 이전에 있었던 값들을 모두 지운다.
// GridWidth 와 GridHeight 는 상수이며,
// 가로와 세로 길이를 뜻한다. 이 값을 바꾸면
// 게임 전체의 가로와 세로 길이도 변하게 된다.


            
// Move stuff to new position
bool anythingBlocking = false;
Point[] newPos = new Point[9];
int newPosNum = 0;
if (moveType == MoveTypes.Left)
{

                for (int x = 0; x < GridWidth; x++)
                    for (int y = 0; y < GridHeight; y++)

                        // 이 소스는 블럭의 값을 검사해서
                        // x 를 -1 즉 왼쪽으로 이동했을 시,
                        // BlockTypes의 값이 비어있을 때 이동시킨다.

                        if (floatingGrid[x, y])
                        {
                            if (x - 1 < 0 ||
                                grid[x - 1, y] != BlockTypes.Empty)
                                anythingBlocking = true;
                            // x -1 < 0 보다 작다는 것은
                            // 더이상 왼쪽으로 갈 수 없는것을 의미한다.
                            // 그리고 grid[x - 1, y] != BlockTypes.Empty
                            // 이 코드는 grid 배열에 테트리스 블럭의 빈공간이
                            // 들어가지 않는것을 의미한다.

                            // 그래서 anythingBlocking 이 true가 된다.                             
                            else if (newPosNum < 9)
                            {
                                // newPosNum 은 블럭이 들어갈 수 있는것을 체크하며,
                                // 4보다 작을 때까지 체크하여,4일때까지만
                                // 새로운 포인터를 할당한다.

                                newPos[newPosNum] = new Point(x - 1, y);
                                newPosNum++;
                            } // else if                  
                        } // for for if
            } // if (left)


newPosNum 이라는 변수가 큰 역활을 하는데,

기존의 소스에는 이 값이 4가 넘지 않을때까지만 Check 되어 있다.

그리고

Point[] newPos = new Point[9];

이 부분의 소스도 Point[4] 로 고정되어 있어,

블럭이 4개까지만 바뀌도록 설정이 되어있다.

그러므로 저 값은 상수값을 줘서 처리하는게 좋지만, 그냥 9개 정도만 설정해 놓았다.


이 코드는 왼쪽으로 이동했을 때의 코드이며,

오른쪽으로 이동하는 소스와, 아래로 이동하는 소스 모두 동일하게 처리해주면 된다.



그리고 마지막으로 바뀐 값의 처리를 확실하게 해줘야 하는데,


            if (anythingBlocking ||
                // Or we didn't get all 4 new positions?
                newPosNum < 4 // 원래는 newPosNum != 4 로써 4개인지만 검사한다.
            {
                for (int x = 0; x < GridWidth; x++)
                    for (int y = 0; y < GridHeight; y++)
                        if (floatingGrid[x, y])
                            grid[x, y] = (BlockTypes)currentBlockType;
                return false;
            } // if
            else
            {
                if (moveType == MoveTypes.Left)
                    currentBlockPos = new Point(currentBlockPos.X - 1, currentBlockPos.Y);
                else if (moveType == MoveTypes.Right)
                    currentBlockPos = new Point(currentBlockPos.X + 1, currentBlockPos.Y);
                else if (moveType == MoveTypes.Down)
                    currentBlockPos = new Point(currentBlockPos.X, currentBlockPos.Y + 1);

                // Else we can move to the new position, lets do it!
                for (int x = 0; x < GridWidth; x++)
                    for (int y = 0; y < GridHeight; y++)
                        floatingGrid[x, y] = false;

 

                /*  여기가 핵심코드   */

                // newPosNum
                for (int i = 0; i < newPosNum ; i++)
                {
                        grid[newPos[i].X, newPos[i].Y] = (BlockTypes)currentBlockType;
                        floatingGrid[newPos[i].X, newPos[i].Y] = true;
                } // for

               
                Sound.Play(Sound.Sounds.BlockMove);

핵심코드라고 되어있는 부분은

새로운 포지션을 얻은 블럭에 크기만큼

새로운 블럭을 그려주고 있다.


이로써 4개로 이루어진 블럭 뿐만이 아니라,

9개 이하의 블럭은 모두 처리가 가능한 것이다.





이로써 블럭 추가는 끝.




Tip으로 Draw 함수에서 GameOver 문구를 출력하는 코드.





if (gameOver)
{
                TextureFont.WriteText(380, 400, "<< GAME OVER >>");
                return;
}

Draw 함수에서 설정해야 한다.

자칫 잘못해서 Update 함수에서 설정하면,

Game Over 문구가 한번 반짝 거리고 사라져 버리기 때문이다.





2시간동안 작업한 것 치고는 꾀 훌륭한 아웃풋이다.

슬슬 C# 코드도 눈에 익어가지만...

아직도 완벽하게 사용하기는 멀고도 멀은 듯하구나..



Comments