Menu
CoddyTech

Một con quái vật

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

Một con quái vật đi thẳng về phía hiệp sĩ. pygame.Vector2 lưu một giá trị x và một giá trị y có thể là số thập phân, và bạn có thể cộng, trừ các Vector2 hoàn chỉnh:

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

step hướng từ quái vật đến hiệp sĩ, dù họ cách xa nhau đến đâu. normalize() giữ nguyên hướng nhưng đưa độ dài về 1 pixel, nên khi nhân với self.speed, quái vật di chuyển đúng bằng tốc độ của nó trong mỗi khung hình, theo bất kỳ hướng nào. Rect chỉ lưu số nguyên, vì vậy vị trí chính xác nằm trong self.pos và Rect đi theo vị trí đó.

Mã khởi đầu hiện có MONSTERS, một bảng có một hàng cho mỗi loại quái vật, nên sau này bạn chỉ cần thêm một dòng để bổ sung loại mới. Quái vật thuộc loại "slime" lấy hình ảnh bằng MONSTERS["slime"]["image"].

challenge icon

Thử thách

Trung bình

Một con slime đi thẳng về phía hiệp sĩ.

  1. Viết class Monster(pygame.sprite.Sprite): __init__(self, kind, pos) lưu kind, image và speed từ MONSTERS, lưu pos dưới dạng Vector2 và tạo một rect kích thước 28 x 28, lấy tâm tại vị trí đó.
  2. Viết update(self, target) như trên.
  3. Trong Night, tạo self.monsters, một Group chứa Monster("slime", (0, 0)), rồi gọi self.monsters.update(self.knight.rect.center) trong update.
  4. Dùng draw để vẽ từng quái vật trước hiệp sĩ.

Nhấn “Chơi”: một con slime đi từ góc trên bên trái thẳng về phía hiệp sĩ. 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 Night:
    def __init__(self):
        self.knight = Knight()
        self.time = 0

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


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
    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