728x90
반응형

mimi.png
0.02MB
mimi_s.png
0.01MB

이번엔 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 키를 누르면 게임이 리셋되는 기능을 추가했다!

 

github: https://github.com/kimjinho1/Python-Game/blob/main/%EA%B8%B0%EB%B3%B8%EC%A0%81%EC%9D%B8%20%EA%B2%8C%EC%9E%84%20%EA%B0%9C%EB%B0%9C%20%EA%B8%B0%EC%88%A0/%EA%B8%B0%EB%B3%B8%EC%A0%81%EC%9D%B8%20%EA%B2%8C%EC%9E%84%20%EA%B0%9C%EB%B0%9C%20%EA%B8%B0%EC%88%A0(%EC%8B%A4%EC%8B%9C%EA%B0%84%20%EC%B2%98%EB%A6%AC%2C%20%ED%82%A4%20%EC%9E%85%EB%A0%A5%20%EB%B0%9B%EA%B8%B0%2C%20%EB%AF%B8%EB%A1%9C%20%EA%B2%8C%EC%9E%84).ipynb 

 

kimjinho1/Python-Game

Contribute to kimjinho1/Python-Game development by creating an account on GitHub.

github.com

728x90
반응형

+ Recent posts