이전 글에서는, 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
- 10 x 20 크기의 Game Field
- 미리 보기
- Hold
가장 주요한 Field인 10 x 20의 Game Field를 먼저 만들어 보겠습니다.
그럼 200개의 사각형을 그려야 할 텐데, 어떻게 만들 수 있을까요? 공식 문서를 확인해 봅시다.
공식 문서를 읽어보자
사각형을 그리려면 pygame.draw.rect를 사용하면 된다고 합니다. 자세히 알아봅시다.
가장 간단하게 사각형을 그리는 방법 : surface, color, rect를 입력하여 draw 할 수 있다고 합니다.
- surface Surface를 객체로 받는다고 합니다. 마침 첫 예제 코드에 self._display_surf가 있음을 알 수 있습니다.
- color는 Color 객체 혹은 int, 혹은 tuple 형태로 받는다고 합니다. 우리는 RGB tuple을 사용해 봅시다.
- 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가 있습니다. 확인해 봅시다.
이런... 공식 홈페이지 자체적으로 제공하는 코드가 없네요... 대신 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인지 확인해 봐야 합니다.
display에 있는 flip과 update라는 설명이 있습니다. 내친김에 생략했던 pygame.display.set_mode()에 대해서도 살펴봅시다
위의 설명들을 요약하면 다음과 같습니다.
- pygame.display.set_mode()로 screen을 시작한다.
- pygame.display.flip()으로 display의 모든 contents를 update 한다.
- pygame.display.update()로 display의 부분을 업데이트한다.
- 특히 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
드디어 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
'Development > Tetris' 카테고리의 다른 글
Python - 테트리스(Tetris) 만들기 (6) - Tetriminos (2) | 2023.03.07 |
---|---|
Python - 테트리스(Tetris) 만들기 (5) - Event (0) | 2023.03.05 |
Python - 테트리스(Tetris) 만들기 (3) - Pygame Library (0) | 2023.03.04 |
Python - 테트리스(Tetris) 만들기 (2) - 환경 설정 (0) | 2023.03.04 |
Python - 테트리스(Tetris) 만들기 (1) - Intro (0) | 2023.03.04 |