Pygame 으로 미로 게임 만들기

PythonBeginner
지금 연습하기

This tutorial is from open-source community. Access the source code

소개

이 프로젝트에서는 Python 의 Pygame 라이브러리를 사용하여 미로 게임을 만들 것입니다. 이 게임은 플레이어가 벽을 피하면서 미로를 통과하여 음식 아이템을 수집하는 것을 포함합니다. 개발 과정을 여러 단계로 나누어 이해하고 따라하기 쉽게 만들 것입니다.

👀 미리보기

Maze game preview screenshot

🎯 과제

이 프로젝트에서 다음을 배우게 됩니다:

  • Pygame 을 사용하여 게임 환경을 설정하는 방법
  • 셀과 벽을 사용하여 미로를 만드는 방법
  • 플레이어가 수집할 음식 아이템을 추가하는 방법
  • 플레이어 이동 및 충돌 감지를 구현하는 방법
  • 점수 계산 및 게임 오버 조건을 포함한 게임 로직을 처리하는 방법
  • 플레이어의 기록을 추적하는 방법
  • 시간, 점수, 기록과 같은 게임 통계를 화면에 표시하는 방법

🏆 성과

이 프로젝트를 완료하면 다음을 수행할 수 있습니다:

  • 게임 개발을 위해 Pygame 라이브러리를 사용합니다.
  • 객체 지향 프로그래밍 (Object-oriented programming) 개념을 적용하여 게임 요소를 만듭니다.
  • 미로 생성을 위한 알고리즘적 사고와 문제 해결 능력을 보여줍니다.
  • 이벤트 처리 및 플레이어 입력을 처리합니다.
  • 게임 환경에서 충돌 감지 및 이동 메커니즘을 구현합니다.
  • 게임 기록을 저장하고 검색하기 위한 파일 처리를 관리합니다.
  • 화면에 게임 통계 및 정보를 표시합니다.
이것은 가이드 실험입니다. 학습과 실습을 돕기 위한 단계별 지침을 제공합니다.각 단계를 완료하고 실무 경험을 쌓기 위해 지침을 주의 깊게 따르세요. 과거 데이터에 따르면, 이것은 중급 레벨의 실험이며 완료율은 67%입니다.학습자들로부터 50%의 긍정적인 리뷰율을 받았습니다.

환경 설정

먼저, 미로 게임을 위한 프로젝트 파일을 만들 것입니다.

cd ~/project
touch maze.py
sudo pip install pygame

이 단계에서는 Pygame 환경을 설정하고 상수를 정의합니다.

import pygame
from random import choice, randrange

## Constants for screen dimensions and tile size
RES = WIDTH, HEIGHT = 1202, 902
TILE = 100
cols, rows = WIDTH // TILE, HEIGHT // TILE

## The rest of your code will go here...

이 단계에서:

  • 필요한 라이브러리 (Pygame 및 random) 를 가져옵니다.
  • 화면 치수 및 타일 크기에 대한 상수를 정의합니다.
  • Pygame 이 초기화되고 게임 창이 설정됩니다.
  • 게임의 배경 이미지를 로드합니다.
✨ 솔루션 확인 및 연습

Cell 클래스 생성

이 단계에서는 미로 셀을 나타내는 Cell 클래스를 정의합니다.

## Define a class to represent cells in the maze
class Cell:
    def __init__(self, x, y):
        self.x, self.y = x, y
        ## Walls represent the boundaries of the cell
        self.walls = {"top": True, "right": True, "bottom": True, "left": True}
        self.visited = False
        self.thickness = 4

    ## Draw the cell's walls
    def draw(self, sc):
        x, y = self.x * TILE, self.y * TILE
        if self.walls["top"]:
            pygame.draw.line(
                sc, pygame.Color("darkorange"), (x, y), (x + TILE, y), self.thickness
            )
        if self.walls["right"]:
            pygame.draw.line(
                sc,
                pygame.Color("darkorange"),
                (x + TILE, y),
                (x + TILE, y + TILE),
                self.thickness,
            )
        if self.walls["bottom"]:
            pygame.draw.line(
                sc,
                pygame.Color("darkorange"),
                (x + TILE, y + TILE),
                (x, y + TILE),
                self.thickness,
            )
        if self.walls["left"]:
            pygame.draw.line(
                sc, pygame.Color("darkorange"), (x, y + TILE), (x, y), self.thickness
            )

    ## Get the rectangles representing each wall of the cell
    def get_rects(self):
        rects = []
        x, y = self.x * TILE, self.y * TILE
        if self.walls["top"]:
            rects.append(pygame.Rect((x, y), (TILE, self.thickness)))
        if self.walls["right"]:
            rects.append(pygame.Rect((x + TILE, y), (self.thickness, TILE)))
        if self.walls["bottom"]:
            rects.append(pygame.Rect((x, y + TILE), (TILE, self.thickness)))
        if self.walls["left"]:
            rects.append(pygame.Rect((x, y), (self.thickness, TILE)))
        return rects

    ## Check if a neighboring cell exists
    def check_cell(self, x, y):
        find_index = lambda x, y: x + y * cols
        if x < 0 or x > cols - 1 or y < 0 or y > rows - 1:
            return False
        return self.grid_cells[find_index(x, y)]

    ## Get neighboring cells that have not been visited
    def check_neighbors(self, grid_cells):
        self.grid_cells = grid_cells
        neighbors = []
        top = self.check_cell(self.x, self.y - 1)
        right = self.check_cell(self.x + 1, self.y)
        bottom = self.check_cell(self.x, self.y + 1)
        left = self.check_cell(self.x - 1, self.y)
        if top and not top.visited:
            neighbors.append(top)
        if right and not right.visited:
            neighbors.append(right)
        if bottom and not bottom.visited:
            neighbors.append(bottom)
        if left and not left.visited:
            neighbors.append(left)
        return choice(neighbors) if neighbors else False

## The rest of your code will go here...

이 단계에서:

  • 벽을 그리고 이웃을 확인하기 위한 속성과 메서드를 갖는 Cell 클래스를 정의합니다.
✨ 솔루션 확인 및 연습

벽 제거 및 미로 생성

이 단계에서는 벽을 제거하고 미로를 생성하는 함수를 만들 것입니다.

## Function to remove walls between two adjacent cells
def remove_walls(current, next):
    dx = current.x - next.x
    if dx == 1:
        current.walls["left"] = False
        next.walls["right"] = False
    elif dx == -1:
        current.walls["right"] = False
        next.walls["left"] = False
    dy = current.y - next.y
    if dy == 1:
        current.walls["top"] = False
        next.walls["bottom"] = False
    elif dy == -1:
        current.walls["bottom"] = False
        next.walls["top"] = False


## Function to generate the maze
def generate_maze():
    grid_cells = [Cell(col, row) for row in range(rows) for col in range(cols)]
    current_cell = grid_cells[0]
    array = []
    break_count = 1

    while break_count != len(grid_cells):
        current_cell.visited = True
        next_cell = current_cell.check_neighbors(grid_cells)
        if next_cell:
            next_cell.visited = True
            break_count += 1
            array.append(current_cell)
            remove_walls(current_cell, next_cell)
            current_cell = next_cell
        elif array:
            current_cell = array.pop()
    return grid_cells

## The rest of your code will go here...

이 단계에서:

  • 인접한 셀 사이의 벽을 제거하는 remove_walls 함수를 정의합니다.
  • 깊이 우선 탐색 (depth-first search) 알고리즘을 사용하여 미로를 생성하는 generate_maze 함수를 만듭니다.
✨ 솔루션 확인 및 연습

게임에 음식 추가

이 단계에서는 게임에 음식 아이템을 추가하기 위해 Food 클래스를 만들 것입니다.

## Class to represent food in the game
class Food:
    def __init__(self):
        ## Load the food image
        self.img = pygame.image.load("img/food.png").convert_alpha()
        self.img = pygame.transform.scale(self.img, (TILE - 10, TILE - 10))
        self.rect = self.img.get_rect()
        self.set_pos()

    ## Set the position of the food randomly
    def set_pos(self):
        self.rect.topleft = randrange(cols) * TILE + 5, randrange(rows) * TILE + 5

    ## Draw the food on the screen
    def draw(self):
        game_surface.blit(self.img, self.rect)

## The rest of your code will go here...

이 단계에서:

  • 위치를 설정하고 음식 아이템을 그리는 메서드를 갖는 Food 클래스를 정의합니다.
✨ 솔루션 확인 및 연습

플레이어 이동 및 충돌 감지

이 단계에서는 플레이어 컨트롤, 이동 및 충돌 감지를 설정합니다.

## Check if the player collides with walls
def is_collide(x, y):
    tmp_rect = player_rect.move(x, y)
    if tmp_rect.collidelist(walls_collide_list) == -1:
        return False
    return True

## The rest of your code will go here...

이 단계에서:

  • 플레이어가 벽과 충돌하는지 확인하는 is_collide 함수를 정의합니다.
✨ 솔루션 확인 및 연습

게임 플레이 및 점수 시스템

이 단계에서는 음식 먹기 및 점수를 포함한 게임 플레이 로직을 구현합니다.

## Check if the player has eaten any food
def eat_food():
    for food in food_list:
        if player_rect.collidepoint(food.rect.center):
            food.set_pos()
            return True
    return False


## Check if the game is over (time runs out)
def is_game_over():
    global time, score, record, FPS
    if time < 0:
        pygame.time.wait(700)
        player_rect.center = TILE // 2, TILE // 2
        [food.set_pos() for food in food_list]
        set_record(record, score)
        record = get_record()
        time, score, FPS = 60, 0, 60

## The rest of your code will go here...

이 단계에서:

  • 플레이어가 음식을 먹었는지 확인하는 eat_food 함수를 정의합니다.
  • 시간이 다 되었을 때 게임이 종료되었는지 확인하는 is_game_over 함수를 생성합니다.
✨ 솔루션 확인 및 연습

기록 관리

이 단계에서는 게임의 기록 관리를 구현합니다.

## Function to get the current record from a file
def get_record():
    try:
        with open("record") as f:
            return f.readline()
    except FileNotFoundError:
        with open("record", "w") as f:
            f.write("0")
            return "0"

## Function to set and update the record in a file
def set_record(record, score):
    rec = max(int(record), score)
    with open("record", "w") as f:
        f.write(str(rec))

## The rest of your code will go here...

이 단계에서:

  • 파일에서 현재 기록을 가져오고 업데이트하는 함수를 정의합니다.
✨ 솔루션 확인 및 연습

게임 초기화

이 단계에서는 게임 초기화 작업을 수행합니다.

## Initialize Pygame and set up the game window
FPS = 60
pygame.init()
game_surface = pygame.Surface(RES)
surface = pygame.display.set_mode((WIDTH + 300, HEIGHT))
clock = pygame.time.Clock()

## Load background images
bg_game = pygame.image.load("img/bg_1.jpg").convert()
bg = pygame.image.load("img/bg_main.jpg").convert()

## Generate the maze
maze = generate_maze()

## Player settings
player_speed = 5
player_img = pygame.image.load("img/0.png").convert_alpha()
player_img = pygame.transform.scale(
    player_img, (TILE - 2 * maze[0].thickness, TILE - 2 * maze[0].thickness)
)
player_rect = player_img.get_rect()
player_rect.center = TILE // 2, TILE // 2
directions = {
    "a": (-player_speed, 0),
    "d": (player_speed, 0),
    "w": (0, -player_speed),
    "s": (0, player_speed),
}
keys = {"a": pygame.K_LEFT, "d": pygame.K_RIGHT, "w": pygame.K_UP, "s": pygame.K_DOWN}
direction = (0, 0)

## Food settings
food_list = [Food() for i in range(3)]

## Create a list of rectangles representing walls for collision detection
walls_collide_list = sum([cell.get_rects() for cell in maze], [])

## Timer, score, and record
pygame.time.set_timer(pygame.USEREVENT, 1000)
time = 60
score = 0
record = get_record()

## Fonts
font = pygame.font.SysFont("Impact", 150)
text_font = pygame.font.SysFont("Impact", 80)

## The rest of your code will go here...

이 단계에서:

  • Pygame 설정, 이미지 로드, 미로 생성, 플레이어 및 음식 관련 변수 초기화를 포함한 다양한 초기화 작업을 수행합니다.
✨ 솔루션 확인 및 연습

메인 게임 루프

이 단계에서는 메인 게임 루프를 설정하고 게임 요소를 표시합니다.

## Main game loop
while True:
    ## Blit background images
    surface.blit(bg, (WIDTH, 0))
    surface.blit(game_surface, (0, 0))
    game_surface.blit(bg_game, (0, 0))

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            exit()
        if event.type == pygame.USEREVENT:
            time -= 1

    ## Handle player controls and movement
    pressed_key = pygame.key.get_pressed()
    for key, key_value in keys.items():
        if pressed_key[key_value] and not is_collide(*directions[key]):
            direction = directions[key]
            break
    if not is_collide(*direction):
        player_rect.move_ip(direction)

    ## Draw the maze
    [cell.draw(game_surface) for cell in maze]

    ## Gameplay: Check if the player has eaten food and if the game is over
    if eat_food():
        FPS += 10
        score += 1
    is_game_over()

    ## Draw the player
    game_surface.blit(player_img, player_rect)

    ## Draw food items
    [food.draw() for food in food_list]

    ## The rest of your code will go here...

이 단계에서:

  • 이벤트 처리, 플레이어 이동 및 게임 렌더링을 처리하는 메인 게임 루프를 설정합니다.
✨ 솔루션 확인 및 연습

게임 통계 표시

이 단계에서는 화면에 게임 통계를 표시합니다.

    ## Draw game statistics
    surface.blit(
        text_font.render("TIME", True, pygame.Color("cyan"), True), (WIDTH + 70, 30)
    )
    surface.blit(font.render(f"{time}", True, pygame.Color("cyan")), (WIDTH + 70, 130))
    surface.blit(
        text_font.render("score:", True, pygame.Color("forestgreen"), True),
        (WIDTH + 50, 350),
    )
    surface.blit(
        font.render(f"{score}", True, pygame.Color("forestgreen")), (WIDTH + 70, 430)
    )
    surface.blit(
        text_font.render("record:", True, pygame.Color("magenta"), True),
        (WIDTH + 30, 620),
    )
    surface.blit(
        font.render(f"{record}", True, pygame.Color("magenta")), (WIDTH + 70, 700)
    )

    pygame.display.flip()
    clock.tick(FPS)

이 단계에서:

  • 글꼴을 사용하여 시간, 점수 및 기록과 같은 게임 관련 정보를 표시합니다.
  • blit() 메서드를 사용하여 화면에 텍스트를 그립니다.
  • flip() 메서드를 사용하여 디스플레이를 업데이트합니다.
✨ 솔루션 확인 및 연습

게임 실행

이제 모든 단계를 완료했으므로 다음 명령을 사용하여 Maze 게임을 실행할 수 있습니다.

cd ~/project
python maze.py
Maze game execution screenshot
✨ 솔루션 확인 및 연습

요약

이 프로젝트에서는 Pygame 을 사용하여 미로 게임을 구축하는 과정을 10 개의 명확하고 관리 가능한 단계로 나누었습니다. 게임 환경 설정, 미로 셀 생성, 미로 생성, 플레이어 이동 및 충돌 감지 처리, 게임 플레이 및 점수 구현, 기록 관리 등에 대해 배우게 됩니다. 이러한 단계를 따르면 Python 으로 완벽하게 작동하는 미로 게임을 만들 수 있습니다.