파이썬/파이썬 활용
파이썬 활용 05 - 오락실 게임 'PANG' 만들기 (위치/무기/공/이벤트)
H-V
2021. 11. 2. 20:37
유투버 '나도코딩'님 강의 참조
01 캐릭터 위치
# 캐릭터 움직임
character_to_x = 0 #좌우니까 x만
# 캐릭터 이동 속도
character_speed = 5
#2. 이벤트 루프
running = True #게임 진행 여부
while running:
dt = clock.tick(60) # 게임 화면의 초당 프레임 수를 설정
#3. 이벤트 처리
for event in pygame.event.get(): #event.get()을 통해 사용자의 움직임을 받음
if event.type == pygame.QUIT: #창의 x버튼
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
character_to_x -= character_speed
elif event.key == pygame.K_RIGHT:
character_to_x += character_speed
elif event.key == pygame.K_SPACE:
pass
if event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT:
character_to_x = 0
#4. 게임 캐릭터 위치 (가로/세로 경계값)
character_x_pos += character_to_x #캐릭터의 위치는 키보드로 움직인 만큼의 위치
if character_x_pos < 0:
character_x_pos = 0
elif character_x_pos > screen_width - character_width:
character_x_pos = screen_width - character_width
02 무기 만들기
# 무기
weapon = pygame.image.load(os.path.join(image_path, "weapon.png"))
weapon_size = weapon.get_rect().size
weapon_width = weapon_size[0]
# 무기 발수 (한번에 여러발 가능)
weapons = []
#무기 이동 속도
weapon_speed = 10
# 무기 위치
weapons = [[w[0], w[1] - weapon_speed] for w in weapons] #무기 위치를 올림
# 무기가 천장에 닿으면 사라짐
weapons = [[w[0], w[1]] for w in weapons if w[1] > 0]
- 무기를 쏘면 무기가 위로 쭉 올라가야하므로 weapons에 담긴 무기의 x,y좌표를 들고와서 x는 그대로 가지만 y의 높이는 스피드에 따라 줄어들면서 위로 올라 가게 됨
- y가 천장에 닿게되면 사라지게 됨
03 공 만들기
# 공 (4개를 각각 따로 처리)
ball_images = [
pygame.image.load(os.path.join(image_path, "balloon1.png")),
pygame.image.load(os.path.join(image_path, "balloon2.png")),
pygame.image.load(os.path.join(image_path, "balloon3.png")),
pygame.image.load(os.path.join(image_path, "balloon4.png")),
]
# 공 속도 (크기에 따라 속도가 각기 다름)
ball_speed_y = [-18, -15, -12, -9] #index 0(공1),1(공2),2(공3),3(공4)
# 공 정보
balls = []
#최초 발생 큰공 추가
balls.append({
"pos_x" : 50, #공 x좌표
"pos_y" : 50, #공 y좌표
"img_idx" : 0, # 공의 이미지 인덱스
"to_x" : 3, #공 x축 이동 방향, -3 = 왼쪽, +3 = 오른쪽
"to_y" : -6, #공 y축 이동 방향
"init_speed_y" : ball_speed_y[0] #y 최초 속도
})
- 공 튕기기
#2. 이벤트 루프
running = True #게임 진행 여부
while running:
dt = clock.tick(60) # 게임 화면의 초당 프레임 수를 설정
....
#공 위치 정의
for ball_idx, ball_val in enumerate(balls):
ball_pos_x = ball_val["pos_x"]
ball_pos_y = ball_val["pos_y"]
ball_img_idx = ball_val["img_idx"]
ball_size = ball_images[ball_img_idx].get_rect().size
ball_width = ball_size[0]
ball_height = ball_size[1]
#공이 가로 화면 끝에 맞으면 다시 돌아 오도록
if ball_pos_x < 0 or ball_pos_x > screen_width - ball_width:
ball_val["to_x"] = ball_val["to_x"] * -1
#공이 세로(스테이지에 닿을 때)에 맞으면 다시 튕기도록
if ball_pos_y >= screen_height - stage_height - ball_height:
ball_val["to_y"] = ball_val["init_speed_y"]
else: #공이 다시 튕긴 후 속도가 증가
ball_val["to_y"] += 0.5
ball_val["pos_x"] += ball_val["to_x"]
ball_val["pos_y"] += ball_val["to_y"]
- for ball_idx, ball_val in enumerate(balls): 를 하게되면 인덱스 위치와 인덱스의 값을 받아온다
- 포문을 돌면서 공의 x,y 좌표와 이미지를 'balls.append'를 통해서 선언했던 변수들로 채워준다
- 공의 size/width/height은 이전의 연습과 똑같이 사이즈를 구하는 용도
- 공의 x축이 가로 맨끝의 '0'보다 작거나 (즉 화면을 완전히 넘어가거나) 화면(640) - 공(160) 의 크기보다 작아지면 공의 위치를 다시 반대방향으로 바꿈 (즉 'to_x' = 3 * 1 ... 로 튕겨서 반대로 감)
- 세로의 개념도 똑같음. 공이 이미 최초값이 50인데 이 y축(50)이 화면(480) - 스테이지(50) - 공(160)보다 작아지면(공이 스테이지에 닿는 위치) 처음 속도 '-18'의 속도로 튕기게 됨
- 공이 스테이지에 안닿으면 'to_y = -6'에서 +0.5 하면서 점점 공이 땅으로 빨리 떨어지게 됨(최초 속도가 '-' 이므로 +0.5씩 하게되면 포물선을 그리면서 떨어지게 됨)
- 마지막으로 x와y축위치에서 'to_x'/'to_y'를 각각 더해주면 "+/-" 가 계속 되면서 좌우로 움직이게 됨
#6. 화면에 그리기 (.blit)
screen.blit(background, (0,0))
for weapon_x_pos, weapon_y_pos in weapons:
screen.blit(weapon, (weapon_x_pos, weapon_y_pos))
for idx, val in enumerate(balls):
ball_pos_x = val["pos_x"]
ball_pos_y = val["pos_y"]
ball_img_idx = val["img_idx"]
screen.blit(ball_images[ball_img_idx], (ball_pos_x, ball_pos_y))
....
- 그리는 위치는 백그라운드→ 무기 → 다음 순으로 그린다. (스테이지/캐릭터와 겹치지 않기 위해)
- 그려지는 위치는 최초의 선언한 값들로 x,y,이미지를 채우면 된다.
(screen.blit(이미지이름, 그려지길 원하는 좌표값)
04 충돌
1) 공과 캐릭터가 충돌 시 게임 종료
#5. 충돌 처리
# 캐릭터 rect 정보 업데이트
character_rect = character.get_rect()
character_rect.left = character_x_pos
character_rect.top = character_y_pos
for ball_idx, ball_val in enumerate(balls):
ball_pos_x = ball_val["pos_x"]
ball_pos_y = ball_val["pos_y"]
ball_img_idx = ball_val["img_idx"]
#공 rect 정보 업데이트
ball_rect = ball_images[ball_img_idx].get_rect()
ball_rect.left = ball_pos_x
ball_rect.top = ball_pos_y
#충돌 체크
if character_rect.colliderect(ball_rect):
running = False
break
2) 공과 무기 충돌 처리
#사라질 무기, 공 정보
weapon_to_remove = -1
ball_to_remove = -1
#2. 이벤트 루프
running = True #게임 진행 여부
while running:
....
#5. 충돌 처리
# 캐릭터 rect 정보 업데이트
character_rect = character.get_rect()
character_rect.left = character_x_pos
character_rect.top = character_y_pos
for ball_idx, ball_val in enumerate(balls):
ball_pos_x = ball_val["pos_x"]
ball_pos_y = ball_val["pos_y"]
ball_img_idx = ball_val["img_idx"]
#공 rect 정보 업데이트
ball_rect = ball_images[ball_img_idx].get_rect()
ball_rect.left = ball_pos_x
ball_rect.top = ball_pos_y
#충돌 체크
if character_rect.colliderect(ball_rect):
running = False
break
#공과 무기들 충돌
for weapon_idx, weapon_val in enumerate(weapons):
weapon_pos_x = weapon_val[0]
weapon_pos_y = weapon_val[1]
#무기 rect 정보 업데이트
weapon_rect = weapon.get_rect()
weapon_rect.left = weapon_pos_x
weapon_rect.top = weapon_pos_y
#충돌 체크
if weapon_rect.colliderect(ball_rect):
weapon_to_remove = weapon_idx
ball_to_remove = ball_idx
break
if ball_to_remove > -1:
del balls[ball_to_remove]
ball_to_remove = -1
if weapon_to_remove > -1:
del weapons[weapon_to_remove]
weapon_to_remove = -1
- 공과 캐릭터의 rect()정보를 불러 온 뒤 캐릭과 공이 충돌하면 게임 종료
- 무기의 rect()정보를 불러오고 무기와 공이 닿으면 공과 무기의 제거 변수 값이 -1에서 인덱스번호로 바뀜
- 바뀐 변수가 인덱스의 위치일테니 그 값을 가지고 if문을 써서 제거변수값이 -1에서 인덱스값으로 바뀌었고, 당연히 -1보다 크니 그 인덱스값을 리스트에서 제거를 하면 됨. 그리고 다시 -1로 복귀 시키고 다시 또 받아서 똑같은 처리.
3) 공 쪼개기
- 위의 연습은 공과 무기가 충돌하면 공이 단순 삭제되는 처리이다. 이제는 쪼개는 처리를 해보자
- 일단 제일 큰공은 사라지는게 맞고 그 이후 작은공들이 순서대로 나와서 쪼개지면 되는 것!
#공과 무기들 충돌
for weapon_idx, weapon_val in enumerate(weapons):
weapon_pos_x = weapon_val[0]
weapon_pos_y = weapon_val[1]
#무기 rect 정보 업데이트
weapon_rect = weapon.get_rect()
weapon_rect.left = weapon_pos_x
weapon_rect.top = weapon_pos_y
#충돌 체크
if weapon_rect.colliderect(ball_rect):
weapon_to_remove = weapon_idx
ball_to_remove = ball_idx
#가장 작은 공이 아니면 다음 단계의 공으로 나눔
if ball_img_idx < 3:
#현재 공 크기 정보
ball_width = ball_rect.size[0]
ball_height = ball_rect.size[1]
#나눠진 공 정보 (인덱스가 커질수록 공이 작아지기때문에 +1)
samll_ball_rect = ball_images[ball_img_idx+1].get_rect()
samll_ball_width = samll_ball_rect.size[0]
samll_ball_height = samll_ball_rect.size[1]
#왼쪽으로 쪼개지는 공
balls.append({
"pos_x" : ball_pos_x + (ball_width / 2) - (samll_ball_width / 2), #공 x좌표
"pos_y" : ball_pos_y + (ball_height / 2) - (samll_ball_height / 2), #공 y좌표
"img_idx" : ball_img_idx + 1, # 공의 이미지 인덱스
"to_x" : -3, #공 x축 이동 방향, -3 = 왼쪽, +3 = 오른쪽
"to_y" : -6, #공 y축 이동 방향
"init_speed_y" : ball_speed_y[ball_img_idx + 1] #y 최초 속도
})
#오른쪽으로 쪼개지는 공
balls.append({
"pos_x" : ball_pos_x + (ball_width / 2) - (samll_ball_width / 2), #공 x좌표
"pos_y" : ball_pos_y + (ball_height / 2) - (samll_ball_height / 2), #공 y좌표
"img_idx" : ball_img_idx + 1, # 공의 이미지 인덱스
"to_x" : +3, #공 x축 이동 방향, -3 = 왼쪽, +3 = 오른쪽
"to_y" : -6, #공 y축 이동 방향
"init_speed_y" : ball_speed_y[ball_img_idx + 1] #y 최초 속도
})
break
- 공과 무기가 충돌 한 후 제일 작은 공이 아니면 현재 공 크기와 그 다음으로 작은 공 rect()정보를 업데이트 함.
(다음 공들은 계속해서 인덱스에 +1을 하면 자연스럽게 리스트에서 꺼내옴) - balls[]에서 최초 공 이후 append로 쪼개지는 공들을 넣으면 됨. 왼쪽/오른쪽으로 각각 쪼개지는 공을 넣는데 쪼개질때의 좌표는 "현재 공의 좌표 + (현재 공의 크기 반) - (다음 작은 공의 크기 반)" 으로 하면 충돌했을때의 위치에서 양쪽으로 쪼개져서 작은공들이 나옴
(왼쪽 오른쪽 두개를 추가하니 양쪽으로 하나씩 떨어지는 것) - 공의 이미지는 인덱스를 추가하면 자연스럽게 들고오게되고 이동방향은 왼쪽 오른쪽 -/+로 선택하고, 속도도 똑같이 인덱스에서 기본으로 세팅되어있기때문에 인덱스꺼를 들고오면 저절로 적용이 됨
05 게임 오버
- 초기에 설정했던 조건을 충족해서 게임진행이 되도록 해야한다
1) 모든 공을 없애면 종료(성공) 2) 캐릭터는 공에 닿으면 게임 종료(실패) 3) 시간 제한 99초 초과시 게임 종료(실패)
- 시간 추가
- 조건에 따른 결과 처리
- 시간 초과 시
- 모든 공을 없앴을 때
- 공과 캐릭이 충돌하면 이미 게임이 종료되도록 세팅이 되어있기때문에 초기 값 game_result = "Game Over" 가 뜬다
→ 모든 이미지를 바꾸면!
※ 이미지같은경우는 png파일이 제일 좋다. 배경이 투명이기때문에 바로 가져다 쓸 수 있다.
이미지 배경삭제 혹은 png파일 사이즈 조절등은 구글에 그대로 구글링하면 많은 무료 사이트들이 나온다!
06 버그 수정
1) 큰공을 맞혔는데도 큰공 + 왼쪽 공이 남는 버그
- 이 현상은 현재 무기와 공이 충돌시에 포문을 완벽하게 빠져나가야하는데 빠져 나가지 못해서 생기는 현상
2) 방향키를 빠르게 바꾸면 물리는 현상
# 캐릭터 움직임
character_to_x_LEFT = 0 #좌우니까 x만
character_to_x_RIGHT = 0
#2. 이벤트 루프
running = True #게임 진행 여부
while running:
dt = clock.tick(55) # 게임 화면의 초당 프레임 수를 설정
#3. 이벤트 처리
for event in pygame.event.get(): #event.get()을 통해 사용자의 움직임을 받음
if event.type == pygame.QUIT: #창의 x버튼
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
character_to_x_LEFT -= character_speed
elif event.key == pygame.K_RIGHT:
character_to_x_RIGHT += character_speed
elif event.key == pygame.K_SPACE: # 무기 발사
weapon_x_pos = character_x_pos + (character_width / 2) - (weapon_width / 2)
weapon_y_pos = character_y_pos
weapons.append([weapon_x_pos, weapon_y_pos])
if event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT:
character_to_x_LEFT = 0
elif event.key == pygame.K_RIGHT:
character_to_x_RIGHT = 0
#4. 게임 캐릭터 위치 (가로/세로 경계값)
character_x_pos += character_to_x_LEFT + character_to_x_RIGHT #캐릭터의 위치는 키보드로 움직인 만큼의 위치
- 왼쪽 오른쪽 변수를 따로 만들어서 각각 처리해야 물리지 않고 처리 된다!