이번엔 tkinter를 사용해서 기본적인 게임 개발 기술(실시간 처리, 키 입력 받기, 미로 게임)에 대해 알아보자.
1 실시간 처리 구현하기
파이썬에서는 after() 명령으로 실시간 처리를 수행할 수 있다.
import tkinter
# 사건 카운트 변수
tmr = 0
def count_up():
global tmr # tmr을 전역 변수로 취급
tmr += 1
label["text"] = tmr
root.after(1000, count_up) # 1초 후에 다시 이 함수를 실행
root = tkinter.Tk()
label = tkinter.Label(font=("Times New Roman", 80))
label.pack()
root.after(1000, count_up)
root.mainloop()
위의 코드를 실행하면 아래와 같이 윈도우에 표시된 숫자가 1초마다 1 증가하는 것을 확인할 수 있다,
2 키 입력 받기
사용자가 소프트웨어에 대해 키나 마우스를 조작하는 것을 이벤트(event)라고 하는데
파이썬에서는 bind() 명령으로 이벤트를 받을 수 있다.
2.1 이벤트 받기
import tkinter
# 키 코드 입력 변수 선언
key = 0
# 키를 눌렀을 때 실행할 함수 정의
def key_down(e):
global key # key을 전역 변수로 취급
key = e.keycode
print(f"KEY: {key}")
root = tkinter.Tk()
root.title("키 코드 얻기")
root.bind("<KeyPress>", key_down)
root.mainloop()
위 코드를 실행하고 키보드 키를 누르면 아래와 같이 누른 키보드 키에 해당하는 아스키코드 값이 출력된다.
2.2 bind() 명령을 사용해 얻을 수 있는 이벤트
bind() 명령은 bind("<이벤트>", 함수) 같은 방식으로 사용하고 얻을 수 있는 주요 이벤트는 아래와 같다.
- KeyPress 혹은 key -> 키를 누름
- KeyRelease -> 키를 눌렀다가 뗌
- Motion -> 마우스 포인터 움직임
- ButtonPress 혹은 Button -> 마우스 버튼 클릭
3 키 입력에 따라 이미지 움직이기
3.1 실시간 키 입력
import tkinter
# 키 코드 입력 변수 선언
key = 0
# 키를 눌렀을 때 실행할 함수 정의
def key_down(e):
global key # key을 전역 변수로 취급
key = e.keycode
# 실시간 처리를 수행할 함수 정의
def main_proc():
label["text"] = key
root.after(100, main_proc)
root = tkinter.Tk()
root.title("실시간 키 입력")
root.bind("<KeyPress>", key_down)
label = tkinter.Label(font=("Times New Roman", 80))
label.pack()
main_proc()
root.mainloop()
위의 코드를 실행하면 아래와 같이 누른 키의 코드가 윈도우에 표시된다.
주요 키 코드는 아래와 같다.
- 방향키(좌상우하) -> 37~40
- Space키 -> 32
- Enter키 -> 13
- 알파벳 A~Z -> 65~90
- 숫자 0~9 -> 48~57
3.2 keysym 값을 사용해 판정하기
import tkinter
# 키 이름을 입력할 변수 선언
key = 0
# 키를 눌렀을 때 실행할 함수 정의
def key_down(e):
global key # key을 전역 변수로 취급
key = e.keysym # 눌려진 키 이름을 key에 대입
# 실시간 처리를 수행할 함수 정의
def main_proc():
label["text"] = key
root.after(100, main_proc)
root = tkinter.Tk()
root.title("실시간 키 입력")
root.bind("<KeyPress>", key_down)
label = tkinter.Label(font=("Times New Roman", 80))
label.pack()
main_proc()
root.mainloop()
위의 코드를 실행하고 Space키를 누르면 space, Enter나 return 키를 누르면 Return이라는 문자가 표시된다.
keysym으로 얻은 키 이름은 윈도우, 맥 공통이므로 keysym 값으로 판정하는 것이 편리하다고 한다!
3.3 실시간으로 캐릭터 움직이기
import tkinter
# 키 이름을 입력할 변수 선언
key = 0
# 키를 눌렀을 때 실행할 함수 정의
def key_down(e):
global key # key을 전역 변수로 취급
key = e.keysym # 눌려진 키 이름을 key에 대입
# 키를 눌렀다 뗐을 때 실행할 함수 정의
def key_up(e):
global key # key을 전역 변수로 취급
key = "" # key에 빈 문자열 대입
cx = 400 # 캐릭터의 X 좌표를 관리할 변수
cy = 300 # 캐릭터의 Y 좌표를 관리할 변수
# 실시간 처리를 수행할 함수 정의
def main_proc():
global cx, cy # cx, cy를 전역 변수로 선언
if key == "Up": # 위쪽 방향키 눌렀다면 Y 좌표 20 감소
cy -= 20
if key == "Down": # 아래쪽 방향키 눌렀다면 Y 좌표 20 증가
cy += 20
if key == "Left": # 왼쪽 방향키 눌렀다면 X 좌표 20 감소
cx -= 20
if key == "Right": # 오른쪽 방향키 눌렀다면 X 좌표 20 증가
cx += 20
canvas.coords("MYCHR", cx, cy) # 캐릭터 이미지 이동
root.after(100, main_proc) # 0.1초 후 main_proc 함수 지정
root = tkinter.Tk()
root.title("캐릭터 이동")
root.bind("<KeyPress>", key_down)
root.bind("<KeyRelease>", key_up)
canvas = tkinter.Canvas(width=800, height=600, bg="lightgreen")
canvas.pack()
img = tkinter.PhotoImage(file="mimi.png")
canvas.create_image(cx, cy, image=img, tag="MYCHR")
main_proc()
root.mainloop()
위의 코드를 실행하면 캐릭터가 표시되고 방향키를 통해 상하좌우로 이동할 수 있다.
coords()는 표시 중인 이미지를 새로운 위치로 이동시켜주는 명령이다.
canvas.create_image(cx, cy, image=img, tag="MYCHR") 부분에서 tag는 캔버스에 그리는
도형이나 이미지에 붙일 수 있고, 도형이나 이미지를 움직이거나 지우는 경우에 사용한다.
태그 명은 자유롭게 붙일 수 있다!
4 미로 게임 구현하기
이제 앞에서 봤던 것들을 사용해서 미로 게임을 구현해보자.
단계 별로 추가된 것들을 이해하며 넘어가도록 하자!
단계 1: 미로 데이터 정의
maze -> 통로: 0, 벽: 1
import tkinter
root = tkinter.Tk()
root.title("미로 표시")
canvas = tkinter.Canvas(width=800, height=560, bg="white")
canvas.pack()
# 리스트로 미로 정의
maze = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 1, 0, 0, 1],
[1, 0, 1, 1, 0, 0, 1, 0, 0, 1],
[1, 0, 0, 1, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 1, 1, 1, 1, 1, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
]
for y in range(7):
for x in range(10):
# maze[y][x]가 1, 즉 벽이라면 회색 사각형을 그림
if maze[y][x] == 1:
canvas.create_rectangle(x*80, y*80, x*80 + 80, y*80 + 80, fill="gray")
root.mainloop()
아래와 같이 미로가 생성됐다.
단계 2: 미로 안 걷기
import tkinter
# 키 이름을 입력할 변수 선언
key = 0
# 키를 눌렀을 때 실행할 함수 정의
def key_down(e):
global key # key을 전역 변수로 취급
key = e.keysym # 눌려진 키 이름을 key에 대입
# 키를 눌렀다 뗐을 때 실행할 함수 정의
def key_up(e):
global key # key을 전역 변수로 취급
key = "" # key에 빈 문자열 대입
mx = 1 # 캐릭터의 가로 뱡향 위치를 관리하는 변수
my = 1 # 캐릭터의 세로 뱡향 위치를 관리하는 변수
# 실시간 처리를 수행할 함수 정의
def main_proc():
global mx, my # mx, my를 전역 변수로 선언
# key 방향이 통로라면 그 방향에 맞게 mx, my값을 변경
if key == "Up" and maze[my-1][mx] == 0:
my -= 1
if key == "Down" and maze[my+1][mx] == 0:
my += 1
if key == "Left" and maze[my][mx-1] == 0:
mx -= 1
if key == "Right" and maze[my][mx+1] == 0:
mx += 1
canvas.coords("MYCHR", mx*80 + 40, my*80 + 40) # 캐릭터 이미지 이동
root.after(200, main_proc) # 0.2초 후 main_proc 함수 지정
root = tkinter.Tk()
root.title("미로 안 이동하기")
root.bind("<KeyPress>", key_down)
root.bind("<KeyRelease>", key_up)
canvas = tkinter.Canvas(width=800, height=560, bg="white")
canvas.pack()
maze = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 1, 0, 0, 1],
[1, 0, 1, 1, 0, 0, 1, 0, 0, 1],
[1, 0, 0, 1, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 1, 1, 1, 1, 1, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
]
for y in range(7):
for x in range(10):
if maze[y][x] == 1:
canvas.create_rectangle(x * 80, y * 80, x * 80 + 79, y * 80 + 79, fill="skyblue", width=0)
img = tkinter.PhotoImage(file="mimi_s.png")
canvas.create_image(mx * 80 + 40, my * 80 + 40, image=img, tag="MYCHR")
main_proc()
root.mainloop()
고양이 캐릭터가 미로 안을 걸을 수 있게 됐다.
단계 3: 지나간 장소 분홍색으로 칠하기
maze -> 통로: 0, 벽: 1, 지나간 위치: 2
import tkinter
# 키 이름을 입력할 변수 선언
key = 0
# 키를 눌렀을 때 실행할 함수 정의
def key_down(e):
global key # key을 전역 변수로 취급
key = e.keysym # 눌려진 키 이름을 key에 대입
# 키를 눌렀다 뗐을 때 실행할 함수 정의
def key_up(e):
global key # key을 전역 변수로 취급
key = "" # key에 빈 문자열 대입
mx = 1 # 캐릭터의 가로 뱡향 위치를 관리하는 변수
my = 1 # 캐릭터의 세로 뱡향 위치를 관리하는 변수
# 실시간 처리를 수행할 함수 정의
def main_proc():
global mx, my # mx, my를 전역 변수로 선언
# key 방향이 통로라면 그 방향에 맞게 mx, my값을 변경
if key == "Up" and maze[my-1][mx] == 0:
my -= 1
if key == "Down" and maze[my+1][mx] == 0:
my += 1
if key == "Left" and maze[my][mx-1] == 0:
mx -= 1
if key == "Right" and maze[my][mx+1] == 0:
mx += 1
'''
캐릭터가 있는 장소가 벽이 아니라면 리스트 값을 2로 변경하고
해당 위치를 분홍색으로 칠한다.
'''
if maze[my][mx] == 0:
maze[my][mx] = 2
canvas.create_rectangle(mx*80, my*80, mx*80 + 79, my*80 + 79,
fill="pink", width=0)
canvas.delete("MYCHR")
canvas.create_image(mx*80 + 40, my*80 + 40, image=img, tag="MYCHR") # 캐릭터 이미지 이동
root.after(200, main_proc) # 0.2초 후 main_proc 함수 지정
root = tkinter.Tk()
root.title("미로를 칠하는 중")
root.bind("<KeyPress>", key_down)
root.bind("<KeyRelease>", key_up)
canvas = tkinter.Canvas(width=800, height=560, bg="white")
canvas.pack()
maze = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 1, 0, 0, 1],
[1, 0, 1, 1, 0, 0, 1, 0, 0, 1],
[1, 0, 0, 1, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 1, 1, 1, 1, 1, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
]
for y in range(7):
for x in range(10):
if maze[y][x] == 1:
canvas.create_rectangle(x * 80, y * 80, x * 80 + 79, y * 80 + 79, fill="skyblue", width=0)
img = tkinter.PhotoImage(file="mimi_s.png")
canvas.create_image(mx * 80 + 40, my * 80 + 40, image=img, tag="MYCHR")
main_proc()
root.mainloop()
고양이 캐릭터가 지나간 길은 분홍색으로 칠해지고 왔던 길을 다시 돌아갈 수 없다.
단계 4: 게임 클리어 판정하기
import tkinter
import tkinter.messagebox # 메시지 박스 표시를 위해 import
# 키 이름을 입력할 변수 선언
key = 0
# 키를 눌렀을 때 실행할 함수 정의
def key_down(e):
global key # key을 전역 변수로 취급
key = e.keysym # 눌려진 키 이름을 key에 대입
# 키를 눌렀다 뗐을 때 실행할 함수 정의
def key_up(e):
global key # key을 전역 변수로 취급
key = "" # key에 빈 문자열 대입
mx = 1 # 캐릭터의 가로 뱡향 위치를 관리하는 변수
my = 1 # 캐릭터의 세로 뱡향 위치를 관리하는 변수
yuka = 0 # 칠해진 칸을 세는 변수
# 실시간 처리를 수행할 함수 정의
def main_proc():
global mx, my, yuka # mx, my, yuka를 전역 변수로 선언
# key 방향이 통로라면 그 방향에 맞게 mx, my값을 변경
if key == "Up" and maze[my-1][mx] == 0:
my -= 1
if key == "Down" and maze[my+1][mx] == 0:
my += 1
if key == "Left" and maze[my][mx-1] == 0:
mx -= 1
if key == "Right" and maze[my][mx+1] == 0:
mx += 1
'''
캐릭터가 있는 장소가 벽이 아니라면 리스트 값을 2로 변경,
칠한 회수를 1 증가시키고, 해당 위치를 분홍색으로 칠한다.
'''
if maze[my][mx] == 0:
maze[my][mx] = 2
yuka += 1
canvas.create_rectangle(mx*80, my*80, mx*80 + 79, my*80 + 79,
fill="pink", width=0)
canvas.delete("MYCHR")
canvas.create_image(mx*80 + 40, my*80 + 40, image=img, tag="MYCHR") # 캐릭터 이미지 이동
# 30개 칸을 모두 칠했다면 클리어 메시지를 표시해주고
# 그렇지 않다면 0.2초 후 main_proc 함수를 실행한다.
if yuka == 30:
canvas.update()
tkinter.messagebox.showinfo("축하합니다!", "모든 바닥을 칠했습니다!")
else:
root.after(200, main_proc)
root = tkinter.Tk()
root.title("미로를 칠하는 중")
root.bind("<KeyPress>", key_down)
root.bind("<KeyRelease>", key_up)
canvas = tkinter.Canvas(width=800, height=560, bg="white")
canvas.pack()
maze = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 1, 0, 0, 1],
[1, 0, 1, 1, 0, 0, 1, 0, 0, 1],
[1, 0, 0, 1, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 1, 1, 1, 1, 1, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
]
for y in range(7):
for x in range(10):
if maze[y][x] == 1:
canvas.create_rectangle(x * 80, y * 80, x * 80 + 79, y * 80 + 79, fill="skyblue", width=0)
img = tkinter.PhotoImage(file="mimi_s.png")
canvas.create_image(mx * 80 + 40, my * 80 + 40, image=img, tag="MYCHR")
main_proc()
root.mainloop()
통로를 다 지나면 게임 클리어 메시지 박스를 띄어준다.
단계 5: 다시 시작 처리 추가하기
import tkinter
import tkinter.messagebox # 메시지 박스 표시를 위해 import
# 키 이름을 입력할 변수 선언
key = 0
# 키를 눌렀을 때 실행할 함수 정의
def key_down(e):
global key # key을 전역 변수로 취급
key = e.keysym # 눌려진 키 이름을 key에 대입
# 키를 눌렀다 뗐을 때 실행할 함수 정의
def key_up(e):
global key # key을 전역 변수로 취급
key = "" # key에 빈 문자열 대입
mx = 1 # 캐릭터의 가로 뱡향 위치를 관리하는 변수
my = 1 # 캐릭터의 세로 뱡향 위치를 관리하는 변수
yuka = 0 # 칠해진 칸을 세는 변수
# 실시간 처리를 수행할 함수 정의
def main_proc():
global mx, my, yuka # mx, my, yuka를 전역 변수로 선언
# 왼쪽 Shift 키를 눌렀고 미로가 2칸 이상 칠해진 상태라면 게임 초기화
if key == "Shift_L" and yuka > 1:
canvas.delete("PAINT")
mx = 1
my = 1
yuka = 0
for y in range(7):
for x in range(10):
if maze[y][x] == 2:
maze[y][x] = 0
# key 방향이 통로라면 그 방향에 맞게 mx, my값을 변경
if key == "Up" and maze[my-1][mx] == 0:
my -= 1
if key == "Down" and maze[my+1][mx] == 0:
my += 1
if key == "Left" and maze[my][mx-1] == 0:
mx -= 1
if key == "Right" and maze[my][mx+1] == 0:
mx += 1
'''
캐릭터가 있는 장소가 벽이 아니라면 리스트 값을 2로 변경,
칠한 회수를 1 증가시키고, 해당 위치를 분홍색으로 칠한다.
'''
if maze[my][mx] == 0:
maze[my][mx] = 2
yuka += 1
# 태그 추가
canvas.create_rectangle(mx*80, my*80, mx*80 + 79, my*80 + 79,
fill="pink", width=0, tag="PAINT")
canvas.delete("MYCHR")
canvas.create_image(mx*80 + 40, my*80 + 40, image=img, tag="MYCHR") # 캐릭터 이미지 이동
# 30개 칸을 모두 칠했다면 클리어 메시지를 표시해주고
# 그렇지 않다면 0.2초 후 main_proc 함수를 실행한다.
if yuka == 30:
canvas.update()
tkinter.messagebox.showinfo("축하합니다!", "모든 바닥을 칠했습니다!")
else:
root.after(200, main_proc)
root = tkinter.Tk()
root.title("미로를 칠하는 중")
root.bind("<KeyPress>", key_down)
root.bind("<KeyRelease>", key_up)
canvas = tkinter.Canvas(width=800, height=560, bg="white")
canvas.pack()
maze = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 1, 0, 0, 1],
[1, 0, 1, 1, 0, 0, 1, 0, 0, 1],
[1, 0, 0, 1, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 1, 1, 1, 1, 1, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
]
for y in range(7):
for x in range(10):
if maze[y][x] == 1:
canvas.create_rectangle(x * 80, y * 80, x * 80 + 79, y * 80 + 79, fill="skyblue", width=0)
img = tkinter.PhotoImage(file="mimi_s.png")
canvas.create_image(mx * 80 + 40, my * 80 + 40, image=img, tag="MYCHR")
main_proc()
root.mainloop()
마지막으로 왼쪽 Shift 키를 누르면 게임이 리셋되는 기능을 추가했다!
'파이썬 게임 개발' 카테고리의 다른 글
파이썬으로 미로 게임 구현하기 2 (0) | 2021.06.29 |
---|---|
파이썬 GUI 기초 2(Tkinter, 고양이 지수 진단 프로그램) (0) | 2021.06.24 |
pyinstaller로 내가 만든 게임 exe파일 생성하기 (0) | 2021.06.23 |
파이썬으로 가위바위보 게임 구현하기(Tkinter) (0) | 2021.06.22 |
파이썬 GUI 기초 1(Tkinter, 제비뽑기 프로그램 ) (0) | 2021.06.20 |