티스토리 뷰

이번 단계에서는 다음 기능을 추가한다.

  1. 공(ball)이 자동으로 움직인다.
  2. 공이 왼쪽 벽, 오른쪽 벽, 위쪽 벽에 부딪히면 방향이 바뀐다.
  3. 공이 패들(paddle)에 부딪히면 위로 튕긴다.
  4. 공이 벽돌(brick)에 부딪히면 벽돌이 사라진다.
  5. 공이 화면 아래로 떨어지면 생명(life)이 줄고 공 위치가 초기화된다.

프롬프트

공이 움직이면서 
벽돌, 벽, 패들과 충돌이 발생하면 
처리하는 코드를 추가해 줘

Step3. 공의 움직임과 충돌 구현

이번 단계에서는 step02.py의 시작 버튼 코드에 이어서, 게임 상태(game state)가 "play"일 때만 공이 움직이고 충돌을 처리하도록 만든다. 핵심 흐름은 다음이다.

→ 공 이동 시작
→ 벽 충돌 처리
→ 패들 충돌 처리
→ 벽돌 충돌 처리

Step 3의 핵심은 다음 세 가지이다.

1. 공의 위치를 계속 바꾼다.
2. 공이 벽, 패들, 벽돌과 닿았는지 확인한다.
3. 충돌하면 공의 이동 방향을 반대로 바꾼다.

Step 3. 공 이동과 충돌 처리 추가 코드

1. 변수 설정 및 초기값

Step 3에서 먼저 공의 이동 속도를 추가한다.

# 공 정보
BALL_RADIUS = 10
BALL_SPEED_X = 4      # ADDED 공의 x축 이동 속도
BALL_SPEED_Y = -4     # ADDED 공의 y축 이동 속도
ball_pos = None
ball_vel = None       # ADDED 공의 이동 방향과 속도
 
  • ball_pos는 공의 위치(position)이다.
  • ball_vel은 공의 속도(velocity)이다.
  • 공은 x방향과 y방향으로 움직이므로 속도도 두 개의 값으로 저장한다.
  • 여기서 BALL_SPEED_Y = -4인 이유는 pygame 화면 좌표계 때문이다. pygame에서는 y값이 커질수록 아래쪽으로 이동한다. 따라서 위쪽으로 이동하려면 y값이 줄어들어야 한다.

2. init() 함수에서 공 위치와 속도 설정

init() 함수의 global 부분에 ball_vel을 추가한다.

# 1. 게임 초기화
def init():
    global screen, clock
    global ball_pos, ball_vel, paddle_rect, bricks  # ADDED ball_vel 추가
  • 그리고 공 초기 위치 부분을 다음처럼 수정한다.
    # 공 초기 위치
    ball_pos = [WIDTH // 2, HEIGHT // 2]

    # ADDED 공 초기 속도
    ball_vel = [BALL_SPEED_X, BALL_SPEED_Y]
 
  • 여기서 중요한 점은 ball_pos를 튜플(tuple)이 아니라 리스트(list)로 작성하는 것이다.
  •  

    공은 계속 위치가 바뀌어야 하므로 값을 수정할 수 있는 리스트가 적절하다.


3 입력 handle_input()

def handle_input():
    global running
    global paddle_dx

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    paddle_dx = 0

    keys = pygame.key.get_pressed()

    if keys[pygame.K_LEFT]:
        paddle_dx = -PADDLE_SPEED

    elif keys[pygame.K_RIGHT]:
        paddle_dx = PADDLE_SPEED

4. update_game() 함수 

기존 update_game() 함수의 pass를 아래 코드로 교체한다.

1. 패들 위치를 바꾼다. 
2. 공 위치를 바꾼다. 
3. 공의 충돌 영역을 만든다. 
4. 벽과 충돌했는지 확인한다. 
5. 패들과 충돌했는지 확인한다. 
6. 벽돌과 충돌했는지 확인한다. 
7. 공이 아래로 떨어졌는지 확인한다.
  • 공의 위치와 속도, 벽돌 목록을 바꾸어야 하므로 global에 추가한다.
def update_game():
    global ball_pos, ball_vel, bricks  # ADDED 공 위치, 공 속도, 벽돌 목록 수정

공 위치 업데이트

ball_pos[0] += ball_vel[0]
ball_pos[1] += ball_vel[1]
  • ball_pos[0]은 공의 x좌표이다. ball_pos[1]은 공의 y좌표이다.
  • ball_vel[0]은 x방향 이동값이다. ball_vel[1]은 y방향 이동값이다.

예를 들어 다음과 같다고 하자.

ball_pos = [300, 400]
ball_vel = [4, -4]
한 번 업데이트되면 다음처럼 바뀐다.
ball_pos = [304, 396]
이 과정이 매 프레임(frame)마다 반복되므로 공이 움직이는 것처럼 보인다.
 

공의 충돌 영역 만들기

공은 화면에 원(circle)으로 그려진다. 하지만 충돌 검사는 사각형(Rect)으로 처리하면 이해하기 쉽다.

ball_rect = pygame.Rect(
    ball_pos[0] - BALL_RADIUS,
    ball_pos[1] - BALL_RADIUS,
    BALL_RADIUS * 2,
    BALL_RADIUS * 2
)
 

이 코드는 공을 감싸는 보이지 않는 사각형을 만든다. 공의 중심이 ball_pos이고, 반지름이 BALL_RADIUS이므로 왼쪽 위 좌표는 다음과 같다.

ball_pos[0] - BALL_RADIUS
ball_pos[1] - BALL_RADIUS
가로와 세로는 공의 지름이다.
BALL_RADIUS * 2
이 ball_rect는 충돌 검사(collision check)를 위한 영역이다.
 

벽 충돌 처리

    # ADDED 왼쪽 벽 충돌
    if ball_pos[0] - BALL_RADIUS <= 0:
        ball_vel[0] = abs(ball_vel[0])

    # ADDED 오른쪽 벽 충돌
    if ball_pos[0] + BALL_RADIUS >= WIDTH:
        ball_vel[0] = -abs(ball_vel[0])

    # ADDED 위쪽 벽 충돌
    if ball_pos[1] - BALL_RADIUS <= 0:
        ball_vel[1] = abs(ball_vel[1])

공이 왼쪽 벽에 닿으면 오른쪽으로 튕긴다.

if ball_pos[0] - BALL_RADIUS <= 0:
    ball_vel[0] = abs(ball_vel[0])
 

공이 오른쪽 벽에 닿으면 왼쪽으로 튕긴다.

if ball_pos[0] + BALL_RADIUS >= WIDTH:
    ball_vel[0] = -abs(ball_vel[0])
공이 위쪽 벽에 닿으면 아래쪽으로 튕긴다.
 
if ball_pos[1] - BALL_RADIUS <= 0:
    ball_vel[1] = abs(ball_vel[1])
 

여기서 핵심은 abs()이다. abs()는 절댓값(absolute value)을 구하는 함수이다.

abs(-4)  # 결과: 4
abs(4)   # 결과: 4
 

따라서 다음 코드는 x속도를 양수로 만들어 오른쪽으로 이동하게 한다.

ball_vel[0] = abs(ball_vel[0])
 

다음 코드는 x속도를 음수로 만들어 왼쪽으로 이동하게 한다.

ball_vel[0] = -abs(ball_vel[0])

패들과 충돌 처리

if ball_rect.colliderect(paddle_rect):
    if ball_vel[1] > 0:
        ball_vel[1] = -abs(ball_vel[1])
        ball_pos[1] = paddle_rect.top - BALL_RADIUS
  • colliderect()는 두 사각형이 겹쳤는지 확인하는 함수이다.
ball_rect.colliderect(paddle_rect)
  • 공이 아래로 내려오는 중일 때만 패들과 충돌 처리

 

if ball_vel[1] > 0:
 

공이 아래로 내려오는 중일 때만 패들과 충돌 처리한다는 뜻이다. 패들과 충돌하면 공은 위쪽으로 튕겨야 한다.

ball_vel[1] = -abs(ball_vel[1])
 

그리고 공이 패들 안에 끼지 않도록 위치를 패들 위로 올린다. 

ball_pos[1] = paddle_rect.top - BALL_RADIUS
 

벽돌과 충돌 처리

for brick in bricks[:]:
    if ball_rect.colliderect(brick):
        bricks.remove(brick)
        ball_vel[1] *= -1
        break
 

벽돌은 여러 개이므로 반복문으로 하나씩 검사한다. bricks[:]는 벽돌 리스트의 복사본이다.
반복문 안에서 벽돌을 제거하기 때문에 복사본을 사용하는 것이 안전하다.

  • 공과 벽돌이 충돌하면 해당 벽돌을 제거한다.
bricks.remove(brick)
  • 그리고 공의 y축 방향을 반대로 바꾼다.
ball_vel[1] *= -1
  • 마지막으로 break를 사용한다.
break
  • 한 번의 충돌에서 벽돌 여러 개가 동시에 사라지지 않게 하기 위한 것이다.

공이 아래로 떨어졌을 때

if ball_pos[1] - BALL_RADIUS > HEIGHT:
    ball_pos = [WIDTH // 2, HEIGHT // 2]
    ball_vel = [BALL_SPEED_X, BALL_SPEED_Y]
 
  • 공이 패들을 지나 화면 아래로 떨어지면 다시 중앙으로 보낸다. 
  • 아직 3단계에서는 생명(life)을 다루지 않으므로, 일단 공을 중앙으로 되돌리는 방식으로 처리한다.

5. 출력

 
  •  
출력 함수는 2단계와 거의 같다.
pygame.draw.circle(
    screen,
    WHITE,
    (int(ball_pos[0]), int(ball_pos[1])),
    BALL_RADIUS
)

다만 3단계에서는 ball_pos가 계속 바뀐다.
그래서 같은 공 그리기 코드라도, 화면에서는 공이 움직이는 것처럼 보인다.

벽돌 출력도 그대로이다.

for brick in bricks:
    pygame.draw.rect(screen, WHITE, brick)
 

하지만 벽돌과 충돌하면 bricks.remove(brick)으로 벽돌이 제거된다. 따라서 제거된 벽돌은 더 이상 화면에 그려지지 않는다.


6. 실행

실행 함수는 2단계와 같다.


이번 단계의 핵심 정리

3단계의 핵심은 다음 한 문장이다.

공은 속도만큼 이동하고, 충돌하면 이동 방향을 반대로 바꾼다.

  이 두 줄이 공을 움직인다. 충돌이 발생하면 속도 방향을 바꾼다.

ball_vel[0] *= -1
ball_vel[1] *= -1
  • 벽돌과 충돌하면 벽돌을 제거한다.
bricks.remove(brick)
  • 즉, 벽돌깨기 게임의 공 움직임은 다음 구조로 이해하면 된다.
공 위치 변경
→ 충돌 확인
→ 충돌한 대상에 따라 방향 변경
→ 벽돌은 제거


전체 소스: step03.py

import pygame
import sys


# 전역 변수
WIDTH, HEIGHT = 600, 800
screen = None
clock = None
running = True

# 색상
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)

# 공 정보
BALL_RADIUS = 10
BALL_SPEED_X = 4      # ADDED 공의 x축 이동 속도
BALL_SPEED_Y = -4     # ADDED 공의 y축 이동 속도
ball_pos = None
ball_vel = None       # ADDED 공의 이동 방향과 속도

# 패들 정보
PADDLE_WIDTH = 100
PADDLE_HEIGHT = 15
PADDLE_SPEED = 7
paddle_rect = None
paddle_dx = 0

# 벽돌 정보
BRICK_ROWS = 5
BRICK_COLS = 8
BRICK_WIDTH = 60
BRICK_HEIGHT = 20
BRICK_GAP = 10
bricks = []


# 1. 게임 초기화
def init():
    global screen, clock
    global ball_pos, ball_vel, paddle_rect, bricks  # ADDED ball_vel 추가

    pygame.init()
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    pygame.display.set_caption("벽돌깨기 3단계 - 공 이동과 충돌")
    clock = pygame.time.Clock()

    # 공 초기 위치
    ball_pos = [WIDTH // 2, HEIGHT // 2]

    # ADDED 공 초기 속도
    ball_vel = [BALL_SPEED_X, BALL_SPEED_Y]

    # 패들 초기 위치
    paddle_x = WIDTH // 2 - PADDLE_WIDTH // 2
    paddle_y = HEIGHT - 50
    paddle_rect = pygame.Rect(
        paddle_x,
        paddle_y,
        PADDLE_WIDTH,
        PADDLE_HEIGHT
    )

    # 벽돌 생성
    bricks = []

    total_brick_width = BRICK_COLS * BRICK_WIDTH + (BRICK_COLS - 1) * BRICK_GAP
    start_x = WIDTH // 2 - total_brick_width // 2
    start_y = 80

    for row in range(BRICK_ROWS):
        for col in range(BRICK_COLS):
            brick_x = start_x + col * (BRICK_WIDTH + BRICK_GAP)
            brick_y = start_y + row * (BRICK_HEIGHT + BRICK_GAP)

            brick = pygame.Rect(
                brick_x,
                brick_y,
                BRICK_WIDTH,
                BRICK_HEIGHT
            )

            bricks.append(brick)


# 2. 입력 처리
def handle_input():
    global running
    global paddle_dx

    paddle_dx = 0

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # 방향키 입력 처리
    keys = pygame.key.get_pressed()

    if keys[pygame.K_LEFT]:
        paddle_dx = -PADDLE_SPEED

    if keys[pygame.K_RIGHT]:
        paddle_dx = PADDLE_SPEED


# 3. 게임 업데이트
def update_game():
    global ball_pos, ball_vel, bricks  # ADDED 공 위치, 공 속도, 벽돌 목록 수정

    # 패들 위치 업데이트
    paddle_rect.x += paddle_dx

    # 패들이 화면 밖으로 나가지 않도록 제한
    if paddle_rect.left < 0:
        paddle_rect.left = 0

    if paddle_rect.right > WIDTH:
        paddle_rect.right = WIDTH

    # ADDED 공 위치 업데이트
    ball_pos[0] += ball_vel[0]
    ball_pos[1] += ball_vel[1]

    # ADDED 공을 충돌 검사용 사각형으로 변환
    ball_rect = pygame.Rect(
        ball_pos[0] - BALL_RADIUS,
        ball_pos[1] - BALL_RADIUS,
        BALL_RADIUS * 2,
        BALL_RADIUS * 2
    )

    # ADDED 왼쪽 벽 충돌
    if ball_pos[0] - BALL_RADIUS <= 0:
        ball_vel[0] = abs(ball_vel[0])

    # ADDED 오른쪽 벽 충돌
    if ball_pos[0] + BALL_RADIUS >= WIDTH:
        ball_vel[0] = -abs(ball_vel[0])

    # ADDED 위쪽 벽 충돌
    if ball_pos[1] - BALL_RADIUS <= 0:
        ball_vel[1] = abs(ball_vel[1])

    # ADDED 패들과 충돌
    if ball_rect.colliderect(paddle_rect):
        if ball_vel[1] > 0:
            ball_vel[1] = -abs(ball_vel[1])
            ball_pos[1] = paddle_rect.top - BALL_RADIUS

    # ADDED 벽돌과 충돌
    for brick in bricks[:]:
        if ball_rect.colliderect(brick):
            bricks.remove(brick)
            ball_vel[1] *= -1
            break

    # ADDED 공이 화면 아래로 떨어졌을 때
    if ball_pos[1] - BALL_RADIUS > HEIGHT:
        ball_pos = [WIDTH // 2, HEIGHT // 2]
        ball_vel = [BALL_SPEED_X, BALL_SPEED_Y]


# 4. 출력 처리
def render():
    screen.fill(BLACK)

    # 벽돌 그리기
    for brick in bricks:
        pygame.draw.rect(screen, WHITE, brick)

    # 공 그리기
    pygame.draw.circle(
        screen,
        WHITE,
        (int(ball_pos[0]), int(ball_pos[1])),
        BALL_RADIUS
    )

    # 패들 그리기
    pygame.draw.rect(screen, WHITE, paddle_rect)

    pygame.display.flip()


# 5. 게임 실행
def run_game():
    init()

    while running:
        handle_input()
        update_game()
        render()
        clock.tick(60)

    pygame.quit()
    sys.exit()


run_game()
반응형
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/06   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함