[파이썬 간단한 게임 만들기] 5. 핑퐁(Ping-Pong)
유치한 게임에 오신 것을 환영합니다.
이번에는 핑퐁 게임을 만들어 보겠습니다.
아래와 같은 순서로 배워보겠습니다.
1. 목표
이번 장에서는 핑퐁 게임을 만들어 보도록 하겠습니다.
이번에 만들 게임은 핑퐁 게임입니다. 핑퐁 게임의 규칙은 다음과 같습니다.
이름에서도 알 수 있듯이 탁구에서 유래된 이 핑퐁 게임은 탁구 공이 양쪽에서 번갈아 가면서 네트를 넘어 탁구판 위를 튀어 넘나드는 규칙을 적용하였습니다. 다만, 네트가 없다는 점과 x축으로 공이나가는 'out'은 있지만 y축은 'out'이 없다는 차이가 있습니다.
이번 장에서는 이 핑퐁 게임을 구현 해보도록 하겠습니다.
2. 사전 준비
1) 대각선 처리
- 공이 대각선으로 이동하는 방법을 생각해 봅니다.
> 공의 반지름은 9, 시작위치는 100x100, 이동은 5만큼씩
- 벽에 부딪히면 방향을 이동시켜 동작시키는 방법을 생각해 봅니다.
참고) 대각선으로 움직이는 공 / 벽에 부딪히면 방향을 바꾸는 공
// 코드 미공개
2) 게임은 어떤 고려 사항이 있을까요?
2-1) 탁구공
- 대각선으로 이동함
- 시작점은 오른쪽 중앙
- 탁구채면을 제외하고는 벽에 부딪히면 움직임
- 탁구채면은 탁구채에 닿아야 움직임
2-2) 탁구채
- 상/하로 이동함
2-3) 게임 종료 조건
- 탁구채에 탁구 공이 맞지 않는 경우
- [x] 키가 눌려졌을때
참고) 설계를 해보자!
3) 게임판 구상
import pygame # 1. pygame 선언
pygame.init() # 2. pygame 초기화
# 3. pygame에 사용되는 전역변수 선언
WHITE = (255,255,255)
BLACK = (0, 0, 0)
size = [400, 300]
screen = pygame.display.set_mode(size)
done = False
clock = pygame.time.Clock()
# 4. pygame 무한루프
def runGame():
global done
## 게임판 크기
screen_width = size[0]
screen_height = size[1]
while not done:
clock.tick(30)
screen.fill(BLACK)
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
break
pygame.display.update()
runGame()
pygame.quit()
게임을 진행할 화면을 다음과 같이 설정하도록 하겠습니다.
6번 라인(공, 막대-탁구채) : white(RGB - 255,255,255)
7번 라인(화면 색) : black(RGB - 0,0,0)
8,9번 라인(화면 크기) : 넓이(width) - 400, 높이(height) - 300/ 400x300
18, 19라인 : 게임에서 게임판의 width와 height를 많이 사용하기에 각각 변수를 두어 게임판의 크기를 설정합니다.
3. 소스 코드 (전체)
파일명 :bomb_game.py
4. 사전 지식
1) 핑퐁 게임에 필요한 요소 정의
앞서 말씀드린 것 처럼 핑퐁 게임은 양쪽에서 공을 밖으로 'out' 되지 않도록 공을 주고 받는 것이 목표입니다.
이번 게임에는 게임 속에서 반응에 따라 지속적으로 움직일 '공'과 공을 칠수 있는 '채'가 필요합니다.
'채' 역할을 수행할 'bar'와 공의 역할을 수행할 'circle'로 명명하겠습니다.
탁구'채' 역할을 하는 'bar'는 직사각형을 그려 표현하고,
탁구'공' 역할을 하는 'circle'은 사각형을 그리던 방식과 다르게 circle()이라는 방식을 이용해 도형을 그려주게 됩니다. circle 함수의 특징으로는 원의 반지름을 통해 도형을 그려낸다는 점이 있습니다.
그렇다면 각각의 도형을 만드는 방법에 대해 알아보도록 하겠습니다.
## 탁구채
pygame.draw.rect(screen,
WHITE,
(bar_x, bar_y, int(bar_width), int(bar_height)))
## 탁구공
pygame.draw.circle(screen,
WHITE,
(int(circle_x), int(circle_y)),
int(circle_radius))
탁구채는 직사각형으로, 탁구공은 원으로 만들었습니다.
2) 공의 흐름
앞서 공을 생성하였고 다음으로는 공의 흐름에 대해 알아보도록 하겠습니다.
현실 세계에서 공은 물리적인 법칙인 중력과 마찰력에 의해 영향을 받으며 탁구 판에 부딛혀 이동하지만 게임판 내부에서는 조금 더 간단하게 벽이나 채(bar)에 맞았을 때, 입사각과 반사각의 원리가 적용되어 움직이게 됩니다.
즉, 공이 어떠한 경계점에 도달했을 때, 해당하는 공이 날아온 위치에 기반하여 다음 이동 방향을 설정해주는 것이 필요합니다.
따라서 해당 방식을 게임 내부에서 구현하기 위해서는 공의 x축 이동속도와 y축 이동속도를 설정하여 진행 방향을 설정해주도록 합니다. 탁구공은 대각선 방향으로 이동합니다. 그래서, 공은 x,y축 모두 변경되며 이동합니다. 이번에는 화면 프레임에 맞추어 속도를 지정하도록 하겠습니다. 이전까지는 속도에 특정 값을 지정했었죠? '화면 프레임' 속도에 맞춰 좌표를 지정하는 방법도 공부해봅시다.
1
2
3
4
5
6
7
|
speed_x, speed_y, speed_bar = -screen_width / 1.28, screen_height / 1.92, screen_height * 1.2
time_passed = clock.tick(30) ## a max of 30 times per second, unit : ms
time_sec = time_passed / 1000.0
circle_x += speed_x * time_sec
circle_y += speed_x * time_sec
ai_speed = speed_bar * time_sec
|
cs |
1번 라인에서 먼저 공의 x, y 이동 속도, bar의 이동 속도를 설정해주도록 하겠습니다.
2번 라인에서 이전에는 tick()이라는 함수를 통해서 fps를 설정만 하였지만, 이번에는 프레임을 변수에 담고 해당 값을 이용하여 '공'과 '바'의 속도를 조절하도록 합니다.
3번 라인에는 해당 프레임 값을 1000으로 나누어 세컨드 값으로 변환 해줍니다.
30fps를 초로 계산을 하면, 0.033 정도의 값을 가집니다.
참고) clock.tick()에 따른 time_sec 확인 방법
[코드]
count=0
while count < 10:
time_passed = clock.tick(30) # 단위는 ms이며, 이전호출된 시간 이후로 흐른 시간이 return된다
time_sec = time_passed / 1000.0
print(time_sec)
count+=1
[실행결과]
0.034
0.034
0.034
0.034
0.033
0.033
0.034
...(생략)...
5~7번 라인은 x, y와 bar의 이동 속도에 앞서 구한 프레임을 활용한 시간 초를 곱하여 프레임에 따라 자연스럽게 해당 이동속도로 이동하게 해줍니다.
5. 구현 순서
구현 순서는 다음과 같습니다.
Step1 | bar와 circle 설정하기 |
Step2 | 키보드 이벤트 처리 |
Step3 | 공의 접촉 처리 및 출력 |
Step1) bar와 circle 설정하기
먼저 게임에서 활용되는 bar와 circle에 대해 설정해주도록 하겠습니다.
# 4. pygame 무한루프
def runGame():
global done
## 게임판 크기
screen_width = size[0]
screen_height = size[1]
## 탁구채 크기 (width, height)
bar_width = 9
bar_height = 50
## 탁구채의 시작점 (x,y), 좌측 맨끝 중앙
bar_x = bar_start_x = 0
bar_y = bar_start_y = (screen_height - bar_height) / 2
## 탁구공 크기 (반지름)
circle_radius = 9
circle_diameter = circle_radius * 2
## 탁구공 시작점 (x, y), 우측 맨끝 중앙
circle_x = circle_start_x = screen_width - circle_diameter ## 원의 지름 만큼 빼기
circle_y = circle_start_y = (screen_width - circle_diameter) / 2
bar_move = 0
speed_x, speed_y, speed_bar = -screen_width / 1.28, screen_height / 1.92, screen_height * 1.2
#게임루프 시작
# ...
먼저 각각 bar와 circle의 기본 사이즈, 속도등을 설정하도록 하겠습니다.
9~15번 라인은 bar에 대한 설정입니다.
각각 변수에 대한 내용은 다음과 같습니다.
- bar_width: 탁구채의 가로폭
- bar_height : 탁구채의 세로폭
- bar_start_x, bar_start_y : 탁구채의 시작 (x,y) 지점
17~23번 라인은 circle에 대한 설정입니다.
각각 변수에 대한 내용은 다음과 같습니다.
- circle_diameter : 탁구공의 지름
- circle_radius : 탁구공의 반지름
- circle_start_x, circle_start_y : 탁구공의 시작 (x,y) 지점
26라인은 앞서 학습한 circle과 bar의 속도를 설정 합니다.
Step2) 키보드 이벤트 처리
이번에는 앞서 작성한 코드를 기반으로 키보드 이벤트 처리를 통해 bar를 이동시키도록 하겠습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
while True:
time_passed = clock.tick(30)
time_sec = time_passed / 1000.0
screen.fill(BLACK)
circle_x += speed_x * time_sec
circle_y += speed_y * time_sec
ai_speed = speed_bar * time_sec
for event in pygame.event.get():
if event.type == pygame.QUIT:
break
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP:
bar1_move = -ai_speed
elif event.key == pygame.K_DOWN:
bar1_move = ai_speed
elif event.type == pygame.KEYUP:
if event.key == pygame.K_UP:
bar1_move = 0
elif event.key == pygame.K_DOWN:
bar1_move = 0
|
cs |
먼저 앞서 학습한 fps 기준값을 통한 이동속도 설정을 한뒤 키보드 처리를 하도록 하겠습니다.
10~22번 코드는 이전 '폭탄 피하기' 게임과 동일한 방식으로 적용 됩니다.
다만, 한 가지 다른점은 앞선 게임에서는 변화 속도값을 직접 지정했지만, 이번에는 앞서 선언한 ai_speed를 이용한다는 차이가 있습니다.
Step3) 공의 접촉 처리 및 출력
마지막으로 공이 각 지점(벽, bar)에 접촉했을 때의 처리를 추가하도록 하겠습니다.
## 탁구채 이동
bar_y += bar_move
## 탁구채 범위 확인
if bar_y >= screen_height:
bar_y = screen_height
elif bar_y <= 0:
bar_y = 0
## 탁구공 범위 확인
## 1) 진행 방향을 바꾸는 행위
## 2) 게임이 종료되는 행위
if circle_x < bar_width: ## bar에 닿았을 때
if circle_y >= bar_y - circle_radius and circle_y <= bar_y + bar_height + circle_radius:
circle_x = bar_width
speed_x = -speed_x
if circle_x < -circle_radius: ## bar에 닿지 않고 좌측 벽면에 닿았을 때, 게임 종료 및 초기화
circle_x, circle_y = circle_start_x, circle_start_y
bar_x, bar_y = bar_start_x, bar_start_y
elif circle_x > screen_width - circle_diameter: ## 우측 벽면에 닿았을 때
speed_x = -speed_x
if circle_y <= 0: ## 위측 벽면에 닿았을때
speed_y = -speed_y
circle_y = 0
elif circle_y >= screen_height - circle_diameter: ## 아래 벽면에 닿았을때
speed_y = -speed_y
circle_y = screen_height - circle_diameter
먼저 탁구채의 이동입니다.
2번 라인에서 bar의 y축 좌표를 키보드 입력에 따라 이동시키도록 하겠습니다.
5~8번 라인은 bar가 만약 y축을 기준으로 바닥 혹은 천장 끝에 닿았을 때 더이상 이동하지 않도록 위치를 조정해주는 코드입니다.
다음은 탁구공의 이동 방향 처리입니다.
공은 순서대로 총 5가지의 처리를 해주도록 합니다.
1) 우측 벽면
2) 좌측 벽면
3) 아래측 벽면
4) 위측 벽면
5) 바에 닿았을 때
핵심 개념은 해당 지점에 도달했을 때, 공이 튕기는 방식은 입력 방향에 따른 축을 뒤집는다는 점입니다. 예를 들어 아래쪽 벽면에 부딪힌다면 x의 이동은 계속 유지한채, y의 이동을 뒤집게 되면 'V' 형태로 공이 튕기게 됩니다.따라서 해당 개념을 적용해 코드를 구현하도록 하겠습니다.
13~16번 라인(bar에 닿았을 때)
- circle_x < bar_dist_from_edge + bar_width : 공의 x 좌표가 바의 위치까지 왔을 때- circle_y >= bar1_y - circle_radius and circle_y <= bar1_y + bar_height + circle_radius : 공의 y 좌표가 bar의 y좌표 사이에 있을때-> 공을 bar 바로 앞에서, speed_x를 뒤집어 이동 방향을 우측으로 변경
17~19번 라인(bar에 닿지 않고 좌측 벽면에 닿았을 때, 게임 종료 및 초기화)
- circle_x < -circle_radius : 공의 x 좌표가 좌측 벽면에 닿을 때- > 공과 bar를 초기화 함
20~21번 라인(우측 벽면에 닿았을 때)
- circle_x > screen_width - circle_diameter : 공의 x 좌표가 우측 벽면에 닿을 때-> 우측 벽에서, speed_x를 뒤집어 이동 방향을 좌측으로 변경
22~24번 라인(위측 벽면에 닿았을 때)
- circle_y <= bar_dist_from_edge : 공의 y 좌표가 위측 벽면에 닿을 때-> 아래측 벽에서, speed_y를 뒤집에 이동 방향을 위로 변경
25~27번 라인(아래측 벽면에 닿았을 때)
- circle_y >= screen_height - circle_diameter : 공의 y 좌표가 아래측 벽면에 닿을 때
-> 위측 벽에서, speed_y를 뒤집에 이동 방향을 아래로 변경
위의 방식으로 공이 벽에 닿았을 때 처리를 한 후, 공과 bar를 화면에 출력하도록 합니다.
6. 정리
이번 장에서는 앞서 배운 pygame의 기본 구조와 게임 루프, 이벤트 처리를 기반으로 핑퐁 게임을 구현 해봤습니다.
먼저, 게임에 필요한 요소 정의를 하고 공의 흐름 이해를 통해 게임을 진행할 수 있도록 사전학습을 한 뒤 진행을 했으며,
Step 1) bar와 circle 설정하기 -> circle()을 활용한 구체 구현하기 및 기본 게임 요소 속도 및 위치 처리
Step 2) 키보드 이벤트 처리 -> 키보드 up, down을 통한 bar 이동 제어
Step 3) 공의 접촉 처리 및 출력 -> screen과 공의 x, y좌표를 기반으로 한 접촉 처리 및 이동 방향 전환(입사각, 반사각)
을 배웠습니다.
이번 장에는 pygame과 키보드 up, down과 및 위치별 접촉 처리를 배웠습니다. 해당 게임에서는 앞서 배운 내용을 기반으로 화면에 글자를 출력하여 점수 표시하기, 2번 플레이어를 생성하여 2인용 게임으로 제작하기, 공의 진행 속도 증가 등 재미를 더할 요소가 많으니 자유롭게 응용해보면 좋을 듯 합니다^^
ㅁ 참고
도움이 되셨다면, 좋아요 / 구독 버튼 눌러주세요~
저작물의 저작권은 작성자에게 있습니다.
공유는 자유롭게 하시되 댓글 남겨주세요~
상업적 용도로는 무단 사용을 금지합니다.
끝까지 읽어주셔서 감사합니다^^