파이썬-파이게임 제로 (순한맛)
매운맛으로 공부하기Mu에디터 커스텀 버전 다운로드Mu에디터 한글화 프로젝트 참여책관련 Q&A
  • 게임제작하며 프로젝트 기반으로 파이썬 배우기 (순한맛)
  • 1. 파이게임 제로(pygame zero) 라이브러리
  • 2. 개발환경 구축
  • 3. Hello World 프로그램에서 시작하자
  • 4. 게임 루프의 이해
  • 5. 플래피 버드(Flappy bird) 게임 만들기
    • 5.1 화면에 배경 이미지(오브젝트) 나타내기
    • 5.2 화면에 플래피버드(오브젝트) 나타내기와 움직이기
    • 5.3 화면에 플래피버드(오브젝트)의 자연스러운 움직임 만들기
    • 5.4 화면에 파이프(오브젝트) 나타내기와 움직이기
    • 5.5 플래피버드와 파이프의 충돌 구현하기
    • 5.6 플래피버드 게임 추가기능 구현하기 (점수기능)
    • 5.7 플래피버드 게임 추가기능 구현하기 (파이프 위치 랜덤화)
    • 5.8 플래피버드 게임 추가기능 구현하기 (플래피버드 애니메이션)
  • 6. 블록격파(Breakout) 게임만들기
    • 6.1 게임무대에 배경과 배우 등장시키기
    • 6.2 배우들의 움직임 구현하기
    • 6.3 공의 반사와 블록격파 구현하기
    • 6.4 충돌검사시 고려할 것들
    • 6.5 (보너스) 오리지널 게임처럼 만들어 보기
  • 7. 트윈비(TwinBee) 슈팅게임 만들기
    • 7.1 스크롤 배경객체 만들기, 배경음, 배우(적, 주인공) 등장시키기
    • 7.2 배우들의 움직임과 총알 공격 구현하기
    • 7.3 충돌처리 및 기타정보(점수 및 게임종료) 표기
    • 7.4 그 밖에 도전과제
  • 8. 퐁(Pong) 게임 만들기
    • 8.1 절차지향으로 개발하기 1
    • 8.2 절차지향형으로 개발하기 2
    • 8.3 객체지향 개발이론 (객제지향 디자인이란)
    • 8.4 객체지향 개발이론 (사용자 정의 객체 만들기, 상속)
    • 8.5 객체지향으로 개발하기 1
    • 8.6 객체지향으로 개발하기 2
    • 8.7 (보너스) 파이게임제로 예제버전 1
    • 8.8 (보너스) 파이게임제로 예제버전 2
  • 9. 배틀 시티(Battle city) 게임 만들기
    • 9.1 절차지향형으로 개발하기 1
    • 9.2 절차지향형으로 개발하기 2
    • 9.3 객체지향으로 개발하기 1
    • 9.5 객체지향 개발이론 (인터페이스)
    • 9.6 객체지향으로 개발하기 2
    • 9.7 객체지향으로 개발하기 3
    • 9.8 객체지향으로 개발하기 4
  • 10. 퐁(Pong)을 네트워크 게임으로 만들기
    • 10.1 네트워크 게임방식의 이해와 라이브러리 설치
    • 10.2 릴레이 서버 구동과 클라이언트 접속
    • 10.3 네트워크 게임으로 만들기 1
    • 10.4 네트워크 게임으로 만들기 2
  • 부록
    • 게임을 단 한 개의 실행파일로 패키징 하기
    • 뮤 에디터 단축키 모음
Powered by GitBook
On this page

Was this helpful?

  1. 6. 블록격파(Breakout) 게임만들기

6.4 충돌검사시 고려할 것들

Previous6.3 공의 반사와 블록격파 구현하기Next6.5 (보너스) 오리지널 게임처럼 만들어 보기

Last updated 6 months ago

Was this helpful?

당신은 이전 절 끝에서 언급했던, 현재까지 구현한 게임의 그 특정(?) 버그(bug)를 발견했는가? 두 가지 정도 발견했을 것 같다. 두 가지 다 반사판의 양 옆 끝부분(edge)로 공을 받았을 때 발생하는 현상인데 첫번째는 엄밀하게 반사판이 공을 받아낸게 아님에도(공이 반사반에 닿지도 않았는데) 마치 정상 받아낸 것처럼 동작하는 현상이 하나 있다.

원인을 찾기 위해 공의 이미지의 실체에 대해 생각해보자. 그래픽에디터를 통해 직접 이미지를 제작해 본 경험이 있는 사람은 더 잘 이해할 것이다. 공 이미지는 게임상에서 우리 눈에 보이긴 원(circle)으로 보이나 실제 원은 아니고, 원래 맨 처음엔 가로세로 32x32px(픽셀) 크기의 정사각형 형태에 모든 픽셀이 꽉들어차 있고, 사람 눈에 보여야 하는 픽셀은 실제 사람 눈에 보이는 가시적인 색깔을 칠하게 되고, 사람 눈에 보이지 말아야 하는 픽셀(특별히 공의 네 귀퉁이를 둥글게 깎은 부분으로 체스판처럼 보이는 부분)은 투명처리색(transparent)으로 표시(색칠)하게된다. 따라서, 그 실체는 본래 사각형이나, 원처럼 보이게하기 위한 눈속임 정도로 생각할 수 있다.

따라서 우리 게임에 등장하는 배우(Actor)들의 원래 이미지는 사각형(rectangle)였던 것이고, 게임에서 말하는 이 이미지들간에 서로 닿았는가의 충돌(collision)이라는 의미는 아래 예시처럼 이 각각의 이미지들의 사각형 영역이 서로 닿았는가라는 것으로 판단(충돌검사)하는 것이었기 때문에, 아래의 예시처럼 내부적으로 실제 프로그램 상에는 닿은게 맞지만, 우리 눈에는 안닿은 것처럼 보여서 오동작처럼 보였던 현상이다.

이 충돌검사하는 기존 코드를 다시한번 살펴보면 왜 코딩에 사용한 메소드(객체소속 함수)이 이름이 colliderect (collide + rectangle) 였는지를 이제 이해할 수 있을 것이다.

if ball.colliderect(bar) == True:

자 그럼 이제 이 부분을 어떻게 처리하면 오동작처럼 보이지 않고 우리 눈에 자연스럽게 보일 수 있을까? 몇 가지 아이디어가 있을 수 있을 것 같다. 제일 첫번째로 생각해 볼 수 있는게 사각형(rect)끼리의 충돌검사가 아닌, 만약 우리 눈에 보이는 이미지가 원형(circle)에 더 가까우면 아래 그림의 우측처럼 충돌검사 영역을 원형으로 만들어 검사(detection)할 수 있으면 더 좋지 않을까? 괜찮은 아이디어이다.

from pgzhelper import *

TITLE = 'Breakout'
WIDTH = 800
HEIGHT = 600

GAP_FROM_SCREEN = 50
ball = Actor('ball', (WIDTH / 2, HEIGHT / 2))
ball.radius = ball.width / 2
bar = Actor('bar', (WIDTH / 2, HEIGHT - GAP_FROM_SCREEN))

# 4행*8열짜리 블록더미 만들기
blocks = []
for block_row in range(4):
    for block_col in range(8):
        block = Actor(
            'block', 
            (block_col * 100, block_row * 32 + GAP_FROM_SCREEN),
            anchor=('left', 'top')
        )
        blocks.append(block)

# 볼의 초기 (방향이 있는)속도값
vx = 5
vy = -5


def draw():
    screen.blit('space', (0, 0))
    ball.draw()
    bar.draw()
    for block in blocks:
        block.draw()
        
def update():
    global vx, vy
    
    # 반사판의 이동을 화면 안에 안에 가두기
    if bar.left < 0:
        bar.left = 0
    if bar.right > WIDTH:
        bar.right = WIDTH
    
    # vx와 vy만큼 속도로 공을 이동시키기
    ball.move_ip(vx, vy)

    # 공이 외쪽 또는 오른쪽벽에 부딪힐 때 
    if ball.left < 0 or ball.right > WIDTH:
        vx = -vx  # 속도의 x축 방향을 반대로하기
        sounds.wall.play()
        
    # 공이 위쪽벽 또는 반사판에 부딪힐 때 
    if ball.top < 0 or ball.circle_colliderect(bar) == True:
        vy = -vy  # 속도의 y축 방향을 반대로하기
        sounds.wall.play()
        
    # 공이 블록과 부딪힐 때
    b_index = ball.collidelist(blocks) 
    if b_index != -1:
        vy = -vy  # 속도의 y축 방향을 반대로하기
        sounds.block.play()
        blocks.pop(b_index)
        
    # 게임종료
    if ball.bottom > HEIGHT:
        sounds.die.play()
        game.exit()
        
    if not blocks:
        sounds.win.play()
        vx = 0
        vy = 0

def on_mouse_move(pos):
    x, y = pos
    bar.centerx = x

두번째 솔루션의 경우도 적용은 쉬운데, 객체 모양을 누끼따낸 픽셀간의 충돌검사를 위한 메소드인 collide_pixel 를 사용하면 되고, 이 메소드는 만약 충돌이 없을 경우엔 None 값을 리턴하기 때문에 그것에 맞춰 다음과 같이 53번라인 코드를 수정하면 끝이다.

if ball.top < 0 or ball.collide_pixel(bar) != None:

이제 두번째 버그를 살펴볼 차례이다. 두번째 버그는 아래의 그림처럼 공이 반사판의 옆구리(edge)에 닿는 순간 반사판을 타고 도는 의도지 않은 기현상(?)이 발생하고 있다. 먼저, 이 버그는 도데체 왜 생겨나는가에 대해 추측해보자.

정확한 원인을 유추하기 위해 타고 돌고 있는 공의 모습을 자세히 살펴보자. 여러분의 이해를 돕기 위해 공의 움직임을 슬로우 모션으로 캡쳐해 보여주고 있다. 첫번째 힌트로 공은 반사판 안에 갇혀서 이동하고 있다. 두번째 힌트로 공은 오르락 내리락을 반복하면서 이동하고 있다. 먼저 첫번째 문제가 공이 반사판 우측끝(edge)에 닿았을 때부터 문제가 생기는데 그 순간 54번 라인의 코드에 의해 공이 반사시키려고 공의 속도(velocity)를 반대방향으로 바꾸는데 바꾸는 것까진 좋았는데 그 결과로 오히려 공이 반사판 내부쪽으로 방향이 바뀌면서 반사판 내부로 침투(?)하는 현상이 생긴 것이다. 이 침투한게 매우 치명적이다. 원래는 공은 침투해서는 안되고, 표면에서 반사되어 반사판을 곧바로 떠나가야 하는데 알다시피 update 함수는 1초 60번이나 호출이 되는데 이는 매우 짧은 순간으로, 공의 아주 조금의 위치변화가 있긴 있었으나, 그래봐야 여전히 현 위치가 계속 반사판 내부를 벗어나지 못한 관계로 매번 계속 반사판과 충돌이라고 판단해 버리게 된다. 그결과로 연이은 매 update 함수 호출마다 속도방향을 반대로 바꾸게 되기 때문에 반사판에 갇혀 있는 공이 오르락 내리락 하면서 이동하게되는 것이다.

그다음으로 이제 원인은 알았으니 어떻게 해결하면 좋을까? 여러분에겐 어떤 아이디어가 있는지 무척이나 궁금하다. 이 문제를 해결하는데는 두 가지 정도의 해법이 있을 것 같다. 두 아이디어 모두 공이 반사판 안쪽으로 침투를 막는 것에 관한 것이다.

첫번 째 아이디어는 공이 반사판 끝쪽에서의 반사의 경우는 반사시 반사판쪽 방향으로 꺽이지 않고, 즉 공은 방향의 꺾임없이 들어온 방향 그대로 같은 방향으로 다시 되돌아 나가게 하는 것이다.

두번 째 아이디어는 반사방향에 대한 것은 건드리지 않되, 반사되어 나갈 때 반사판에 걸려서 그 안으로 침투가 안되도록 하기 위해 일단 공을 수직으로 얼마간(10픽셀) 반사판으로부터 떨어뜨려놓은 후, 그다음에 기존처럼 반사하는 것이다.

우리는 복잡하지 않게 최대한 간단하게 구현하는데 더 우선순위가 있으므로, 둘 중에 후자가 훨씬 간단하므로 해당 솔루션을 적용한 이번 게임의 최종 코드는 다음과 같다.

from pgzhelper import *

TITLE = 'Breakout'
WIDTH = 800
HEIGHT = 600

GAP_FROM_SCREEN = 50
ball = Actor('ball', (WIDTH / 2, HEIGHT / 2))
ball.radius = ball.width / 2
bar = Actor('bar', (WIDTH / 2, HEIGHT - GAP_FROM_SCREEN))

# 4행*8열짜리 블록더미 만들기
blocks = []
for block_row in range(4):
    for block_col in range(8):
        block = Actor(
            'block', 
            (block_col * 100, block_row * 32 + GAP_FROM_SCREEN),
            anchor=('left', 'top')
        )
        blocks.append(block)

# 볼의 초기 (방향이 있는)속도값
vx = 5
vy = -5


def draw():
    screen.blit('background', (0, 0))
    ball.draw()
    bar.draw()
    for block in blocks:
        block.draw()
        
def update():
    global vx, vy
    
    # 반사판의 이동을 화면 안에 안에 가두기
    if bar.left < 0:
        bar.left = 0
    if bar.right > WIDTH:
        bar.right = WIDTH
    
    # vx와 vy만큼 속도로 공을 이동시키기
    ball.move_ip(vx, vy)

    # 공이 외쪽 또는 오른쪽 벽에 부딪힐 때
    if ball.left < 0 or ball.right > WIDTH:
        vx = -vx  # 속도의 x축 방향을 반대로하기
        sounds.wall.play()
        
    # 공이 위쪽벽에 부딪힐 때
    if ball.top < 0: 
        vy = -vy  # 속도의 y축 방향을 반대로하기
        sounds.wall.play()
        
    # 공이 반사판에 부딪힐 때
    if ball.circle_colliderect(bar) == True:
        ball.y -= 10  # 10픽셀 수직으로 먼저 튀어오르기
        vy = -vy  # 속도의 y축 방향을 반대로하기    
        sounds.bar.play()
        
    # 공이 블록과 부딪 때
    b_index = ball.collidelist(blocks) 
    if b_index != -1:
        vy = -vy  # 속도의 y축 방향을 반대로하기    
        sounds.block.play()
        blocks.pop(b_index)
        
    # 게임종료
    if ball.bottom > HEIGHT:
        sounds.die.play()
        pygame.exit()
        
    if not blocks:
        sounds.win.play()
        vx = 0
        vy = 0

def on_mouse_move(pos):
    x, y = pos
    bar.centerx = x

그 다음으로 또 다른 아이디어는 뭐가 있을까? 완벽한 충돌검사를 위해 아예 사람 눈에 보이는 색깔이 칠해진 영역끼리만의 충돌 검사를 허락한다면 이것이 젤 완벽한 충돌검사일 것 같다. 그럴려면 본래 사각형에서 아래와 같이 색깔 칠해진 영역만 따로 한번 더 분리해 내야 하는 작업(그래픽툴에서는 이걸 라고 말함)이 추가로 필요하고, 또 이후에 실제 충돌영역 검사에서도 불규칙적인 외형끼리의 픽셀겸칩이라는 더 복잡한 연산이 필요한게 사실이다. 따라서, 이로인한 게임의 속도가 느려지는 현상이 생길 수도 있기에 이렇듯 각각의 솔루션에는 항상 트레이드오프(trade-off)가 있다. 결국 게임사용자에게 큰 불편을 주지 않는 어느정도 선해서 솔루션 선택의 타협을 해야할 것이다.

이제 이 솔루션을 적용해 기존 코드를 바꿔보자. 그런데, 아쉽게도 그런식의 충돌검사 방법을 파이게임제로 라이브러리에서는 제공하지 않고 있다는 것이다. 그러나, 다행인건 지난 에서 플래피버드 애니메이션을 위해 사용해봤던 파이게임제로를 기능확장하는 추가모듈(pgzhelper)에서는 제공하고 있기 때문에 이를 통해 해결할 수 있다는 것이다. 만약, 당신의 커스텀 뮤 에디터의 사용자가 아니고, 이전에 이 모듈을 설치한 적이 없다면, 최초 1회에 한 해 해당 모듈의 설치가 필요하며 설치에 관한 것은 에서 이미 충분히 설명하였으므로 해당 부분을 참고하여 설치하자. 다음으로 이제 위에서 언급한 첫번째 솔루션을 적용한 코드를 살펴보자.

기존 코드에서 딱 3라인의 변화만 있다. 먼저 맨 첫 라인에서 pgzhelper 모듈을 우선적으로 import 했다. 8-10라인의 게임등장 배우객체들의 생성코드 중, 기존과 다른 우리 목표는 공을 사각형(rect)가 아닌 원형(circle)로 간주되길 원하는거라 9번라인처럼 객체에 반지름(radius)을 사전에 설정해 두는게 필요하다. 가로세로 32픽셀 사각형 안에 꽉차는 원이기 때문에 반지름은 ball 이미지 길이의 절반(16픽셀)으로 ball 객체의 반지름(radius)을 설정해 놓으면 된다. 이제 실제 충돌검사하는 부분인 53번 라인에서는 circle_colliderect 함수를 사용하면 된다. 이 함수로 인해 반사판은 여전히 사각형(rect)일지라도 적어도 공 객체는 원 형태로해서 상호간에 충돌검사가 일어난다.

🔢
누끼따기
5.8절
5.8
절