티스토리 뷰


이번 단계에서는 다음 기능을 추가한다.
- 공(ball)이 자동으로 움직인다.
- 공이 왼쪽 벽, 오른쪽 벽, 위쪽 벽에 부딪히면 방향이 바뀐다.
- 공이 패들(paddle)에 부딪히면 위로 튕긴다.
- 공이 벽돌(brick)에 부딪히면 벽돌이 사라진다.
- 공이 화면 아래로 떨어지면 생명(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]
공의 충돌 영역 만들기
공은 화면에 원(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
벽 충돌 처리
# 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. 출력
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
- 스마트기술
- UX
- 관계자분석
- 초보자를 위한 텍스트마이닝
- HCI
- UI
- python
- 데이타R지
- HIG
- 텍스트 마이닝
- pygame
- 챗봇
- 4차산업혁명
- 사물인터넷
- Idle
- matplotlib
- 라이브러리
- 컴퓨팅사고력
- 안드로이드
- 휴리스틱평가
- 데이터R지
- figma
- 챗GPT
- 파이썬기초
- 피그마
- Text Mining
- 텍스트마이닝
- 파이썬 기초
- 파이썬
- 프로토타입
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
