- 목표 : 오락실 pang게임 만들기.
- 캐릭터는 화면 아래에 위치, 좌우로만 이동 가능
- 스페이스를 누르면 무기를 쏘아 올림
- 큰 공 1개가 나타나서 바운스
- 무기에 닿으면 공은 작은 크기 2개로 분할, 가장 작은 크기의 공은 사라짐
- 모든 공을 없애면 게임종료(성공)
- 캐릭터가 공에 닿으면 게임 종료 (실패)
- 시간 제한 99초 초과 시 게임 종료 (실패)
- FPS는 30으로 고정
초기 설정
1. 환경설정 & 프레임
import pygame
import os
#1 초기화 (반드시 필요.)
pygame.init()
# 화면 크기 설정
screen_width = 640 # 가로 크기
screen_height = 480 # 세로 크기
screen = pygame.display.set_mode((screen_width, screen_height))
# 화면 타이틀 설정
pygame.display.set_caption("rusharp Pang") # 게임 이름
# 지금 수행하고 있는 파일의 위치를 반환.
current_path = os.path.dirname(__file__)
print(current_path)
# 이미지 폴더 위치 반환
image_path = os.path.join(current_path,"image")
print(image_path)
# 사라질 무기와 공 정보 저장 변수
weapon_to_remove = -1
ball_to_remove = -1
- os.path.dirname(__file__) : 현재 수행중인 파일 위치를 반환
- os.path.join(a,b) : a\b 를 반환함.
- weapon_to_remove/ball_to_remove : 삭제할 weapons, balls index
2. 배경
# background.png파일 가져오기
backgrouond = pygame.image.load(os.path.join(image_path, "background.png"))
stage = pygame.image.load(os.path.join(image_path,"stage.png"))
stage_size = stage.get_rect().size
# stage_size[0] : 가로 / stage_size[1] : 세로
stage_height = stage_size[1]
3. 캐릭터
# 캐릭터 만들기
character = pygame.image.load(os.path.join(image_path, "character.png"))
# character_size = character.get_rect().size
character_rect = character.get_rect()
character_width = character_rect.size[0]
character_height = character_rect.size[1]
character_x =(screen_width - character_width)/ 2
character_y = screen_height - character_height - stage_height
# 캐릭터 속도/이동
character_speed = 5
to_right = 0
to_left = 0
- character_x/character_y : 캐릭터 초기 위치 및 이동할때마다 rect 저장.
- character_width/character_height : 캐릭 이동 및 무기 위치를 위한 변수.
4. 무기
# 무기 만들기
weapon = pygame.image.load(os.path.join(image_path, "weapon.png"))
weapon_width = weapon.get_rect().size[0]
weapon_height = weapon.get_rect().size[1]
# 여러번 발사를 위한 리스트 변수
weapons = []
weapon_speed = 10
- weapon_width/weapon_height : 무기 위치 지정을 위한 변수.
- weapons : 스페이스를 누를때마다 무기를 여러번 발사하게끔 list화
5. 공
# 공 만들기 (크기가 4종류)
ball_image = [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]
# 공 리스트
# 최초 발생하는 큰 공 추가, dictionary 타입
balls = []
balls.append({"pos_x" : 50, #공의 x좌표
"pos_y" : 50, #공의 y좌표
"img_idx" : 0, #공의 이미지 인덱스 (ball_image[n])
"to_x" : 3, #공의 x축 이동방향 (-3 : 좌 / 3 : 우)
"to_y" : -6, # 공의 y축 이동방향
"init_spe_y": ball_speed_y[0]}) # y축의 최초 속도
- ball_image : 공 크기에 맞는 이미지 불러오기
- balls : 공을 생성하기 위한 dictionary list
ㄴ pos_x/pos_y : 공의 x,y좌표
ㄴ to_x / to_y : 공의 x,y축 이동 방향
ㄴ img_idx : 공의 이미지 인덱스, ball_imge의 index 값
ㄴ init_spe_y : 공의 최초 속도, y축 처리함.
6. 시간 및 메시지(텍스트 처리)
# 게임종료 조건 처리 (시간)
# Font정의
clock = pygame.time.Clock()
game_font = pygame.font.Font(None, 40)
total_time = 100
start_ticks = pygame.time.get_ticks() # 시작시간정의
# 게임 종료 메시지
# Time out : 시간 종료.
# Mission Complete : 공을 모두 제거
# Game Over : 공에 맞음
game_result = "Game Over"
- clock = pygame.time.Clock() : 시간추적개체
- pygame.time.get_ticks() : pygame.init() 호출 후 밀리초를 반환
게임 실행
1. FPS설정 & 키보드 입력 작동
running = True # 게임이 진행중인가?
while running:
dt = clock.tick(30)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
to_left -=character_speed
elif event.key == pygame.K_RIGHT:
to_right +=character_speed
# 무기 발사
if event.key == pygame.K_SPACE:
weapon_x = character_x +(character_width-weapon_width)/2
weapon_y = character_y
weapons.append([weapon_x, weapon_y])
elif event.type == pygame.KEYUP:
to_left = 0
to_right = 0
- while running : 게임이 바로 종료되지 않도록 설정함.
- dt = clock.tick(30) : fps를 30으로 설정함.
- event in pygame.event.get() : 입력된 이벤트가 있으면 event에 삽입
- event.type ㄴ pygame.QUIT : 게임 종료(닫기) 버튼
ㄴ pygame.KEYDOWN : 키보드 눌림
ㄴ pygame.KEYUP : 키보드에서 손을 뗌
- event.key
ㄴ pygame.K_RIGHT / LEFT / SPACE : 각각 우, 좌, 스페이스 키
ㄴ to_left 와 to_right 를 나눈 이유는, 캐릭터 이동할 때
방향키를 빠르게 누르면 캐릭터가 아예 멈춰버리는 현상을 방지
- weapons.append([weapon_x, weapon_y]) : space를 누를때마다 무기 추가
2. 캐릭터 위치 세팅
character_x =character_x + to_right + to_left
if character_x < 0:
character_x = 0
elif character_x > screen_width-character_width:
character_x = screen_width-character_width
# 무기 위치 조절 (y를 점점 위로 올림)
weapons = [ [w[0], w[1] -weapon_speed] for w in weapons]
# 천장에 닿은 무기 없애기
# weapons = []
# for w in weapons:
# if w[1] > 0:
# weapons.append = [w[0], w[1]]
# 즉, weaponse 의 높이가 0보다 클때만 weapons 값을 추가함.
weapons = [ [w[0], w[1]] for w in weapons if w[1] > 0]
- weapons = [ [w[0], w[1] -weapon_speed] for w in weapons]
ㄴ weapons 의 세로 위치를 점점 줄임으로서 점점 위로 발사되도록 함.
- weapons = [ [w[0], w[1]] for w in weapons if w[1] > 0] : 아래 코드 한줄화
weapons = []
for w in weapons:
if w[1] > 0:
weapons.append = [w[0], w[1]]
3. 충돌 처리
# 공 위치 정의
# enumerate : ball list 의 것들을 가져와서 index 번호, 값을 가져옴
for ball_idx, ball_val in enumerate(balls):
# ball 가로/세로 위치/ 공의 이미지 인덱스 지정
# 초기값의 경우, 50, 50, 0 이다.
ball_pos_x = ball_val["pos_x"]
ball_pos_y = ball_val["pos_y"]
ball_img_idx = ball_val["img_idx"]
ball_size = ball_image[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"] *= -1
# 공 세로가 바닥에 닿는 경우, 속도 초기화
if ball_pos_y > screen_height-ball_height-stage_height:
ball_val["to_y"] = ball_val["init_spe_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 idx , list in enumerate(LIST): LIST의 번호, 값을 각각 idx, list에 받아옴
- 공 가로가 화면 좌우밖으로 나가는 경우,
공의 x축 이동방향에 -1을 곱한 뒤 x좌표에서 더해 방향을 변경.
- 공 세로가 스테이지 바닥에 닿는 경우, 속도를 초기화함.
- 공의 y축 이동방향에서 0.5 를 더해 음수 > 양수로 만든 뒤
y좌표에 더해 공이 포물선을 이루도록 만듬.
4. 공과 캐릭터가 충돌하는 케이스 (”Game Over”)
# 공과 캐릭터가 충돌하는 케이스
character_rect.x = character_x
character_rect.y = character_y
for ball_idx, ball_val in enumerate(balls):
# 공 쪼개기 시작값을 위한 위치 받아오기
ball_pos_x = ball_val["pos_x"]
ball_pos_y = ball_val["pos_y"]
# 공 rect 정보 업데이트
ball_rect = ball_image[ball_val["img_idx"]].get_rect()
ball_rect.x = ball_val["pos_x"]
ball_rect.y = ball_val["pos_y"]
ball_img_idx = ball_val["img_idx"]
if ball_rect.colliderect(character_rect):
print("공과 사용자가 충돌하였습니다.")
running = False
break
- 캐릭터와 공의 rect 값에 캐릭터의 현재 x, y 값을 입력함.
- 공.colliderect(캐릭터) 가 True 일 때 게임 종료.
5. 공과 무기터가 충돌하는 케이스 (공 쪼개기)
for ball_idx, ball_val in enumerate(balls):
# 공 쪼개기 시작값을 위한 위치 받아오기
ball_pos_x = ball_val["pos_x"]
ball_pos_y = ball_val["pos_y"]
# 공 rect 정보 업데이트
ball_rect = ball_image[ball_val["img_idx"]].get_rect()
ball_rect.x = ball_val["pos_x"]
ball_rect.y = ball_val["pos_y"]
ball_img_idx = ball_val["img_idx"]
# 공과 무기가 충돌하는 케이스
for weapon_idx, weapon_val in enumerate(weapons):
weapon_rect = weapon.get_rect()
weapon_rect.left = weapon_val[0]
weapon_rect.top = weapon_val[1]
if weapon_rect.colliderect(ball_rect):
# 충돌하면 무기와 공을 제거해야함.
# 없애기 위한 공과 무기 index 받아옴.
weapon_to_remove = weapon_idx
ball_to_remove = ball_idx
# 공 2개로 만들어서 하나는 왼쪽/하나는 오른쪽으로 만듬
# 가장 작은 크기의 공이 아니라면 다음 단계의 공으로 나누어주기.
if ball_img_idx < 3:
# 현재 공 크기의 정보를 가지고옴.
ball_width = ball_rect.size[0]
ball_height = ball_rect.size[1]
# 나눠진 공 정보
small_ball_rect = ball_image[ball_img_idx+1].get_rect()
small_ball_width = small_ball_rect.size[0]
small_ball_height = small_ball_rect.size[1]
# 왼쪽으로 튕겨나가는 공
balls.append({"pos_x" : ball_pos_x + (ball_width - small_ball_width)/2, #공의 x좌표
"pos_y" : ball_pos_y + (ball_height - small_ball_height)/2, #공의 y좌표
"img_idx" : ball_img_idx+1, #공의 이미지 인덱스 (ball_image[n])
"to_x" : -3, #공의 x축 이동방향 (-3 : 좌 / 3 : 우)
"to_y" : -6, # 공의 y축 이동방향
"init_spe_y": ball_speed_y[ball_img_idx+1]}) # y축의 최초 속도})
balls.append({"pos_x" : ball_pos_x + (ball_width - small_ball_width)/2, #공의 x좌표
"pos_y" : ball_pos_y + (ball_height - small_ball_height)/2, #공의 y좌표
"img_idx" : ball_img_idx+1, #공의 이미지 인덱스 (ball_image[n])
"to_x" : 3, #공의 x축 이동방향 (-3 : 좌 / 3 : 우)
"to_y" : -6, # 공의 y축 이동방향
"init_spe_y": ball_speed_y[ball_img_idx+1]}) # y축의 최초 속도})
# 해당부분은 `for weapon_idx~` 부분만 탈출함
'''2중 탈출을 하고싶을 때
for문1 :
for문2 :
if 조건 : 액션
break
else: # 조건이 맞지 않으면 continue를 진행하여 for1을 작동함.
continue
break
이렇게하면 break를 만나면 for문1도 break
'''
break
else : continue # for문이 진행중이면 계속 진행
break
- weapon 객체 가져옴 → ball 객체 가져옴
ㄴ 만약 weapon과 ball이 충돌하면 삭제할 무기/공 인덱스 저장.
ㄴ if ball_img_idx < 3 : ball이 가장 작은 크기의 공이 아닌지 확인
ㄴ 현재 공의 크기 및 가로/세로 위치를 가져온 뒤 나눠진 공 위치 지정.
ㄴ 왼쪽으로 튕기는 공 / 오른쪽으로 튕기는 공을 각각 추가함.
- for-else : for문이 break 되지 않고 끝까지 실행되면 else문이 작동됨.
즉, 하단 케이스에서 for문2가 break되면 else로 넘어가지 않고 for문1 break됨.
for문1 :
for문2 :
if 조건 : 액션
break
else: # 조건이 맞지 않으면 continue를 진행하여 for1을 작동함.
continue
break
6. 충돌한 공 and 무기 없애기
# 충돌한 공 or 무기 없애기
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
- ball_to_remove / weapon_to_remove = 없애야 할 공, 무기 인덱스
- del list[n] : n번째 리스트 값을 삭제함.
텍스트 지정 & 화면 출력
1. 공을 전부 다 제거한 케이스
# 공이 더이상 없을 때(balls 크기가 0일 때)
if len(balls) == 0:
game_result = "Mission Complete"
running = False
- del balls[ball_to_remove] 을 통해 공이 더이상 없으면 Mission Complete
2. 경과 시간 계산 및 종료.
# 경과 시간 계산
elapsed_time = (pygame.time.get_ticks() - start_ticks)/1000
timer = game_font.render("Time : {}".
format(int(total_time - elapsed_time)), True, (0,0,0))
if total_time-elapsed_time <= 0:
game_result = "Time Over"
running = False
- 폰트 객체.render(텍스트,antialias, color) 를 통해 surface 객체로 변환
- 총 시간 - 소요시간이 0보다 작거나 같으면 Time Over
3. 화면 출력
# 화면 출력
screen.blit(backgrouond,(0,0))
for weapon_x, weapon_y in weapons:
screen.blit(weapon, (weapon_x, weapon_y))
for idx, val in enumerate(balls):
ball_x = val["pos_x"]
ball_y = val["pos_y"]
ball_img_idx = val["img_idx"]
screen.blit(ball_image[ball_img_idx], (ball_x, ball_y))
screen.blit(stage, (0, screen_height-stage_height))
screen.blit(character, (character_x, character_y))
screen.blit(timer,(10,10))
pygame.display.update()
- weapons 리스트의 값을 전부 출력
- balls 의 리스트의 모든 값을 출력
- 출력 순서 배경 > 무기 > 공 > stage > 캐릭터 > 타이머
게임 종료
1. 게임이 종료되었을 때 화면 노출
msg = game_font.render(game_result,True, (255,255,0))
# 화면 기준 가로/세로 중앙 위치 반환
msg_rect = msg.get_rect(center = (screen_width/2, screen_height/2))
# 메시지 출력 후 2초 딜레이 > 종료
screen.blit(msg, msg_rect)
pygame.display.update()
pygame.time.delay(2000)
pygame.quit()
- rect 변수 = 객체.get_rect(center = (x,y) ) : 객체의 중앙값을 x, y로 지정한다.
- 게임 종료 메시지를 출력 후 2초 딜레이 후 종료.