Post

Pygame으로 게임 만들어본 후기

Pygame은 다양한 사용자 입력 처리와 사운드, 그래픽 제공을 통해 python으로 간단한 게임을 만들 수 있게 해주는 라이브러리입니다. 이 글은 pygame을 사용하여 처음 작은 게임 하나를 시도해 본 후기로, 처음 사용하는 사람을 위한 소소한 가이드와 함께 시작합니다.

기초만 뽑은 Pygame 가이드

화면 설정

일단 설치하는 것은 생략하고, import pygame as pg 로 불러왔다고 칩니다.

제일 먼저 해야 하는 것은 게임 스크린을 만드는 것입니다. 원하는 크기(width * length) 의 게임 화면 창을 다음과 같은 방식으로 설정해 줄 수 있습니다. (크기를 설정하는 것은 꽤 중요한 부분입니다. 이 전체 스크린의 크기에 따라 이미지를 스케일링하고 위치를 지정해야 하기 때문이죠. )

1
2
3
4
screen_width = 400
screen_length = 400
screen = pg.display.set_mode((screen_width, screen_length))
#앞으로 이 screen이라는 변수에 계속 이미지든 텍스트든 뭔가를 표시해줄 것이다.

게임의 제목이 정해졌다면, 다음과 같이 창의 캡션을 넣어줄 수 있습니다.

1
2
pg.display.set_caption('pygame window')
#그 외에도 원하는 제목 아무거나!

이미지 출처

이미지 표시하기

이미지는 저장된 이미지파일의 경로를 입력해서 Surface라는 pygame 객체로 불러오고 → 스케일링한 다음(생략 가능) → 표시해주는 순서입니다.

예시 이미지가 저 위 사진의 파란 직사각형이라고 생각해 봅시다.

  • 표시할 때는 blit 을 쓰는데 이 메소드는 기본적으로 무엇을, 어디에 표시할 것인지를 지정해줘야 한다.
    • 무엇을: 불러와서 크기를 바꾼 surface (이미지)
    • 어디에: (x좌표, y좌표). 이때 x좌표는 가로축을 기준으로 맨 왼쪽이 0이라면 거기서 얼마나 떨어져 있는지, y좌표는 세로축을 기준으로 맨 위쪽이 0이라면 거기서 얼마나 떨어져 있는지를 뜻한다.
1
2
3
4
5
6
7
img_test = pg.image.load('img/blue_rect.png') #이미지 불러오기
img_test = pg.transform.scale(img_test, (200, 100)) #원하는 크기로 바꿔주기

img_loc = (300,300) #좌로부터 300, 위로부터 300 만큼의 위치
screen.blit(img_test, img_loc)

pg.display.update()

여기서 가장 중요한 것은 마지막 줄입니다. 화면을 업데이트해주지 않으면 반영이 되지 않습니다.

텍스트 표시하기

텍스트를 표시하려면 우선 폰트를 지정해줘야 합니다. 여기서는 두가지 선택지가 있는데요,

  1. 디렉토리에 저장된 폰트 파일(.ttf)을 불러오기

    1
    
     font_test = pg.font.Font('Nanumgothic.ttf', 30)
    
  2. 시스템에 등록된 폰트를 사용하기

    1
    2
    
     font_list = pg.font.get_fonts() #어떤 폰트가 있는지 리스트를 확인 후
     font_test = pg.font.SysFont('Nanumgothic', 30) #이렇게 간단히 이름만으로 설정 가능
    

두번째 파라미터는 폰트의 크기입니다.

참고로 한글 지원이 되지 않는 폰트를 사용한다면 기껏 입력한 한글이 귀여운 네모ㅁㅁㅁㅁ로 표시되는 걸 확인할 수 있을 것입니다. 한글을 표시하고 싶다면 꼭 적절한 폰트를 씁시다.

저는 개인적으로 SysFont 이용 시, 사용하는 인터프리터에서 프로그램 실행 후 실제 윈도우가 뜨고 동작하기까지 20초에 육박하는 도저히 한국인이 기다릴 수 없는 기나긴 시간이 걸리는 바람에 (그렇게 텍스트를 많이 쓰지 않았을 때도) 그냥 폰트 파일을 다운받아 첫번째 방식을 이용하였습니다.

폰트가 정해졌다면 다음과 같이 텍스트를 Surface로 렌더링하고, 이걸 이미지와 같은 방식으로 표시해 줍니다. 렌더링 시 지정해야 하는 것은 순서대로,

  • 표시할 텍스트
  • Anti-aliasing 여부 True / False
  • 텍스트의 색상 *(R, G, B) 형태로 기입. 구글에 RGB Color Picker 를 검색하면 원하는 색의 코드를 알아낼 수 있다. (0,0,0)은 까만색.
1
2
3
4
BLACK = (0,0,0) #자주 쓸 색이라면 이렇게 지정해놓고 써주자.
text_test = font.render('하고 싶은 말', True, BLACK)
text_loc = (200,100)
screen.blit(text_test, text_loc) #이번에도 위치 지정

하나의 루프 안에서 사용자 입력 처리하기

지금까지는 화면 세팅을 배웠습니다. 하지만 게임에서 가장 중요한 건 사용자 입력을 처리하는 방식입니다. 어떤 키를 누르면 캐릭터가 점프하고, 어떤 키를 누르다가 멈추면 비행기가 왼쪽으로 가다가 멈추는 것처럼요.

간단하게 우리가 만들 게임을 하나의 루프문이라고 생각합시다. 특정 조건을 걸면 이 루프를 끝낼 건데, 그 전까지는 계속 돌면서 게임이 진행되는 것입니다. 그 와중에 사용자가 어떤 입력을 보내주면 게임은 그것에 반응을 해야 합니다.

외부에서 발생하는 이런 사용자 입력을 우리는 이벤트 라고 부르겠습니다. 그리고 모든 발생 가능한 이벤트에 대해서, 이벤트 발생 시 어떤 일이 일어나라! 고 지정해주겠습니다. 발생한 이벤트는 event.get() 으로 가져오고 구분할 수 있습니다. pygame은 KEYDOWN , KEYUP , MOUSEMOTION , MOUSEBUTTONUP 등 다양한 이벤트를 지원합니다.

1
2
3
4
5
6
7
8
9
10
while True:
	for event in pg.event.get():
		if event.type == pg.QUIT:
			quit() #종료 버튼(창닫기) 시 게임 종료.

	key = pg.key.get_pressed() #어떤 키를 눌렀는지
	if key[K_SPACE] ==1:
		#누른 게 스페이스바라면...
	if key[K_Y] ==1:
		#키보드 y키라면...

이런 식으로 특정 이벤트가 발생했을 때 게임이 어떻게 반응할지를 처리해줄 수 있습니다.

여기까지 익혔다면 기본은 끝입니다.


왜, 그리고 무엇을 만들었냐면

사실 저는 게임 회사에 다니지만 게임 개발과는 관련 없는 일을 하고 있고, 게임을 하는 건 좋아하지만 만들고 싶은 생각은 딱히 한 적이 없습니다. 그런데 이번에 갑자기 pygame을 써보게 된 계기는,

  • 연애 상태를 약혼 상태로 전환하고 싶다.
  • 상대의 동의가 필요하므로 물어봐야 한다.
  • 진지함에 별로 자신이 없어서 선물을 포함한 본격 이벤트 이전에 가볍고 깜찍하게 시작하고 싶다.
  • 상대가 패미컴을 갖고 싶어 한 적이 있어서 분명 레트로 게임 테마를 좋아할 것 같다. 🎮

그래서 가볍고 깜찍한 게임을 한번 만들어보기로 했고요. 다시 말하지만 저는 관련된 경험 및 능력은 전혀 없으며 할 줄 아는 건 파이썬뿐입니다. 하지만 충분히 게임을 만들 수 있긴 하더라고요!

Pygame RPG TutorialOne Hour Dungeon 을 주로 참고했습니다.

제가 이번에 따라 만들어 본 게임은 스타듀밸리 광산과 포켓몬의 혼합이라고 하면 설명이 빠를 것 같습니다.

이미지 출처. 층마다 랜덤위치의 계단을 찾아 지하로 내려가는 구조

이미지 출처. 익숙한 턴제 배틀

  • 플레이어🐺는 목표 캐릭터🐧를 찾기 위해 여러 층의 맵을 뚫고 내려간다.
  • 각 층 맵에는 아이템 획득이나 몬스터 출현이 랜덤으로 발생하는 이벤트가 곳곳에 심어져 있다.
  • 몬스터 출현 시 턴제 배틀이 진행된다. (공격/포션/도망 중 액션 선택)
  • 각 층에서 다음 층으로 넘어가는 방법은 맵 중 한 칸에 랜덤으로 생성된 계단을 찾는 것이다.
  • 맵의 구성(바닥/벽/계단/랜덤 이벤트)는 다음 층으로 넘어갈 때 랜덤으로 생성된다. 특정 조건을 만족하면 다음 층 맵은 마지막 층이 되며, 계단이 아닌 목표 캐릭터 🐧가 등장하는 맵으로 생성한다.
    • 1) 3층 이상이며 2) 플레이타임이 180초가 지났을 때.
    • 사실 플레이를 하는 것이 주요 목적은 아니므로 너무 오래 걸리지 않도록 층수를 설정했다. 그러나 (계단이 매번 랜덤으로 생성되기 때문에) 운이 좋으면 지나치게 빠르게 내려갈 수도 있어서 너무 빨리 끝나지 않도록 시간 조건까지 추가.
  • 목표 캐릭터🐧를 찾고 질문에 답을 하면 엔딩.
  • 마지막 층에서 목표 캐릭터 펭귄🐧이 기다리고 있습니다. 설정상 접니다. 아주 귀엽죠? 그리고 차마 밝힐 수 없는 대사들을 몇 개 친 다음 중요한 질문을 물어봅니다. 참고로 이 질문에 Definitely가 아닌 그냥 Yes를 누르면 제가 기분이 좀 상하기 때문에 게임을 끝낼 수 없도록 만들었습니다. Definitely YES 를 골라야 엔딩이 뜹니다. 💍

(부록) 전체 루프가 동작하는 방식

아까 제가 하나의 게임이 하나의 루프라고 했는데 아무래도 실제로 짜보지 않으면 바로 와닿지는 않을 것 같습니다. 그래서 이 게임을 예시로 들어 어떻게 루프가 도는 건지 보여드리도록 하겠습니다.

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
29
tmr = 0
while True:
	for event in pygame.event.get():
		if event.type == QUIT:
			pygame.quit()
			sys.exit()
	tmr = tmr + 1
	key = pygame.key.get_pressed()

	if idx == 0:  #타이틀 화면
		### 배경화면 그리기
		if key[K_SPACE] == 1: #스페이스키 누르면
			idx = 1
			##다음 화면으로 넘어가고 랜덤 생성된 맵을 표시하기
  
	elif idx == 1:  #맵에서 플레이어가 이동하는 화면
		## 이동 시작할 때 몇 층인지 텍스트 표시

	elif idx == 2:  #화면 전환
		## 새로운 맵 표시, 층수 올리기

	elif idx == 3:  # 아이템 입수 혹은 함정
		#아이템이나 함정 이벤트 발동 시 발생하는 이벤트 설정
		if tmr == 10:
			idx = 1 #짧은 시간 대기 후 다시 맵에서 플레이어가 이동하는 화면으로

	###....

	pygame.display.update()

*IF문 내 대부분의 코드는 내용 설명에 불필요하여 헷갈리지 않도록 주석 처리 후 지워놓았습니다.

  • 게임을 종료하지 않으면 while 루프를 계속 돈다.
  • 마우스나 조이스틱 입력이 아닌 키보드 입력만 사용하는 게임이라 키보드 입력만 생각하자.
  • tmr은 루프가 한번 돌 때마다 1씩 올라가는 변수로, 화면 전환이 필요할 때 시간을 체크하기 위해 쓴다.
    • ex. tmr ==30 이면 루프를 30번 돌고 난 다음(즉, 잠깐 기다렸다가) 다음 것을 진행한다, 이런 식으로 정의 (그 다음에는 0으로 초기화해줘야 한다.)
  • idx는 게임 내에서 발생하는 모든 발생 가능한 화면에 대해 인덱스를 설정한 것이다. (0이면 타이틀 화면이고, 1이면 플레이어가 이동하는 화면고, 5이면 배틀 화면 등)
  • 각 화면 내에서 플레이어가 특정 행동을 하면 다른 화면으로 넘어갈 수 있도록 설정한다.
    • 예를 들어 위 예시에서는
      • 타이틀 화면(idx가 0인 상태)에서 스페이스키를 누르면 게임이 시작되기 때문에 입력된 키가 스페이스바면 (key[K_SPACE]==1) 플레이어가 맵에서 이동하는 화면(idx =1)로 바꾼다.
      • 플레이어가 맵에서 이동(idx가 1인 상태) 하다가 랜덤 생성된 아이템을 먹을 수 있는데, 그 경우 idx = 3으로 넘어간 다음에, 발생하는 부수적인 결과(아이템 이미지가 캐릭터 상단에 뜨는 효과와 소지템에 + 1이 됨)를 실행해준 다음 tmr이 10이 될 때까지, 즉 루프가 10번 돌 때까지(이때 루프는 아무 일도 발생하지 않고 계속 돌기만 함) 기다렸다가 다시 맵에서 이동하는 화면(idx=1)로 돌아간다.

즉 정리하면 while 내에서 여러개로 if-elif 로 각 인덱스(화면)이 어떻게 설정되어있는지, 그리고 어떤 행동을 하면 어떤 화면으로 넘어갈지를 정의하는 것이 이 프로그램의 전부입니다. 물론 그 과정에서 사전 정의된 함수들(랜덤 맵 형성하기, 배틀 시 몬스터 픽하기 등)을 끌어다 쓰게 되지만 전반적인 구조는 이렇게나 간단합니다! 저는 마지막 층의 이벤트(엔딩)까지 설정하고 나니 idx = 25까지 썼네요.


후기

간단히 요약하면 이렇습니다.

  • 결과는 성공. 하나뿐인 유저님께서 매우 만족하셨다. 😎
  • 과정이 생각보다 진짜! 재미있었다.
    • 텍스트랑 이미지를 한땀한땀 넣고 실행되는 걸 보는 코딩의 원초적인 재미가 이런 거였지 하고 정말 오랜만에 느꼈다.
  • 라이브러리 자체는 굉장히 사용하기 쉽고, 공개된 소스를 따라한 부분도 커서 실제로 구현하는 것보다 기획과 적절한 폰트/이미지/배경음악 찾기에 시간을 더 많이 쓴 것 같다.
    • ex. 얼음 맵으로 만들고 싶었기 때문에 ice floor pixel art 이런 식으로 열심히 검색… 대사 나올 때는 뭔가 뿅뿅하는 고전게임 느낌 살리면서도 너무 방방뜨지 않는 음악을 원해서 무한 검색과 청취… (찾는 데 실패해서 포기한 것도 많음)
    • 기획 개발 아트 등등 모든 전문가분들 새삼 존경스럽다..
  • 어쨌거나 pygame 한줄 요약은, 파이썬으로 WHILE과 IF문 쓸 수 있다면 게임을 만들 수 있다.

RPG뿐만 아니라 슈팅, 레이스 등 다양한 게임을 만들 수 있다고 합니다. 그리고 오래된 라이브러리인지라 찾아볼 수 있는 자료도 상당히 많고요. 관심 있으신 분들은 심심할 때 한번쯤 도전해보시는 것을 추천합니다.

This post is licensed under CC BY 4.0 by the author.