3 minute read

구현한 기능

  • 시작 화면에서 난이도 선택 가능
  • mongoDB를 이용해 highscore 기록 및 표현
  • fake_screen을 이용해서 전체화면이나 화면크기 전환시에도 내용물이 scale 되도록
  • 난이도에 따른 수치 조절
  • 높이 올라갈수록 난이도 점점 상승
  • 점수에 따라 배경화면 변경
  • Pause, Start 기능
  • 배경음악과 효과음 추가

최고득점을 표현하면서 여러줄을 한번에 넣는 방법을 찾아 보았다. 시간날 때 음미해 볼것

# 텍스트 여러줄을 한번에 쓰기
def blit_text(surface, text, pos, font, color=pygame.Color(WHITE)):
    words = [word.split(' ') for word in text.splitlines()]  # 2D array where each row is a list of words.
    space = font.size(' ')[0]  # The width of a space.
    max_width, max_height = surface.get_size()
    x, y = pos
    for line in words:
        for word in line:
            word_surface = font.render(word, 0, color)
            word_width, word_height = word_surface.get_size()
            if x + word_width >= max_width:
                x = pos[0]  # Reset the x.
                y += word_height  # Start on new row.
            surface.blit(word_surface, (x, y))
            x += word_width + space
        x = pos[0]  # Reset the x.
        y += word_height  # Start on new row.

화면에 blit을 하는 순서가 중요하다. 나중에 한 것이 위에 올라간다.

blit은 screen에만 할 필요가 없다.fake_screen을 만들어도 되고, image에 해도 된다.

최고점수의 db를 업데이트 할때

        db.doodles.update_one({'easy' : easy_score}, {'$set': {'easy': howManySteps}})

이런 방식으로 원하는 값을 직접 찾아도 된다.


이하 전체 코드. 코드가 너무 지저분해서 스스로도 찾기 힘들다. 깔끔한 정리 방식이 필요하다.

import pygame
import os
import random
from pymongo import MongoClient

client = MongoClient('mongodb+srv://test:sparta@cluster0.nqwfa.mongodb.net/Cluster0?retryWrites=true&w=majority')
db = client.dbsparta

high_scores = db.doodles.find({}, {'_id': False})
easy_score = high_scores[0]['easy']
hard_score = high_scores[0]['hard']

pygame.init()

# 사운드
pygame.mixer.init()
pygame.mixer.music.load('sounds/bgm.mp3')
pygame.mixer.music.set_volume(0.2)
pygame.mixer.music.play(-1)
jump_sound = pygame.mixer.Sound('sounds/jump.wav')
gameover_sound = pygame.mixer.Sound('sounds/gameover.wav')

BG = (50, 50, 50)
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)

screen_width = 800
screen_height = 800
screen = pygame.display.set_mode((screen_width, screen_height), pygame.RESIZABLE, pygame.FULLSCREEN)  # 리사이즈, 전체화면 기능
# 화면 사이즈에 맞게 컨텐츠 확장하도록. 안그러면 픽셀이 그대로 유지됨
fake_screen = screen.copy()

pygame.display.set_caption("Jump Jump")

clock = pygame.time.Clock()

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


# 게임 오버 함수
def game_over():
    global running, howManySteps, easy_score, hard_score, difficulty

    gameover_sound.play()

    game_over = gameover_font.render(str('GAME OVER'), True, (255, 255, 255))
    game_over_text_rect = game_over.get_rect(center=(screen_width / 2, screen_height / 2))

    end_step = end_step_font.render(str(f'Your level is {howManySteps}!'), True, (213, 213, 213))
    end_step_text_rect = end_step.get_rect(center=(screen_width / 2, (screen_height / 2) + 50))

    highscore_text = highscore_end_font.render(str('HIGHSCORE!!!'), True, (255, 0, 0))
    highscore_text_rect = highscore_text.get_rect(center=(screen_width / 2, (screen_height / 2) + 100))
    if difficulty == 'easy' and howManySteps > easy_score:
        fake_screen.blit(highscore_text, highscore_text_rect)
        db.doodles.update_one({'easy' : easy_score}, {'$set': {'easy': howManySteps}})

    elif difficulty == 'hard' and howManySteps > hard_score:
        fake_screen.blit(highscore_text, highscore_text_rect)
        db.doodles.update_one({'hard' : hard_score}, {'$set': {'hard': howManySteps}})

    fake_screen.blit(game_over, game_over_text_rect)
    fake_screen.blit(end_step, end_step_text_rect)
    screen.blit(pygame.transform.scale(fake_screen, screen.get_rect().size), (0, 0))
    pygame.display.flip()

    running = False

    pygame.display.update()  # 게임 화면을 다시 그리기
    pygame.time.delay(2000)  # 종료 전 2초 대기


# 배경 만들기
background1 = pygame.image.load(os.path.join(image_path, "bg1.jpg"))
background2 = pygame.image.load(os.path.join(image_path, "bg2.jpg"))
background3 = pygame.image.load(os.path.join(image_path, "bg3.jpg"))
background4 = pygame.image.load(os.path.join(image_path, "bg4.jpg"))

# ground, land 아래로
gravity = 0.0
land_down_speed = 0

# ground 만들기
ground = pygame.image.load(os.path.join(image_path, "ground.jpg"))
ground_size = ground.get_rect().size
ground_height = ground_size[1]
ground_y_pos = screen_height - ground_height

# 캐릭터 만들기

character_image = pygame.image.load('images/squid-game.png').convert_alpha()  # 시작화면 캐릭터 - 오징어게임

character = pygame.image.load(os.path.join(image_path, "squid-game-resize.png"))
character_size = character.get_rect().size
character_width = character_size[0]
character_height = character_size[1]
character_x_pos = (screen_width - character_width) / 2
character_y_pos = screen_height - character_height - ground_height

character_to_x = 0

character_speed = 5.5
howManySteps = 0

# 캐릭터 점프
character_to_y = 0
characterOnTheLand = False  # 캐릭터가 발판 위에 있는지 판정


def get_image(sheet, width, height):
    image = pygame.Surface((width, height)).convert_alpha()
    image.blit(sheet, (0, 0), (0, 0, width, height))
    return image


land_image_source = pygame.image.load(os.path.join(image_path, "land.jpg"))


# 발판 생성
class LandToStep:
    global land_down_speed

    def __init__(self, x_pos, y_pos, land_width, land_height):
        self.land_image = get_image(land_image_source, land_width, land_height)
        self.land_width = land_width
        self.land_height = land_height
        self.x_pos = x_pos
        self.y_pos = y_pos

    def image(self):
        return self.land_image, self.land_width, self.land_height

    def pos(self):
        return [self.x_pos, self.y_pos + land_down_speed]


# 게임 시작시 나오는 발판들
lands = []
for i in range(7):
    land_x_position = random.randint(50, 700)
    var_height = random.randint(-30, 30)
    land_width = random.randint(110, 250)

    land = LandToStep(land_x_position, 750 - 80 * (i + 1) + var_height, land_width, 25)
    lands.append([land.image(), land.pos()])

# 폰트 정의
game_font = pygame.font.Font(None, 50)  # None 쓰면 기본 폰트. 40은 크기
gameover_font = pygame.font.Font(None, 70)
end_step_font = pygame.font.Font(None, 45)
font_difficulty = pygame.font.Font(None, 60)
highscore_end_font = pygame.font.Font(None, 50)
highscore_start_font = pygame.font.Font(None, 30)
pause_text = pygame.font.Font(None, 70).render('Pause', True, WHITE)
pause_text_rect = pause_text.get_rect(center=(screen_width / 2, screen_height / 2))

# 난이도 버튼
easy_button = pygame.Rect(480, 150, 200, 100)
hard_button = pygame.Rect(480, 400, 200, 100)

# 난이도에 따른 수치 조절. [easy, hard]
difficulty_nums = [
    {'character_to_y +': 0.68,
     'gravity': 0.71 + howManySteps * 0.05,
     'character_to_y': -22,
     'num_of_land': random.randint(8, 10)},
    {'character_to_y +': 0.8,
     'gravity': 0.83 + howManySteps * 0.08,
     'character_to_y': -19,
     'num_of_land': random.randint(7, 9)}
]

# 텍스트 여러줄을 한번에 쓰기
def blit_text(surface, text, pos, font, color=pygame.Color(WHITE)):
    words = [word.split(' ') for word in text.splitlines()]  # 2D array where each row is a list of words.
    space = font.size(' ')[0]  # The width of a space.
    max_width, max_height = surface.get_size()
    x, y = pos
    for line in words:
        for word in line:
            word_surface = font.render(word, 0, color)
            word_width, word_height = word_surface.get_size()
            if x + word_width >= max_width:
                x = pos[0]  # Reset the x.
                y += word_height  # Start on new row.
            surface.blit(word_surface, (x, y))
            x += word_width + space
        x = pos[0]  # Reset the x.
        y += word_height  # Start on new row.


# 게임 시작 화면
def start_display():
    global easy_score, hard_score
    fake_screen.fill(BG)

    pygame.draw.rect(fake_screen, WHITE, easy_button, width=3, border_radius=5)
    easy_text = font_difficulty.render(str('EASY'), True, WHITE)
    easy_text_rect = easy_text.get_rect(center=(580, 200))
    fake_screen.blit(easy_text, easy_text_rect)

    pygame.draw.rect(fake_screen, WHITE, hard_button, width=3, border_radius=5)
    hard_text = font_difficulty.render(str('HARD'), True, WHITE)
    hard_text_rect = hard_text.get_rect(center=(580, 450))
    fake_screen.blit(hard_text, hard_text_rect)

    fake_screen.blit(character_image, (50, 80))

    highscore_text = f'HIGH SCORES\n' \
                     f'EASY : {easy_score}\n' \
                     f'HARD : {hard_score}'
    high_scores_start_display = highscore_start_font.render(highscore_text, True, WHITE)
    # fake_screen.blit(high_scores_start_display, (600, 600))

    blit_text(fake_screen, highscore_text, (500, 600), highscore_start_font, WHITE)

    screen.blit(pygame.transform.scale(fake_screen, screen.get_rect().size), (0, 0))
    pygame.display.flip()


def check_buttons(pos):
    global start, difficulty

    if easy_button.collidepoint(pos):
        start = True
        difficulty = 'easy'
    elif hard_button.collidepoint(pos):
        start = True
        difficulty = 'hard'


# 메인 게임 함수
def main():
    global running, characterOnTheLand, character_x_pos, character_y_pos, ground_y_pos, character_to_x, character_to_y, gravity, howManySteps, land_size_x, landCharacterOn_pos, start, difficulty, difficulty_nums

    # 캐릭터 좌우 이동
    character_x_pos += character_to_x

    # 캐릭터 상하 이동
    if character_to_y != gravity:
        if difficulty == 'easy':
            character_to_y += difficulty_nums[0]['character_to_y +']
        elif difficulty == 'hard':
            character_to_y += difficulty_nums[1]['character_to_y +']
    character_to_y = min(character_to_y, 9)
    character_y_pos += character_to_y

    # 캐릭터가 화면 옆으로 나가지 않도록
    if character_x_pos < 0:
        character_x_pos = 0
    elif character_x_pos > screen_width - character_width:
        character_x_pos = screen_width - character_width

    # 캐릭터가 바닥 아래로 떨어지면 게임오버
    if character_y_pos > screen_height:
        game_over()

    # 새로운 발판 추가
    if difficulty == 'easy':
        num_of_land = difficulty_nums[0]['num_of_land']
    elif difficulty == 'hard':
        num_of_land = difficulty_nums[1]['num_of_land']

    if len(lands) < num_of_land:
        width = random.randint(110, 250)
        land_spawn = LandToStep(random.randrange(0, screen_width - width),
                                random.randrange(-50, -28),
                                width, 25)
        lands.append([land_spawn.image(), land_spawn.pos()])

    # 충돌 처리
    character_rect = character.get_rect()
    character_rect.left = character_x_pos
    character_rect.top = character_y_pos

    for land in lands:
        land_pos_x = land[1][0]
        # 바닥을 아래로 내리기
        land[1][1] += gravity
        land_pos_y = land[1][1]

        # 화면 아래로 내려간 발판 삭제
        if land_pos_y > screen_height:
            lands.pop(0)
            howManySteps += 1

        # 발판 rect 정보 업데이트
        land_rect = land[0][0].get_rect()
        land_rect.left = land_pos_x
        land_rect.top = land_pos_y

        # 캐릭터가 발판에 올라서는가 판정
        if character_to_y >= -0.5:
            if land_pos_y + 7 >= character_y_pos + character_height >= land_pos_y - 2:
                if land[0][1] + land_pos_x + character_width >= character_x_pos + character_width >= land_pos_x:
                    if difficulty == 'easy':
                        character_to_y = difficulty_nums[0]['gravity']
                    elif difficulty == 'hard':
                        character_to_y = difficulty_nums[1]['gravity']

                    character_to_y += 0
                    characterOnTheLand = True
                    landCharacterOn_pos = land_pos_x
                    land_size_x = land[0][1]

    # 캐릭터가 발판을 벗어나면 떨어지도록
    if characterOnTheLand:
        if land_size_x + landCharacterOn_pos + character_width <= character_x_pos + character_width or character_x_pos + character_width <= landCharacterOn_pos:
            if difficulty == 'easy':
                character_to_y += difficulty_nums[0]['character_to_y +']
            elif difficulty == 'hard':
                character_to_y += difficulty_nums[1]['character_to_y +']

            characterOnTheLand = False

    ground_y_pos += gravity

    if howManySteps < 20:
        fake_screen.blit(background1, (0, 0))  # 배경그리기
    elif 20 <= howManySteps < 40:
        fake_screen.blit(background2, (0, 0))
    elif 40 <= howManySteps < 60:
        fake_screen.blit(background3, (0, 0))
    elif 60 <= howManySteps:
        fake_screen.blit(background4, (0, 0))

    fake_screen.blit(ground, (0, ground_y_pos))  # 바닥그리기
    fake_screen.blit(character, (character_x_pos, character_y_pos))  # 캐릭터 그리기
    for land in lands:
        fake_screen.blit(land[0][0], (land[1][0], land[1][1]))  # 발판 그리기

    score = game_font.render(str(int(howManySteps)), True, (255, 255, 255))
    fake_screen.blit(score, (20, 20))
    screen.blit(pygame.transform.scale(fake_screen, screen.get_rect().size), (0, 0))
    pygame.display.flip()


# 이벤트 루프
start = False
running = True
difficulty = None
characterOnTheGround = True
RUNNING, PAUSE = 0, 1
state = RUNNING
print('start')
while running:
    dt = clock.tick(60)  # 초당 프레임
    click_pos = None
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.MOUSEBUTTONUP:
            click_pos = pygame.mouse.get_pos()

        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:
                if difficulty == 'easy':
                    gravity = difficulty_nums[0]['gravity']
                elif difficulty == 'hard':
                    gravity = difficulty_nums[1]['gravity']

                if characterOnTheGround:
                    jump_sound.play()
                    characterOnTheGround = False
                    character_to_y = -22
                elif characterOnTheLand:
                    characterOnTheLand = False
                    jump_sound.play()
                    if difficulty == 'easy':
                        character_to_y = difficulty_nums[0]['character_to_y']
                    elif difficulty == 'hard':
                        character_to_y = difficulty_nums[1]['character_to_y']
            # pause, start
            if start:
                if event.key == pygame.K_p:
                    state = PAUSE
                elif event.key == pygame.K_s:
                    state = RUNNING

        if event.type == pygame.KEYUP:
            if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT:
                character_to_x = 0

    if click_pos:
        check_buttons(click_pos)

    if not start:
        start_display()
    elif start:
        if state == RUNNING:
            pygame.mixer.music.unpause()
            main()
        elif state == PAUSE:
            pygame.mixer.music.pause()
            screen.blit(pause_text, pause_text_rect)

    pygame.display.update()  # 게임 화면을 다시 그리기

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

pygame.quit()

Updated: