Menu
CoddyTech

성채 탈출

Coddy Pygame 여정의 프로젝트 섹션에 포함된 레슨. 31개 중 29번째.

마지막 방의 문을 지나면 기사는 밖으로 나갑니다. 게임에 You escaped!가 표시됩니다. 어느 쪽으로 끝나든 R을 누르면 첫 번째 방에서 다시 시작합니다.

이제 문 검사는 두 가지 결과를 가집니다.

if room.door_open and knight.rect.colliderect(room.door):
    if level == len(ROOMS) - 1:
        state = "won"
    else:
        level += 1
        room = Room(ROOMS[level])
        knight.rect.center = room.start

다시 시작하는 과정은 짧습니다. 클래스가 게임을 관리하기 때문입니다. 새로운 Room은 열쇠와 문을 다시 배치하고 모든 슬라임을 제자리로 되돌려 첫 번째 방을 재구성합니다. 새로운 Knight는 하트 3개를 가지고, 부상을 입지 않았으며, 휘두르기 동작도 하지 않은 상태입니다.

elif event.key == pygame.K_r and state != "playing":
    level = 0
    room = Room(ROOMS[level])
    knight = Knight(room.start)
    state = "playing"

처음부터 플레이해 보세요. 방이 너무 어렵거나 너무 빠르게 진행된다면 맵을 바꾸세요. 슬라임을 옮기거나, 물약을 추가하거나, 새로운 방을 그려 ROOMS에 추가하면 됩니다. 모든 규칙이 맵을 참조하므로 다른 것은 바꿀 필요가 없습니다. 타일 너비가 1인 복도에 있는 슬라임은 걸어서 지나칠 수 없고 싸워야만 합니다. 탁 트인 홀에 있는 슬라임은 피할 수 있습니다.

challenge icon

챌린지

중급
  1. 위와 같이 문 검사 부분을 변경하세요. 마지막 방에서는 상태를 "won"으로 설정하세요.
  2. 상태가 "won"이면 (336, 240)에 You escaped!를 중앙에 표시하세요.
  3. 어느 쪽으로 끝나든, (336, 290)에 Press R to play again을 중앙에 표시하세요.
  4. 상태가 "playing"이 아닐 때 R의 KEYDOWN 이벤트가 발생하면 위와 같이 다시 시작합니다.

직접 해보기

import random
import pygame

TILE = 48
MAP = [
    "##############",
    "#............#",
    "#.P......K...#",
    "#............#",
    "#....##......D",
    "#....##......#",
    "##.#######.###",
    "#............#",
    "#...S....H...#",
    "##############",
]
MAP2 = [
    "##############",
    "#P...#...X...#",
    "#....#.......#",
    "#..S.#..###..#",
    "#....#..#K...#",
    "#.......#....#",
    "##.######X####",
    "#....S.......#",
    "#.H..........#",
    "###D##########",
]
MAP3 = [
    "##############",
    "#....#...X...#",
    "#.P..#.......#",
    "#....#..S....#",
    "#..S.....###.D",
    "#....#...#K.S#",
    "###.###X.#...#",
    "#........#...#",
    "#..H.....#...#",
    "##############",
]
ROOMS = [MAP, MAP2, MAP3]

pygame.init()
screen = pygame.display.set_mode((672, 480))
pygame.display.set_caption("Rune Keep")
clock = pygame.time.Clock()
font = pygame.font.Font("assets/fonts/pixel.ttf", 32)


def load(name):
    return pygame.image.load("assets/images/" + name).convert_alpha()


floor_img = load("floor.png")
wall_img = load("wall.png")
knight_img = load("knight.png")
door_img = load("dungeon_door.png")
key_img = load("key.png")
slime_img = load("slime.png")
spider_img = load("spider.png")
heart_img = load("heart.png")
empty_img = load("heart_empty.png")
sword_img = load("sword.png")
potion_img = load("potion_red.png")
ANGLES = {(0, -1): 0, (-1, 0): 90, (0, 1): 180, (1, 0): -90}


def draw(image, rect):
    screen.blit(image, image.get_rect(center=rect.center))


def cell_rect(col, row):
    rect = pygame.Rect(0, 0, 32, 32)
    rect.center = (col * TILE + 24, row * TILE + 24)
    return rect


class Enemy(pygame.sprite.Sprite):
    def __init__(self, image, rect, dx, dy):
        super().__init__()
        self.image = image
        self.rect = rect
        self.dx = dx
        self.dy = dy

    def update(self, blocks):
        self.rect.x += self.dx
        self.rect.y += self.dy
        if self.rect.collidelist(blocks) != -1:
            self.rect.x -= self.dx
            self.rect.y -= self.dy
            self.dx = -self.dx
            self.dy = -self.dy


class Room:
    def __init__(self, lines):
        self.walls = []
        self.enemies = pygame.sprite.Group()
        self.potions = []
        self.start = (72, 72)
        self.has_key = False
        self.door_open = False
        for row, line in enumerate(lines):
            for col, char in enumerate(line):
                if char == "#":
                    self.walls.append(pygame.Rect(col * TILE, row * TILE, TILE, TILE))
                elif char == "P":
                    self.start = cell_rect(col, row).center
                elif char == "K":
                    self.key = cell_rect(col, row)
                elif char == "D":
                    self.door = pygame.Rect(col * TILE, row * TILE, TILE, TILE)
                elif char == "S":
                    speed = random.choice([-2, 2])
                    self.enemies.add(Enemy(slime_img, cell_rect(col, row), speed, 0))
                elif char == "X":
                    speed = random.choice([-2, 2])
                    self.enemies.add(Enemy(spider_img, cell_rect(col, row), 0, speed))
                elif char == "H":
                    self.potions.append(cell_rect(col, row))

    def blocks(self):
        if self.door_open:
            return self.walls
        return self.walls + [self.door]


class Knight(pygame.sprite.Sprite):
    def __init__(self, center):
        super().__init__()
        self.image = knight_img
        self.rect = pygame.Rect(0, 0, 32, 32)
        self.rect.center = center
        self.hearts = 3
        self.hurt = 0
        self.facing = (1, 0)
        self.swing = 0

    def move(self, dx, dy, blocks):
        self.rect.x += dx
        for block in blocks:
            if self.rect.colliderect(block):
                if dx > 0:
                    self.rect.right = block.left
                elif dx < 0:
                    self.rect.left = block.right
        self.rect.y += dy
        for block in blocks:
            if self.rect.colliderect(block):
                if dy > 0:
                    self.rect.bottom = block.top
                elif dy < 0:
                    self.rect.top = block.bottom

    def sword(self):
        box = pygame.Rect(0, 0, 36, 36)
        box.centerx = self.rect.centerx + self.facing[0] * 34
        box.centery = self.rect.centery + self.facing[1] * 34
        return box


level = 0
room = Room(ROOMS[level])
knight = Knight(room.start)
state = "playing"

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE and state == "playing" and knight.swing == 0:
                knight.swing = 12

    if state == "playing":
        keys = pygame.key.get_pressed()
        dx = 0
        dy = 0
        if keys[pygame.K_LEFT]:
            dx = -3
            knight.facing = (-1, 0)
        if keys[pygame.K_RIGHT]:
            dx = 3
            knight.facing = (1, 0)
        if keys[pygame.K_UP]:
            dy = -3
            knight.facing = (0, -1)
        if keys[pygame.K_DOWN]:
            dy = 3
            knight.facing = (0, 1)
        knight.move(dx, dy, room.blocks())
        knight.rect.clamp_ip(screen.get_rect())

        if not room.has_key and knight.rect.colliderect(room.key):
            room.has_key = True
        if room.has_key and knight.rect.inflate(8, 8).colliderect(room.door):
            room.door_open = True
        if room.door_open and knight.rect.colliderect(room.door) and level < len(ROOMS) - 1:
            level += 1
            room = Room(ROOMS[level])
            knight.rect.center = room.start

        room.enemies.update(room.walls + [room.door])

        if knight.swing > 0:
            knight.swing -= 1
            for enemy in room.enemies:
                if enemy.rect.colliderect(knight.sword()):
                    enemy.kill()

        if knight.hurt > 0:
            knight.hurt -= 1
        elif pygame.sprite.spritecollide(knight, room.enemies, False):
            knight.hearts -= 1
            knight.hurt = 60
            if knight.hearts == 0:
                state = "lost"

        index = knight.rect.collidelist(room.potions)
        if index != -1 and knight.hearts < 3:
            knight.hearts += 1
            room.potions.pop(index)

    for row in range(10):
        for col in range(14):
            screen.blit(floor_img, (col * TILE, row * TILE))
    for wall in room.walls:
        screen.blit(wall_img, wall)
    if not room.door_open:
        screen.blit(door_img, room.door)
    if not room.has_key:
        draw(key_img, room.key)
    for potion in room.potions:
        draw(potion_img, potion)
    for enemy in room.enemies:
        draw(enemy.image, enemy.rect)
    if knight.hurt % 10 < 5:
        draw(knight.image, knight.rect)
    if knight.swing > 0:
        blade = pygame.transform.rotate(sword_img, ANGLES[knight.facing])
        draw(blade, knight.sword())
    for i in range(3):
        if i < knight.hearts:
            screen.blit(heart_img, (8 + i * 40, 6))
        else:
            screen.blit(empty_img, (8 + i * 40, 6))
    if room.has_key:
        screen.blit(key_img, (136, 6))
    label = font.render(f"Room {level + 1} of {len(ROOMS)}", True, (240, 240, 240))
    screen.blit(label, label.get_rect(topright=(660, 8)))
    if state == "lost":
        message = font.render("Game over", True, (240, 240, 240))
        screen.blit(message, message.get_rect(center=(336, 240)))
    pygame.display.flip()
    clock.tick(60)

pygame.quit()

프로젝트의 모든 레슨

직접 연습해 보세요: Pygame 플레이그라운드