본문 바로가기

내일배움캠프/사전과제

[내일배움캠프] - 사전과제2 : 파이썬으로 게임 만들기

두번째 파이썬 과제.

아직 영타가 미숙해서 오래걸리더라도 일부러 복붙을 최소화하고 직접 타이핑했다.

직접 타이핑하느라 오래걸림 -> 오타를 많이 냄 -> 오류 찾는데 오래걸림 -> 다 만드는데 오래걸림

ㅠㅠ

 

[강의] - 파이썬 코딩 무료 강의(활용편1) :: pygame | 나도코딩

https://www.youtube.com/watch?v=Dkx8Pl6QKW0

'파이썬으로 만든 공 쪼개기 게임'

'''
Project) 오락실 Pang 게임 만들기

[게임 조건]
1. 캐릭터는 화면 아래에 위치, 좌우로만 이동가능
2. 스페이스를 누르면 무기를 쏘아 올림
3. 큰 콩 1개가 나타나서 바운스
4. 무기에 닿으면 공은 작은 크기 2개로 분할, 가장 작은 크기의 공은 사라짐
5. 모든 공을 없애면 게임 종료 (성공)
6. 캐릭터는 공에 닿으면 게임 종료(실패
7. 시간 제한 99초 초과 시 게임 종료 (실패)
8. FPS 는 30 으로 고정 (필요시 speed 값을 조정)

[게임 이미지]
1. 배경 : 640 * 480 background.png
2. 무대 : 640 * 50 stage.png
3. 캐릭터 : 33 * 60 character.png
4. 무기 : 20 * 430 weapon.png
5. 공 : 160 * 160, 80 * 80, 40 * 40, 20* 20 balloon1.png ~ balloon4.png
'''

import pygame
import os

####################################################################
# 기본 초기화 (반드시 해야 하는 것들)

pygame.init()

# 화면 크기 설정
screen_width = 640
screen_height = 480
screen = pygame.display.set_mode((screen_width, screen_height))

# 화면 타이틀 설정
pygame.display.set_caption("공 쪼개기") # 게임 이름

# FPS
clock = pygame.time.Clock()

####################################################################

# 1. 사용자 게임 초기화 (배경 화면, 게임 이미지, 좌표, 폰트 등)

current_path = os.path.dirname(__file__) # 현재 파일의 위치 반환
image_path = os.path.join(current_path, "images") # images 폴더 위치 반환

# 배경 만들기
background = 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_height = stage_size[1]

# 캐릭터 만들기
character = pygame.image.load(os.path.join(image_path, "character.png"))
character_size = character.get_rect().size
character_width = character_size[0]
character_height = character_size[1]
character_x_pos = (screen_width / 2) - (character_width /2)
character_y_pos = screen_height - stage_height - character_height

# 캐릭터 이동 방향
character_to_x = 0

# 캐릭터 이동 속도
character_speed = 0.5

# 무기 만들기
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 = 15

# 공 만들기
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, -16, -14, -12]

# 공들
balls = []

# 최초 발생하는 첫번째 큰 공 추가
balls.append({
    "x_pos": 50, # 공의 x좌표
    "y_pos": 50, # 공의 y좌표
    "img_idx": 0, # 공의 이미지 인덱스(balloon 종류)
    "to_x": 2, # x축 이동 방향
    "to_y": -4, # y축 이동 방향
    "init_spd_y": ball_speed_y[0] # y 최초 속도
})

# 사라질 무기, 공 정보 저장 변수
weapon_to_remove = -1
ball_to_remove = -1

# 폰트 정의
game_font = pygame.font.Font(None, 40)

# 시간 정의
total_time = 100
start_ticks = pygame.time.get_ticks()

# 게임 종료 메세지 : Time Over / Mission Complate / Game over
game_result = "Game Over"

running = True
while running:
    dt = clock.tick(50)

    # 2. 이벤트 처리 (키보드, 마우스 등)

    for event in pygame.event.get(): # 어떤 이벤트가 발생했는가?
        if event.type == pygame.QUIT: # 창이 닫히는 이벤트 발생
            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:
                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 or event.key == pygame.K_RIGHT:
                character_to_x = 0


    # 3. 게임 캐릭터 위치 정의

    # 위치 변화
    character_x_pos += character_to_x * dt

    # 경계값
    if character_x_pos < 0:
        character_x_pos = 0
    elif character_x_pos > screen_width - character_width:
        character_x_pos = screen_width - character_width

    # 무기 위치 조정 : weapon의 x값은 고정, y값은 speed값만큼 작아지게(위로) 함
    weapons = [ [w[0], w[1] - weapon_speed] for w in weapons ]

    # 천장에 닿은 무기 없애기
    weapons = [ [w[0], w[1]] for w in weapons if w[1] > 0 ]

    # 공 위치 정의
    for idx, val in enumerate(balls): # idx: 인덱스값 / val: i번째 딕셔너리
        ball_x_pos = val["x_pos"]
        ball_y_pos = val["y_pos"]
        ball_img_idx = 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_x_pos < 0 or ball_x_pos > screen_width - ball_width:
            val["to_x"] = val["to_x"] * -1

        # 경계값, 속도 변환 : 세로 위치
        if ball_y_pos >= screen_height - stage_height - ball_height: # 공이 바닥에 닿았을 때, 최초 속도(-)로 올라감
            val["to_y"] = val["init_spd_y"]
        else: # 그외 경우에는 올라가는 속도가 점점 줄어듦
            val["to_y"] += 0.5

        val["x_pos"] += val["to_x"]
        val["y_pos"] += val["to_y"]

    # 4. 충돌 처리

    # 캐릭터 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): # idx: 인덱스값 / val: i번째 딕셔너리
        ball_x_pos = ball_val["x_pos"]
        ball_y_pos = ball_val["y_pos"]
        ball_img_idx = ball_val["img_idx"]

        # 공 rect 정보 업데이트
        ball_rect = ball_images[ball_img_idx].get_rect()
        ball_rect.left = ball_x_pos
        ball_rect.top = ball_y_pos

        # 공과 캐릭터 충돌 체크
        if character_rect.colliderect(ball_rect):
            running = False
            break

        # 공과 무기들 충돌 처리
        for weapon_idx, weapon_val in enumerate(weapons):
            weapon_x_pos = weapon_val[0]
            weapon_y_pos = weapon_val[1]

            # 무기 rect 정보 업데이트
            weapon_rect = weapon.get_rect()
            weapon_rect.left = weapon_x_pos
            weapon_rect.top = weapon_y_pos

            # 공과 무기들 충돌 체크
            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]
                    
                    # 나눠진 공 정보
                    small_ball_rect = ball_images[ball_img_idx + 1].get_rect()
                    small_ball_width = small_ball_rect.size[0]
                    small_ball_height = small_ball_rect.size[1]

                    # 왼쪽 공
                    balls.append({
                        "x_pos": ball_x_pos + (ball_width / 2) - (small_ball_width / 2), # 공의 x좌표
                        "y_pos": ball_y_pos + (ball_height / 2) - (small_ball_height / 2), # 공의 y좌표
                        "img_idx": ball_img_idx + 1, # 공의 이미지 인덱스(balloon 종류)
                        "to_x": -2, # x축 이동 방향
                        "to_y": -4, # y축 이동 방향
                        "init_spd_y": ball_speed_y[ball_img_idx + 1] # y 최초 속도
                    })

                    # 오른쪽 공
                    balls.append({
                        "x_pos": ball_x_pos + (ball_width / 2) - (small_ball_width / 2), # 공의 x좌표
                        "y_pos": ball_y_pos + (ball_height / 2) - (small_ball_height / 2), # 공의 y좌표
                        "img_idx": ball_img_idx + 1, # 공의 이미지 인덱스(balloon 종류)
                        "to_x": 2, # x축 이동 방향
                        "to_y": -4, # y축 이동 방향
                        "init_spd_y": ball_speed_y[ball_img_idx + 1] # y 최초 속도
                    })
                break
        else: # 이중 for문을 탈출하기 위한 장치
            continue
        break            

    # 충돌된 공 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

    # 모든 공을 없앤 경우 게임 종료
    if len(balls) == 0:
        game_result = "Mission Complate"
        running = False

    # 5. 화면에 그리기

    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_x_pos = val["x_pos"]
        ball_y_pos = val["y_pos"]
        ball_img_idx = val["img_idx"]
        screen.blit(ball_images[ball_img_idx], (ball_x_pos, ball_y_pos))

    screen.blit(stage, (0, screen_height - stage_height))
    screen.blit(character, (character_x_pos, character_y_pos))

    # 경과 시간 계산
    elapsed_time = (pygame.time.get_ticks() - start_ticks) / 1000 # ms => s
    timer = game_font.render("Time : {}".format(int(total_time - elapsed_time)), True, (255, 255, 255))
    screen.blit(timer, (10, 10))

    # 시간 초과했을 경우
    if total_time - elapsed_time <= 0:
        game_result = "Time Over"
        running = False

    pygame.display.update() # 게임화면 계속 그리기

msg = game_font.render(game_result, True, (255, 255, 0))
msg_rect = msg.get_rect(center=(int(screen_width / 2), int(screen_height / 2)))
screen.blit(msg, msg_rect)

pygame.display.update() # 다시 그리기

# 2초 대기
pygame.time.delay(2000)

# pygame 종료
pygame.quit()