Menu
CoddyTech

웨이브

Coddy의 Pygame 프로젝트: 성채를 지켜라 코스 레슨. 20개 중 8번째.

슬라임만 나오는 밤은 지루할 거예요. 밤을 여덟 개의 웨이브로 나누세요. 각 웨이브에는 어떤 종류의 몬스터가 등장하는지 나열합니다:

WAVES = [
    ["slime"],
    ["slime", "bat"],
    ...
    ["bat", "ghost"],
]

지금은 몇 번째 웨이브일까요? self.time * len(WAVES) // NIGHT는 밤의 첫 8분의 1 동안 0이고 마지막 8분의 1 동안 7입니다. 1부터 세도록 1을 더하고, min을 사용하면 밤이 끝나는 순간에도 숫자가 8을 넘지 않습니다:

def wave(self):
    return min(len(WAVES), self.time * len(WAVES) // NIGHT + 1)

각 웨이브는 점점 더 빠르게 이어집니다. 몬스터 사이의 프레임 수를 max(16, 46 - wave * 5)로 정하면, 1번째 웨이브에서는 41이고 마지막 웨이브들에서는 16이며, 그보다 적어지지는 않습니다. random.choice(list)는 웨이브 목록에서 종류 하나를 고릅니다.

박쥐는 빠르고, 거미는 조금 느리며, 유령은 느립니다. 각자 속도를 지정해 MONSTERS에 추가하세요.

challenge icon

챌린지

중급
  1. WAVES 목록을 추가하세요: ["slime"], ["slime", "bat"], ["slime", "bat", "spider"], ["bat", "spider"], ["slime", "spider", "ghost"], ["bat", "spider", "ghost"], ["spider", "ghost"], ["bat", "ghost"].
  2. MONSTERS에 다음을 추가하세요: "bat" (dungeon_bat.png, 속도 1.6), "spider" (spider.png, 1.2), "ghost" (ghost.png, 0.8).
  3. 위와 같이 wave(self)를 작성하세요.
  4. spawn에서 max(16, 46 - wave * 5) 프레임마다 웨이브 목록에서 무작위로 선택한 종류의 몬스터를 추가하세요.

직접 해보기

import random
import pygame

WIDTH, HEIGHT = 720, 480
TILE = 48
NIGHT = 60 * 60 * 4
WHITE = (240, 240, 240)

pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Hold the Keep")
clock = pygame.time.Clock()
font = pygame.font.Font("assets/fonts/pixel.ttf", 24)
big_font = pygame.font.Font("assets/fonts/pixel.ttf", 48)


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")
heart_img = load("heart.png")
empty_img = load("heart_empty.png")
MONSTERS = {
    "slime": {"image": load("slime.png"), "speed": 1.0},
}

courtyard = pygame.Rect(TILE, TILE, WIDTH - 2 * TILE, HEIGHT - 2 * TILE)
background = pygame.Surface((WIDTH, HEIGHT))
for row in range(HEIGHT // TILE):
    for col in range(WIDTH // TILE):
        x = col * TILE
        y = row * TILE
        if courtyard.collidepoint(x, y):
            background.blit(floor_img, (x, y))
        else:
            background.blit(wall_img, (x, y))


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


def clock_text(frames):
    seconds = frames // 60
    return f"{seconds // 60}:{seconds % 60:02d}"


def edge_point():
    side = random.randint(0, 3)
    if side == 0:
        return (random.randint(0, WIDTH), -24)
    if side == 1:
        return (random.randint(0, WIDTH), HEIGHT + 24)
    if side == 2:
        return (-24, random.randint(0, HEIGHT))
    return (WIDTH + 24, random.randint(0, HEIGHT))


class Knight(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = knight_img
        self.rect = pygame.Rect(0, 0, 24, 24)
        self.rect.center = courtyard.center
        self.speed = 3
        self.hearts = 5
        self.max_hearts = 5
        self.hurt = 0

    def move(self, keys):
        if keys[pygame.K_LEFT]:
            self.rect.x -= self.speed
        if keys[pygame.K_RIGHT]:
            self.rect.x += self.speed
        if keys[pygame.K_UP]:
            self.rect.y -= self.speed
        if keys[pygame.K_DOWN]:
            self.rect.y += self.speed
        self.rect.clamp_ip(courtyard)


class Monster(pygame.sprite.Sprite):
    def __init__(self, kind, pos):
        super().__init__()
        self.kind = kind
        self.image = MONSTERS[kind]["image"]
        self.speed = MONSTERS[kind]["speed"]
        self.pos = pygame.Vector2(pos)
        self.rect = pygame.Rect(0, 0, 28, 28)
        self.rect.center = self.pos

    def update(self, target):
        step = pygame.Vector2(target) - self.pos
        if step.length() > 0:
            self.pos += step.normalize() * self.speed
        self.rect.center = self.pos


class Night:
    def __init__(self):
        self.knight = Knight()
        self.time = 0
        self.monsters = pygame.sprite.Group()
        self.state = "playing"

    def spawn(self):
        if self.time % 60 == 0:
            self.monsters.add(Monster("slime", edge_point()))

    def take_hits(self):
        knight = self.knight
        if knight.hurt > 0:
            knight.hurt -= 1
        elif pygame.sprite.spritecollideany(knight, self.monsters):
            knight.hearts -= 1
            knight.hurt = 75
            if knight.hearts <= 0:
                self.state = "lost"

    def update(self, keys):
        self.time += 1
        self.knight.move(keys)
        self.spawn()
        self.monsters.update(self.knight.rect.center)
        self.take_hits()


night = Night()

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    if night.state == "playing":
        night.update(pygame.key.get_pressed())

    screen.blit(background, (0, 0))
    knight = night.knight
    for monster in night.monsters:
        draw(monster.image, monster.rect.center)
    if knight.hurt % 10 < 5:
        draw(knight.image, knight.rect.center)
    for i in range(knight.max_hearts):
        if i < knight.hearts:
            screen.blit(heart_img, (8 + i * 34, 6))
        else:
            screen.blit(empty_img, (8 + i * 34, 6))
    left = font.render(clock_text(max(0, NIGHT - night.time)) + " until dawn", True, WHITE)
    screen.blit(left, left.get_rect(center=(WIDTH // 2, 24)))
    if night.state == "lost":
        message = big_font.render("The keep has fallen", True, WHITE)
        screen.blit(message, message.get_rect(center=(WIDTH // 2, 200)))
    pygame.display.flip()
    clock.tick(60)

pygame.quit()
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

Pygame 프로젝트: 성채를 지켜라의 모든 레슨

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