18. 사례 연구: 상속#

18.1. 클래스 상속#

16장에서 사용한 Sprite 클래스를 상속해서 캐릭터에 새로운 기능을 추가한다.

참고

여기서 사용하는 코드는 (레플릿) 충돌 감지 - 상속 1에서 확인하고 직접 실행할 수 있다.

기본 설정

기본 설정은 이전과 동일하다.

import turtle
import math

wn = turtle.Screen()
wn.bgcolor("black")
wn.title("Collision Detection by @TokyoEdtech")
wn.tracer(0)

pen = turtle.Turtle()
pen.speed(0)
pen.hideturtle()

shapes = ["wizard.gif", "goblin.gif", "pacman.gif", "cherry.gif", "bar.gif", "ball.gif", "x.gif"]

for shape in shapes:
    wn.register_shape(shape)
    

스프라이트 클래스

이전에 정의했던 Sprite 클래스는 다음과 같다.

class Sprite():
    
    ## 생성자: 스프라이트의 위치, 가로/세로 크기, 이미지 지정

    def __init__(self, x, 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)

스프라이트 클래스 상속

Sprite 클래스를 상속하면서 Character 클래스를 정의한다. 생성자 함수가 조금 달라지며, 객체의 점프 기능을 담당하는 hop() 메서드가 추가된다. 하지만 hop() 함수를 실행했을 때 점프 기능이 지정된 경우에만 작동하도록 설정한다.

class Character(Sprite):
	def __init__(self, x, y, width, height, image, jump=False):
		super().__init__(x, y, width, height, image) # 부모 클래스의 초기화 그대로 활용
		self.jump = jump                             # jump 기능 지정

	# 점프 기능 담당 메서드: jump=True일 경우에만 작동.
    def hop(self, distance=300):
		if self.jump == True:
			self.x += distance

객체 생성

부모 클래스와 자식 클래스 모두를 이용하여 객체를 생성할 수 있다. wizardpacmanCharacter 클래스의 인스턴스로, 나머지는 Sprite 클래스의 인스턴스로 선언된다. 또한 pacman 객체는 점프 기능도 갖는다. wizard 객체는 점프 기능을 제대로 갖추지 못했기에 Sprite 클래스의 인스턴스와 동일한 기능만 수행한다.

wizard = Character(-128, 200, 128, 128, "wizard.gif")
goblin = Sprite(128, 200, 108, 128, "goblin.gif")

pacman = Character(-128, 0, 128, 128, "pacman.gif", jump=True)
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]

이벤트와 콜백 함수

팩맨의 점프 기능을 담당하는 콜백 함수와 이벤트 처리 기능이 추가된다.

# 고블린 이동
def move_goblin():
    goblin.x -= 64

# 팩맨 이동
def move_pacman():
    pacman.x += 30

# 팩맨 점프
def jump_pacman(distance=300):
    pacman.hop(distance)

# 야구공 이동
def move_ball():
    ball.y -= 24

# 이벤트 처리
wn.listen()
wn.onkeypress(move_goblin, "Left")  # 왼쪽 방향 화살표 입력
wn.onkeypress(move_pacman, "Right") # 오른쪽 방향 화살표 입력
wn.onkeypress(jump_pacman, "space") # 스페이크 키 입력
wn.onkeypress(move_ball, "Down")    # 아래방향 화살표 입력

게임 실행

게임 실행 코드는 이전과 동일하다.

while True:
    
    # 각 스프라이트 위치 이동 및 도장 찍기
    for sprite in sprites:
        sprite.render(pen)
        
    # 충돌 여부 확인
    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.update() # 화면 업데이트
    pen.clear() # 스프라이트 이동흔적 삭제

18.2. 모듈 활용 클래스 상속#

다른 파이썬 파일(모듈)에 정의된 Sprite 클래스를 상속하는 Character 클래스를 선언한다.

참고

여기서 사용하는 코드는 (레플릿) 충돌 감지 - 상속 2에서 확인하고 직접 실행할 수 있다. 레플릿 사이트의 특성상 주 실행파일의 이름은 main.py 이어야 한다. 따라서 Sprite 클래스는 collision_detection.py 라는 다른 파일에 정의되어 있다.

파일 구성

아래 두 개의 파일로 구성된다.

  • collision_detection.py: 캔버스 기본 설정과 Sprite 클래스 정의

  • main.py: Character 클래스 정의와 실행에 필요한 모든 코드

collision_dection.py 파일

캔버스 기본 설정과 Sprite 클래스의 정의가 포함된다.

import turtle
import math

wn = turtle.Screen()
wn.bgcolor("black")
wn.title("Collision Detection by @TokyoEdtech")
wn.tracer(0)

pen = turtle.Turtle()
pen.speed(0)
pen.hideturtle()

shapes = ["wizard.gif", "goblin.gif", "pacman.gif", "cherry.gif", "bar.gif", "ball.gif", "x.gif"]

for shape in shapes:
    wn.register_shape(shape)
    
class Sprite():
    
    ## 생성자: 스프라이트의 위치, 가로/세로 크기, 이미지 지정

    def __init__(self, x, 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)

main.py 파일

실행 관련 나머지 코드를 포함한다. 단, Sprite 클래스가 선언된 collision_detection 모듈에서 필요한 모든 것을 불러온다.

from collision_detection import *

class Character(Sprite):
	def __init__(self, x, y, width, height, image, jump=False):
		super().__init__(x, y, width, height, image) # 부모 클래스의 초기화 그대로 활용
		self.jump = jump                             # jump 기능 지정

	# 점프 기능 담당 메서드: jump=True일 경우에만 작동.
    def hop(self, distance=300):
		if self.jump == True:
			self.x += distance

wizard = Character(-128, 200, 128, 128, "wizard.gif")
goblin = Sprite(128, 200, 108, 128, "goblin.gif")

pacman = Character(-128, 0, 128, 128, "pacman.gif", jump=True)
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]

# 고블린 이동
def move_goblin():
    goblin.x -= 64

# 팩맨 이동
def move_pacman():
    pacman.x += 30

# 팩맨 점프
def jump_pacman(distance=300):
    pacman.hop(distance)

# 야구공 이동
def move_ball():
    ball.y -= 24

# 이벤트 처리
wn.listen()
wn.onkeypress(move_goblin, "Left")  # 왼쪽 방향 화살표 입력
wn.onkeypress(move_pacman, "Right") # 오른쪽 방향 화살표 입력
wn.onkeypress(jump_pacman, "space") # 스페이크 키 입력
wn.onkeypress(move_ball, "Down")    # 아래방향 화살표 입력

while True:
    
    # 각 스프라이트 위치 이동 및 도장 찍기
    for sprite in sprites:
        sprite.render(pen)
        
    # 충돌 여부 확인
    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.update() # 화면 업데이트
    pen.clear() # 스프라이트 이동흔적 삭제

18.3. 연습문제#

참고: (실습) 사례 연구: 상속