본문 바로가기

Development/Tetris

Python - 테트리스(Tetris) 만들기 (4) - Draw Rect

이전 글에서는, Tetris를 구동하기 위한 코드 초안을 작성했습니다.

 

import pygame as pg

 
class Tetris:
    def __init__(self, width: int, height: int):
        self._size = self.width, self.height = width, height
        self._display_surf = None
        self._running = False

    def _init(self):
        pg.init()
        self._display_surf = pg.display.set_mode(self._size)
        self._running = True
 
    def _event(self):
        for event in pg.event.get():
            if event.type == pg.QUIT:   ## Window 상단 X버튼
                self._running = False

    def _quit(self):
        pg.quit()
 
    def execute(self):
        self._init()

        while self._running:
            self._event()
        self._quit()
 
def main():
    tetris = Tetris(640, 400)
    tetris.execute()

if __name__ == "__main__" :
    main()

 

하지만 이 코드를 실행하면, 검은 화면만 뜨고 어떠한 기능도 하지 않습니다.

검은 화면...이런 것은 게임이 아니다.

 

이번 글에서는, 간단하게 물체를 만들어 보겠습니다. 테트리스에서 언급된 필드(Tetrion)는 크게 3가지였습니다.

* https://tyoon9781.tistory.com/entry/python-tetris-1-intro

  1. 10 x 20 크기의 Game Field
  2. 미리 보기
  3. Hold

가장 주요한 Field인 10 x 20의 Game Field를 먼저 만들어 보겠습니다.

그럼 200개의 사각형을 그려야 할 텐데, 어떻게 만들 수 있을까요? 공식 문서를 확인해 봅시다.

 

 


공식 문서를 읽어보자

구글에서 pygame draw라고 검색하니, 다음과 같은 페이지를 안내해 주었다.

 

사각형을 그리려면 pygame.draw.rect를 사용하면 된다고 합니다. 자세히 알아봅시다.

rect method에 대해서 자세히 나와있다.

 

가장 간단하게 사각형을 그리는 방법 : surface, color, rect를 입력하여 draw 할 수 있다고 합니다.

  1. surface Surface를 객체로 받는다고 합니다. 마침 첫 예제 코드에 self._display_surf가 있음을 알 수 있습니다.
  2. color는 Color 객체 혹은 int, 혹은 tuple 형태로 받는다고 합니다. 우리는 RGB tuple을 사용해 봅시다.
  3. rect는 Rect 객체인데... 정보가 없으니 Rect도 알아봅시다.

Rect에 대한 설명, 기하학 정보를 입력하면 된다.

 

생각보다 어렵지 않습니다. 그럼 한번 사각형을 그려봅시다. 저는 surf가 정의되는 시점 바로 뒤에 rect를 그려보겠습니다.

 

    def _init(self):
        pg.init()
        self._display_surf = pg.display.set_mode(self._size)
        pg.draw.rect(self._display_surf, (255, 255, 255), pg.Rect(10, 20, 30, 40))
        self._running = True

 

동작... 안 합니다. 왜 안 할까요? 동작하는 코드와 제 코드를 비교하면, 어떤 점 때문에 동작 안 하는지 쉽게 알 수 있습니다.

공식 문서가 제공하는 example code가 있다. 이 코드를 확인해보자.

 

밑에 보면 Example code가 있습니다. 확인해 봅시다.

 

공식 코드 예제가 아닌...github에서 검색을 제공해준다...?

 

이런... 공식 홈페이지 자체적으로 제공하는 코드가 없네요... 대신 github에서 키워드 검색 결과를 보여줍니다. 이 결과라도 활용해 봅시다.

검색 결과 페이지에서 아래와 같은 코드를 발견했습니다.

 

## 거의 마지막 줄
pygame.draw.rect(window,blue,(395,0,10,10)),
pygame.draw.rect(window,blue,(395,10,10,10)),
pygame.draw.rect(window,blue,(395,20,10,10)),
pygame.draw.rect(window,blue,(395,30,10,10))
pygame.display.flip()

* 출처 : https://github.com/elutecs/Retetris/blob/master/Shapes.py

 

draw.rect까지는 비슷한데, display.flip()을 하는 동작이 있습니다. 왠지 display.flip이 rect들을 실제 surface에 보여주는 동작 같습니다. 바로 적용해 봅시다.

 

    def _init(self):
        pg.init()
        self._display_surf = pg.display.set_mode(self._size)
        pg.draw.rect(self._display_surf, (255, 255, 255), pg.Rect(10, 20, 30, 40))
        pg.display.flip()
        self._running = True

 

성공했습니다!

 

이제 하얀색 직사각형을 확인해 볼 수 있습니다! 하지만 우리는 여기서 멈춰서는 안 됩니다.

py.display.flip()이 무슨 method인지 확인해 봐야 합니다.

 

flip에 대한 공식문서, 그리고 그 밑에는 update라는 문서가 같이 설명되어 있다.

 

display에 있는 flip과 update라는 설명이 있습니다. 내친김에 생략했던 pygame.display.set_mode()에 대해서도 살펴봅시다

 

display를 시작하는 method

 

위의 설명들을 요약하면 다음과 같습니다.

 

  1. pygame.display.set_mode()로 screen을 시작한다.
  2. pygame.display.flip()으로 display의 모든 contents를 update 한다.
  3. pygame.display.update()로 display의 부분을 업데이트한다.
    1. 특히 update(rectangle)을 통해, 업데이트하고자 하는 부분만 업데이트할 수 있다.

 

flip()은 전체를 업데이트한다고 하는데, 현재 시점에는 Field만 업데이트하면 되니, display.update()만 사용합니다.

아래의 코드는 update를 활용해 보는 코드입니다. 실제로 잘 동작하는 것을 확인할 수 있습니다.

    def _init(self):
        pg.init()
        self._display_surf = pg.display.set_mode(self._size)
        test_rect = pg.draw.rect(self._display_surf, (255, 255, 255), pg.Rect(10, 20, 30, 40))
        update_list = [test_rect]
        pg.display.update(update_list)
        self._running = True

 

이것을 활용해서, 이제 10 x 20 field를 만들어 보도록 하겠습니다.

resolution이 640 x 480이므로, tile을 10x10 pixel 크기 (총 크기 100 x 200) 기준으로 만들어 보겠습니다.

__init__에서 생긴 변수명과, _init에서 그려질 rect를 어떻게 반복문으로 활용했는지 살펴보시기 바랍니다.

 

 


적용해 보자

class Tetris:
    def __init__(self, width: int, height: int,
                 field_w:int=10, field_h:int=20, tile_size:int=10):
        self._size = self.width, self.height = width, height
        self._field_w, self._field_h, self._tile_size = field_w, field_h, tile_size
        self._display_surf = None
        self._running = False

    def _init(self):
        pg.init()
        self._display_surf = pg.display.set_mode(self._size)
        field_rect_list = []
        ## field 추가 
        for i in range(self._field_w):
            for j in range(self._field_h):
                _new_rect = pg.Rect(i*self._tile_size, j*self._tile_size, self._tile_size, self._tile_size)
                _draw_rect = pg.draw.rect(self._display_surf, (255, 255, 255), _new_rect, 1)
                field_rect_list.append(_draw_rect)
        pg.display.update(field_rect_list)
        self._running = True

 

다음과 같이 수정해 보았습니다. 결과는...?

음...커다란 흰색 사각형이...

 

이게 10 * 20인지 그냥 하얀색 사각형인지 구분하기 어렵습니다. 타일마다 테두리라도 넣으면 어떨까 싶은데 말이죠. Rect문서를 다시 살펴보겠습니다.

 

테투리를 넣는 방법에 대한 설명이 있다.

 

Width에 대한 설명을 읽어보니, 입력을 안 하면 색깔이 채워지고, 0보다 큰 숫자를 넣으면 그 숫자만큼의 두께를 테두리로 그려준다고 합니다. 저는 1이란 값을 입력하여 테두리를 그려보도록 하겠습니다.

 

    def _init(self):
        pg.init()
        self._display_surf = pg.display.set_mode(self._size)
        field_rect_list = []
        for i in range(self._field_w):
            for j in range(self._field_h):
                _new_rect = self._make_rect(i*self._tile_size, j*self._tile_size)
                _draw_rect = pg.draw.rect(self._display_surf, (255, 255, 255), _new_rect, 1)
                field_rect_list.append(_draw_rect)
        pg.display.update(field_rect_list)
        self._running = True

드디어 10 x 20 Field가 그려젔습니다.

 

드디어 pygame에 무언가를 그려본 순간입니다. 우리는 이 도형을 그리기 위해 공식문서를 적극 활용했습니다. 공식 문서를 최대한 활용하면, 구글링만으로 다른 사람의 코드를 의지해서 복붙 하는 것과는 이해도 면에서 차원이 다른 코드를 작성할 수 있습니다.

 

함수가 명확히 한 가지 일만 할 수 있도록 _init함수를 분리하겠습니다.

    def _init(self):
        pg.init()
        self._display_surf = pg.display.set_mode(self._size)
        pg.display.update(self._get_field_rect_list())
        self._running = True

    def _get_field_rect_list(self):
        field_rect_list = []
        for i in range(self._field_w):
            for j in range(self._field_h):
                _new_rect = pg.Rect(i*self._tile_size, j*self._tile_size, self._tile_size, self._tile_size)
                _draw_rect = pg.draw.rect(self._display_surf, (255, 255, 255), _new_rect, 1)
                field_rect_list.append(_draw_rect)
        return field_rect_list

 

이렇게 하면, 함수가 어떤 기능을 하는지 빠르게 파악할 수 있어, 그다음 작업을 하는 데 도움이 됩니다.

 

다음 시간에는, 키보드에 따라 움직이는 도형을 만들어 보겠습니다.

 

 


* reference

https://www.pygame.org/docs/ref/draw.html

https://www.pygame.org/docs/ref/display.html

https://github.com/elutecs/Retetris/blob/master/Shapes.py