Menu
CoddyTech

Từ mọi phía

Bài học 6 trên 25 trong khóa học Dự án Pygame: Trấn giữ thành trì của Coddy.

Một con slime chưa phải là cả một bầy. Quái vật nên xuất hiện từ bên ngoài cửa sổ, ở bất kỳ cạnh nào. Một hàm chọn một cạnh bằng random.randint(0, 3), rồi chọn ngẫu nhiên một vị trí dọc theo cạnh đó, cách ra 24 pixel. Cạnh trên và cạnh dưới trông như sau:

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

Sau đó, tạo một con mới mỗi giây. self.time % 60 bằng 0 sau mỗi 60 khung hình. Việc tạo ra những vật thể mới được gọi là sinh ra; hãy tạo một phương thức riêng cho việc này, spawn, vì các bài học tiếp theo sẽ thay đổi tần suất và loại quái vật được tạo ra.

Gọi phương thức này trong update trước khi quái vật di chuyển, để quái vật mới có bước đi đầu tiên ngay trong khung hình nó xuất hiện.

challenge icon

Thử thách

Dễ

Đưa một slime vào từ một cạnh ngẫu nhiên mỗi giây.

  1. Thêm import random và viết edge_point(): cạnh 0 và 1 như trên, cạnh 2 trả về (-24, random.randint(0, HEIGHT)) và cạnh 3 cũng tương tự tại WIDTH + 24.
  2. Xóa slime đầu tiên khỏi Night.__init__.
  3. Viết spawn(self): cứ mỗi 60 khung hình, thêm một slime tại edge_point(). Gọi hàm này trong update sau khi hiệp sĩ di chuyển.

Nhấn “Chơi”: cứ mỗi giây, một slime xuất hiện từ một cạnh. Sau đó nhấn “Kiểm tra”.

Tự làm thử

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)


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")
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}"


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

    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.monsters.add(Monster("slime", (0, 0)))

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


night = Night()

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

    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)
    draw(knight.image, knight.rect.center)
    left = font.render(clock_text(max(0, NIGHT - night.time)) + " until dawn", True, WHITE)
    screen.blit(left, left.get_rect(center=(WIDTH // 2, 24)))
    pygame.display.flip()
    clock.tick(60)

pygame.quit()

Tất cả bài học trong Dự án Pygame: Trấn giữ thành trì

Tự luyện tập: Playground Pygame