16. 클래스, 인스턴스, 객체#
감사의 글
아래 내용은 (유튜브) Python Game Coding: Introduction to Collision Detection의 내용을 참고합니다.
준비사항
여기서 사용하는 코드는 두 객체의 충돌을 감지하는 다양한 방법을 보여주며,
(레플릿) 충돌 감지에서
확인하고 직접 실행할 수 있다.
오프라인 환경에서 실행하려면 먼저 아래 코드의 실행에 필요한
이미지 파일과 소스코드를
다운로드해서 압축을 푼 다음에 collision_dection.py
파일을 실행하면 된다.
16.1. 객체 교감: 충돌 감지#
객체의 이동과 충돌 감지를 지원하는 클래스를 선언하고 활용하는 과정을 상세히 소개한다.
충돌이 감지되는 경우
아래 두 그림은 팩맨 객체와 체리 객체의 충돌 이전과 이후를 보여준다. 오른쪽 사진에서 팩맨이 체리와 접촉하는 순간 체리가 X 자로 변하며 잡아먹히는 장면이다.
충돌이 감지되지 못하는 경우
반면에 아래 두 그림은 팩맨 객체와 체리 객체가 서로의 충돌을 감지하지 못하여 그냥 통과하는 장면을 보여준다.
16.2. 클래스 선언과 생성자#
아래 그림에 있는 스프라이트 객체를 생성할 때 사용되는 클래스를 확장하는 방식으로 선언하면서 클래스 선언에 필요한 요소들을 하나씩 살펴 본다.
16.2.1. 클래스, 인스턴스, 객체#
객체object는 특정 클래스class의 인스턴스로 생성된다. 즉, 클래스는 활용하고자 하는 객체object를 인스턴스instance로 생성할 때 사용되는 설계도에 해당하며, 생성되는 인스턴스가 가져야 하는 속성과 기능을 지정한다.
속성attribute: 클래스에서 선언된 변수. 인스턴스의 속성 저장.
메서드method: 클래스에서 선언된 함수. 인스턴스의 기능 지정.
16.2.2. 클래스 선언#
스프라이트 객체 선언에 필요한 클래스를 정의해보자. 클래스 정의는 다음 형식으로 이루어진다.
class Sprite:
...
# 속성 지정 및 메서드 선언
각각의 스프라이트 객체는 고유의 모양을 가져야 하며 이동 기능과 충돌감지 기능을 갖는다.
따라서 Sprite
클래스는 속성으로 스프라이트의 모양에 대한 정보를 저장하고,
스프라이트의 이동, 충돌감지 기능을 정의해야 한다.
16.2.3. 생성자#
모든 파이썬 클래스는 클래스의 인스턴스instance를 생성하는 생성자 메서드를 포함해야 한다.
파이썬 클래스의 생성자는 항상 __init__()
함수로 구현된다.
생성자는 함수의 인자로 받는 값을 생성하는 인스턴스의 속성attributes과
메서드의 기능을 지정하기 위해 사용한다.
예를 들어 아래 코드에서 선언되는 Sprite
클래스의 생성자는
생성되는 스프라이트 객체의 위치, 크기, 모양 등을 결정하는 요소를 지정한다.
class Sprite:
# 생성자: 스프라이트의 위치, 가로/세로 크기, 이미지 지정
def __init__(self, x, y, width, height, image):
"""
x: x-좌표
y: y-좌표
width: 가로 크기
height: 세로 크기
image: 모양
"""
self.x = x
self.y = y
self.width = width
self.height = height
self.image = image
인스턴스 초기 설정
엄밀히 말하면 __init__()
함수는 객체를 생성하는 게 아니라
생성되는 객체의 초기 설정을 담당하지만
생성자라는 표현이 일반적으로 사용된다.
16.3. 인스턴스와 객체#
객체object는 특정 클래스의 인스턴스를 가리키는 일반적인 이름이다.
Sprite
클래스의 인스턴스를 이용하여 객체가 갖는 속성과 기능을 설명한다.
16.3.1. 인스턴스 생성#
Sprite
클래스의 인스턴스를 생성하는 방식은 다음과 같이
Sprite
클래스를 마치 하나의 함수처럼 호출한다.
이때 사용되는 인자들은 생성자의 인자로 전달된다.
단, self
에 대한 인자는 지정하지 않는다.
wizard = Sprite(-128, 200, 128, 128, "wizard.gif")
그러면 생성자 함수 __init__()
가 파이썬 실행기에 의해 다음과 같이 호출된다.
__init__(wizard, -128, 200, 128, 128, "wizard.gif")
메모리 상에서 wizard
변수가 가리키는 스프라이트 객체는 아래와 같이 구성된다.
self
의 기능
self
매개변수에는 현재 생성되는 객체가 인자로 자동 지정된다.
이런 이유로 self
에 대해서는 굳이 인자를 명시하지 않는다.
16.3.2. 인스턴스 변수와 속성#
인스턴스 변수instance variable는
클래스 내부에서 self
와 함께 선언된 변수를 가리킨다.
앞서 선언된 Sprite
클래스의 인스턴스 변수는 아래 방식으로 선언된 x
, y
, width
, height
, image
다섯 개다.
self.x
self.y
self.width
self.height
self.image
인스턴스 변수는 해당 클래스의 영역scope에서만 의미를 가지며, 일반적으로 생성자가 실행되는 동안 선언되지만 클래스 내부 어디에서도 선언될 수 있다.
클래스의 인스턴스로 생성된 객체는 인스턴스 변수가 가리키는 값을 속성으로 갖는다. 이런 의미에서 인스턴스 변수가 가리키는 값을 인스턴스 속성instance attribute이라 부른다.
인스턴스 변수 이외에 self
를 사용하지 않는
클래스 변수class variable 가 선언될 수도 있지만
여기서는 다루지 않는다.
__init__()
함수의 매개 변수와 인스턴스 변수
함수를 호출할 때 매개 변수를 통해 함수 내부로 전달되는 인자는 해당 함수의 본문에서만 사용될 수 있다.
반면에 self.x
, self.width
처럼 선언되는 인스턴스 변수는 클래스 내부 어디에서도 사용될 수 있다.
Sprite
클래스의 생성자인 __init__()
함수 내부에서 선언된 다섯 개의 인스턴스 변수명이
매개 변수명과 동일하지만 이것은 의무사항이 아니다.
여기서는 함수 내부에서 굳이 다른 변수명을 사용할 이유가 없기에 동일한 변수명을 사용했을 뿐이다.
실제로 함수의 매개 변수와 클래스 내부에서 선언된 변수의 기능은 완전히 다르기 때문에 서로 충돌하지 않는다.
프로그래밍 입문자에게 조금은 난해할 수도 있기 때문에 아래와 같이 다른 변수명을 사용할 수도 있어지만 일부러 그러지 않았다. 이유는 많은 프로그래머가 유사한 방식으로 인스턴스 변수를 선언하기 때문이다.
def __init__(self, x_, y_, width_, height_, image_):
self.x = x_
self.y = y_
self.width = width_
self.height = height_
self.image = image_
16.3.3. 인스턴스 메서드#
생성자 __init__()
함수처럼 클래스 내부에 선언된 함수를 메서드method라 부른다.
메서드는 크게 인스턴스 메서드, 클래스 메서드, 정적 메서드로 나뉜다.
하지만 여기서는 인스턴스 메서드만 소개한다.
파이썬 인스턴스 메서드의 첫재 매개 변수는 항상 self
로 지정한다.
생성자 __init__()
함수 또한 이런 의미에서 인스턴스 메서드이며,
필요에 따라 인스턴스 메서드를 추가로 선언할 수 있다.
아래 코드는 여러 개의 인스턴스 메서드를 추가한 완성된 Sprite
클래스를 선언한다.
render()
메서드: 스프라이트 객체를 지정된 위치로 이동시킨 후에 지정된 이미지의 도장 찍기is_overlapping_collision()
,is_distance_collision()
,is_aabb_collision()
메서드: 두 스프라이트 객체의 충돌 감지
class Sprite():
## 생성자: 스프라이트의 위치, 가로/세로 크기, 이미지 지정
def __init__(self, x, y, width, height, image):
"""
x: x-좌표
y: y-좌표
width: 가로 크기
height: 세로 크기
image: 모양
"""
self.x = x
self.y = y
self.width = width
self.height = height
self.image = image
## 인스턴스 메서드
# 지정된 위치로 스프라이트 이동 후 도장 찍기
def render(self, pen):
pen.goto(self.x, self.y)
pen.shape(self.image)
pen.stamp()
# 충돌 감지 방법 1: 두 객체의 중심이 일치할 때
def is_overlapping_collision(self, other):
if self.x == other.x and self.y == other.y:
return True
else:
return False
# 충돌 감지 방법 2: 두 객체 사이의 거리가 두 객체 너비의 평균값 보다 작을 때
def is_distance_collision(self, other):
distance = (((self.x - other.x)**2) + ((self.y - other.y)**2))**0.5
if distance < (self.width + other.width) / 2.0:
return True
else:
return False
# 충돌 감지 방법 3: 객체를 둘러썬 경계상자가 겹칠 때
# aabb: Axis Aligned Bounding Box
def is_aabb_collision(self, other):
x_collision = (math.fabs(self.x - other.x) * 2) < (self.width +
other.width)
y_collision = (math.fabs(self.y - other.y) * 2) < (self.height +
other.height)
return (x_collision and y_collision)
16.4. OOP 예제: 객체 충돌 게임#
두 스프라이트 객체가 움직이면서 서로 충돌할 때 벌어지는 이벤트와 이벤트 처리 과정을 살펴 보면서 상호 독립적으로 생성된 객체들이 어떤 방식으로 교감하는지 살펴 본다.
기본 세팅
도화지 객체 wn
와 거북이 객체 pen
을 준비한다.
또한 스프라이트에 사용될 사진 7 개를 거북이 객체의 모양 라이브러리에 추가한다.
import turtle
import math
# 도화지 객체 설정
wn = turtle.Screen()
wn.bgcolor("black")
wn.title("Collision Detection")
wn.tracer(0) # 도화지 내용 한 번에 업데이트 되도록 지정
# 거북이 객체 설정
pen = turtle.Turtle()
pen.speed(0) # 가장 빠르게 이동
pen.hideturtle() # 숨김
# 스프라이트 이미지 7개 등록
shapes = ["wizard.gif", "goblin.gif",
"pacman.gif", "cherry.gif",
"bar.gif", "ball.gif", "x.gif"]
for shape in shapes:
wn.register_shape(shape)
위 코드는 turtle
모듈이 지원하는 Turtle
클래스와 Screen
클래스의 인스턴스를 활용한다.
코드에서 생성된 객체와 활용된 메서드의 기능은 다음과 같다.
거북이 객체
pen
:Turtle
클래스의 인스턴스다. 모양은 마우스를 이용하여 선택된 스프라이트의 이미지로 지정된다. 마우스 클릭으로 선택된 스프라이트의 이동은 따라서pen
객체를 지정된 위치로 이동시킨 후 도장을 찍는 방식으로 구현한다.pen.hideturtle()
: 거북이 객체pen
을 보이지 않도록 지정하기에 이동할 때 선은 그려지지 않는다.pen.speed(0)
: 거북이 객체의 이동 속도를 가장 빠르게 지정. 1이면 가장 느리고, 10일 때 빨라진다.
wn
:Screen
클래스의 인스턴스wn.tracer(0)
: 거북이 객체pen
이 지정된 위치로 이동할 때마다 찍는 스프라이트 이미지를 하나씩이 아닌 화면이 전환될 때마다 한꺼번에 실행되도록 설정한다.wn.register_shape()
: 거북이 객체의 모양으로 활용될 수 이미지를 등록한다.
이제부터는 스프라이트 객체를 생성하기 위한 Sprite
클래스를 직접
선언하면서 클래스의 주요 요소와 함께 인스턴스를 생성하는 방법을 살펴 본다.
스프라이트 객체 생성
위 그림에 포함된 6 종류의 스프라이트 객체를 생성한다. 스프라이트 중심의 x, y 좌표, 길이와 너비, 이미지 등이 지정된다.
wizard = Sprite(-128, 200, 128, 128, "wizard.gif")
goblin = Sprite(128, 200, 108, 128, "goblin.gif")
pacman = Sprite(-128, 0, 128, 128, "pacman.gif")
cherry = Sprite(128, 0, 128, 128, "cherry.gif")
bar = Sprite(0, -400, 128, 24, "bar.gif")
ball = Sprite(0, -200, 32, 32, "ball.gif")
# 스프라이트 리스트
sprites = [wizard, goblin, pacman, cherry, bar, ball]
메모리 상에서 wizard
변수가 가리키는 스프라이트 객체는 이제 아래와 같이 구성된다.
wizard
는 자신의 속성 뿐만 아니라 Sprite
클래스가 제공하는 render()
와 충돌감지 메소드 등
모든 인스턴스 메서드를 활용할 수 있다.
이벤트, 이벤트 처리, 콜백
마우스 클릭, 버튼 누루기, 키 입력 등을 사용하여
영향을 미치는 사건을 이벤트event라 부르며,
이벤트에 반응하도록 프로그램을 작성하는 것이
이벤트 처리다.
여기서는 goblin
, pacman
, ball
등 움직일 대상인 스프라이트 객체를 선택한 후에 상하좌우 화살표 키를
이용하여 움직이도록 하는 이벤트를 사용한다.
콜백callback은 이벤트 처리에 사용되는 함수를 가리킨다.
사용하는 패키지, 모듈 등에 따라 콜백 함수가 사용되는 방식이 달라진다.
turtle
모듈의 경우 Screen.listen()
, turtle.listen()
메서드가 이벤트 발생을 감지하며
이후에 다양한 콜백 함수를 이용하여 마우스로 선택된 스프라이트 객체를 이동시킨다.
마우스 클릭 감지
아래 sprite_idx_fn()
함수는 마우스가 클릭된 지점의 좌표를 이용하여 마우스로 선택된 스프라이트 객체의 인덱스를 지정한다.
함수 정의에 사용된 변수들은 다음과 같다.
sprite_idx
변수마우스로 선택된 객체의 인덱스 지정.
global
키워드와 함께 선언되었기에 함수 밖에서 선언된 전역 변수를 가리킴.
sprite.x
변수Sprite
객체의 인스턴스 변수인x
를 가리킴.Sprite
클래스에서self.x
로 선언되었지만 객체의 속성으로 사용될 때는self
대신 객체명이 사용됨.
distance
변수마우스 클릭 위치와
sprite
와의 거리측정된 거리가 특정
sprite
객체와의 거리가 해당 스프라이트 객체의width
의 절반 이하인 경우 해당 스프라이트의 인덱스 선택
sprite.width
변수Sprite
객체의 인스턴스 변수인width
를 가리킴.Sprite
클래스에서self.width
로 선언되었지만 객체의 속성으로 사용될 때는self
대신 객체명이 사용됨.
# (콜백) 마우스로 선택된 스프라이트 인덱스 지정
def sprite_idx_fn(x_, y_):
global sprite_idx # 전역 변수 활용
for idx, sprite in enumerate(sprites):
distance = (((sprite.x - x_)**2) + ((sprite.y - y_)**2))**0.5
if distance < sprite.width / 2:
sprite_idx = idx
# 마우스 클릭 감지 후 콜백 함수 바로 실행
wn.listen()
wn.onclick(sprite_idx_fn)
global
키워드와 전역 변수
파이썬은 함수 밖에서 선언된 전역 변수의 값을 함수 내에서 재할당하려면 global
키워드와 함께 지정해야 한다.
그렇게 하지 않으면 함수 내의 지역 변수로 취급되어 함수 밖의 전역 변수와 아무 상관이 없게 된다.
방향키 선택 감지
아래 코드는 상하좌우 방향키가 눌리는 이벤트를 감지할 때의 이벤트 처리를 담당한다.
# 이벤트 처리: 선택된 방향키에 따른 이벤트 지정
turtle.listen()
turtle.onkey(move_left, "Left") # 왼쪽 방향키 입력
turtle.onkey(move_right, "Right") # 오른쪽 방향키 입력
turtle.onkey(move_up, "Up") # 위쪽 방향키 입력
turtle.onkey(move_down, "Down") # 아래쪽 방향키 입력
move_left()
함수 등은 눌러진 방향키에 따라 선택된 스프라이트를 이동시킬 좌표를 지정한다.
선택된 스프라이는 sprites[sprite_idx]
가 가리킨다.
콜백 |
기능 |
---|---|
|
스프라이트 객체 왼쪽으로 32만큼 이동 |
|
스프라이트 객체 오른쪽으로 32만큼 이동 |
|
스프라이트 객체 윗쪽으로 32만큼 이동 |
|
스프라이트 객체 아랫쪽으로 32만큼 이동 |
# (콜백) 이동 화살표 키 이벤트 처리 함수
def move_left():
sprites[sprite_idx].x -= 32
def move_right():
sprites[sprite_idx].x += 32
def move_up():
sprites[sprite_idx].y += 24
def move_down():
sprites[sprite_idx].y -= 24
게임 실행
게임 실행 중에 화살표 키가 입력될 때마다 지정된 콜백 함수가 실행되어 의도된 스프라이트가 이동한다. 이동하다가 다른 스프라이트와 충돌하면 해당 스프라이트가 X 모양의 이미지로 변경된다.
while True:
# 각 스프라이트 위치 이동 및 도장 찍기
for sprite in sprites:
sprite.render(pen)
wn.update() # 도화지 내용 업데이트
pen.clear() # 스프라이트 이동흔적 삭제
# 충돌 여부 확인
if wizard.is_overlapping_collision(goblin):
wizard.image = "x.gif"
if pacman.is_distance_collision(cherry):
cherry.image = "x.gif"
if bar.is_aabb_collision(ball):
ball.image = "x.gif"
wn.mainloop()
위 코드에 사용된 객체, 인스턴스 속성, 인스턴스 메서드는 다음과 같다.
wn
:Screen
클래스의 인스턴스wn.update()
:wn
객체의 메서드wn.mainloop()
:wn
객체의 메서드
pen
:Turtle
클래스의 인스턴스pen.clear()
:pen
객체의 메서드
wizard
,pacman
,bar
:Character
클래스의 인스턴스image
변수Character
클래스의 인스턴스 변수wizard
,pacman
,bar
세 객체의 모양을 가리키는 속성
is_overlapping_collision()
,is_distance_collision()
,is_aabb_collision()
메서드Character
클래스의 인스턴스 메서드각각
wizard
,pacman
,bar
의 충돌 감지를 담당하는 메서드
프레임
이전 코드의 while
반복문이 한 번 실행되는 과정을 프레임frame이라 부른다.
1초당 몇 개의 프레임이 실행되는 가가 게임 동영상의 화질을 결정한다.
OOP의 핵심: 객체 교감
매 프레임마다 아래 코드가 반복적으로 실행된다.
if wizard.is_overlapping_collision(goblin):
wizard.image = "x.gif"
if pacman.is_distance_collision(cherry):
cherry.image = "x.gif"
if bar.is_aabb_collision(ball):
ball.image = "x.gif"
위 코드는 프레임마다 예를 들어 wizard
가 goblin
과 충돌하는 순간
wizard
의 이미지를 X 모양으로 변경한다.
그런데 wizard
가 goblin
과 충돌하는지를 어떻게 감지하는가?
wizard
의 is_overlapping_collision()
메서드가
자신과 goblin
의 위치, 길이, 너비 정보를 이용하여 알아낸다.
즉, wizard
가 goblin
의 속성 정보를 활용한다.
반면에 pacman
은 cherry
와의 출동을 is_distance_collision()
메서드로 확인하고,
bar
는 ball
과의 충돌을 is_aabb_collision()
메서드로 확인한다.
충돌 감지에 사용되는 세 개의 메서드는 서로 다른 방식으로 두 객체의 충돌을 감지한다.
충돌 감지 메서드 |
충돌 감지법 |
---|---|
|
두 객체의 중심이 일치할 때 충돌 감지 |
|
두 객체 사이의 거리가 두 객체 너비의 평균값 보다 작을 때 충돌 감지 |
|
객체를 둘러썬 경계상자가 겹칠 때 충돌 감지 |
이처럼 상호 독립적인 객체들이 서로 공유할 수 있는 정보를 이용하여 객체의 상태와 행동을 결정하는 프로그래밍 기법이 바로 객체 지향 프로그래밍이다.
코드 완성
지금까지 설명한 코드를 하나로 정리하면 다음과 같다.
import turtle
import math
## 스프라이트 클래스 선언
class Sprite():
## 생성자: 스프라이트의 위치, 가로/세로 크기, 이미지 지정
def __init__(self, x, y, width, height, image):
"""
x: x-좌표
y: y-좌표
width: 가로 크기
height: 세로 크기
image: 모양
"""
self.x = x
self.y = y
self.width = width
self.height = height
self.image = image
## 인스턴스 메서드
# 지정된 위치로 스프라이트 이동 후 도장 찍기
def render(self, pen):
pen.goto(self.x, self.y)
pen.shape(self.image)
pen.stamp()
# 충돌 감지 방법 1: 두 객체의 중심이 일치할 때
def is_overlapping_collision(self, other):
if self.x == other.x and self.y == other.y:
return True
else:
return False
# 충돌 감지 방법 2: 두 객체 사이의 거리가 두 객체 너비의 평균값 보다 작을 때
def is_distance_collision(self, other):
distance = (((self.x - other.x)**2) + ((self.y - other.y)**2))**0.5
if distance < (self.width + other.width) / 2.0:
return True
else:
return False
# 충돌 감지 방법 3: 객체를 둘러썬 경계상자가 겹칠 때
# aabb: Axis Aligned Bounding Box
def is_aabb_collision(self, other):
x_collision = (math.fabs(self.x - other.x) * 2) < (self.width +
other.width)
y_collision = (math.fabs(self.y - other.y) * 2) < (self.height +
other.height)
return (x_collision and y_collision)
## 게임 세팅
# 도화지 객체 설정
wn = turtle.Screen()
wn.bgcolor("black")
wn.title("Collision Detection")
wn.tracer(0) # 도화지 내용 한 번에 업데이트 되도록 지정
# 거북이 객체 설정
pen = turtle.Turtle()
pen.speed(0) # 가장 빠르게 이동
pen.hideturtle() # 숨김
# 스프라이트 이미지 7개 등록
shapes = ["wizard.gif", "goblin.gif",
"pacman.gif", "cherry.gif",
"bar.gif", "ball.gif", "x.gif"]
for shape in shapes:
wn.register_shape(shape)
## 스프라이트 생성
wizard = Sprite(-128, 200, 128, 128, "wizard.gif")
goblin = Sprite(128, 200, 108, 128, "goblin.gif")
pacman = Sprite(-128, 0, 128, 128, "pacman.gif")
cherry = Sprite(128, 0, 128, 128, "cherry.gif")
bar = Sprite(0, -400, 128, 24, "bar.gif")
ball = Sprite(0, -200, 32, 32, "ball.gif")
# 스프라이트 리스트
sprites = [wizard, goblin, cherry, pacman, bar, ball]
## 이벤트 처리
# 마우스로 선택된 스프라이트 기억 장치
# 마우스가 눌린 위치 정보를 활용하는 이벤트 처리 함수 fxn()에 의해 지정됨
sprite_idx = None
# (콜백) 이동 화살표 키 이벤트 처리 함수
def move_left():
sprites[sprite_idx].x -= 32
def move_right():
sprites[sprite_idx].x += 32
def move_up():
sprites[sprite_idx].y += 24
def move_down():
sprites[sprite_idx].y -= 24
# (콜백) 마우스로 선택된 스프라이트 인덱스 지정
def sprite_idx_fn(x_, y_):
global sprite_idx # 전역 변수 활용
for idx, sprite in enumerate(sprites):
distance = (((sprite.x - x_)**2) + ((sprite.y - y_)**2))**0.5
if distance < sprite.width / 2:
sprite_idx = idx
# 마우스 클릭 감지 후 콜백 함수 바로 실행
wn.listen()
wn.onclick(sprite_idx_fn)
# 이벤트 처리: 선택된 이동 화살표에 따른 이벤트 지정
turtle.listen()
turtle.onkey(move_left, "Left") # 왼쪽 방향 화살표 입력
turtle.onkey(move_right, "Right") # 오른쪽 방향 화살표 입력
turtle.onkey(move_up, "Up") # 위쪽 방향 화살표 입력
turtle.onkey(move_down, "Down") # 아래쪽 방향 화살표 입력
## 게임 진행
while True:
# 각 스프라이트 위치 이동 및 도장 찍기
for sprite in sprites:
sprite.render(pen)
wn.update() # 도화지 내용 업데이트
pen.clear() # 스프라이트 이동흔적 삭제
# 충돌 여부 확인
if wizard.is_overlapping_collision(goblin):
wizard.image = "x.gif"
if pacman.is_distance_collision(cherry):
cherry.image = "x.gif"
if bar.is_aabb_collision(ball):
ball.image = "x.gif"
wn.mainloop()