오락실 pang게임.py

몽자비루 ㅣ 2023. 3. 26. 03:01

  1. 목표 : 오락실 pang게임 만들기. 
    1. 캐릭터는 화면 아래에 위치, 좌우로만 이동 가능
    2. 스페이스를 누르면 무기를 쏘아 올림
    3. 큰 공 1개가 나타나서 바운스
    4. 무기에 닿으면 공은 작은 크기 2개로 분할, 가장 작은 크기의 공은 사라짐
    5. 모든 공을 없애면 게임종료(성공)
    6. 캐릭터가 공에 닿으면 게임 종료 (실패)
    7. 시간 제한 99초 초과 시 게임 종료 (실패)
    8. 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초 딜레이 후 종료.

'python > python_pygame' 카테고리의 다른 글

오락실 pang게임 직접 코딩해보기.py  (0) 2023.03.27
pygame 기초  (1) 2023.03.22