Menu
CoddyTech

Trái tim

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

Quái vật chạm vào hiệp sĩ sẽ làm mất một trái tim. Nếu không tạm dừng sau khi bị trúng đòn, quái vật đứng trên hiệp sĩ sẽ làm mất một trái tim ở mỗi khung hình, cả năm trái tim chỉ trong một phần mười hai giây. Vì vậy, khi bị trúng đòn, bộ đếm hurt bắt đầu chạy. Khi bộ đếm này giảm về không, hiệp sĩ không thể bị thương:

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

spritecollideany(sprite, group) trả về sprite đầu tiên trong nhóm có rect chồng lên nhau, hoặc None.

Giờ đây, trò chơi có các trạng thái: "playing" và "lost". Vòng lặp chỉ chạy night.update khi trò chơi đang ở trạng thái playing, vì vậy khi hết trái tim, mọi thứ sẽ dừng lại tại chỗ.

challenge icon

Thử thách

Trung bình

Quái vật giờ làm mất tim.

  1. Cho hiệp sĩ hearts = 5, max_hearts = 5 và hurt = 0, còn Night có state = "playing".
  2. Viết take_hits như trên; khi còn 0 tim, đặt self.state = "lost". Gọi hàm này cuối cùng trong update.
  3. Trong vòng lặp, chỉ cập nhật đêm khi if night.state == "playing". Khi thua, vẽ The keep has fallen bằng big_font cỡ 48, căn giữa tại (360, 200).

Nhấn Chơi rồi đứng yên: trúng năm đòn, pháo đài sẽ thất thủ. Sau đó nhấn Kiểm tra.

Tự làm thử

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)


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


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

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

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

    def update(self, keys):
        self.time += 1
        self.knight.move(keys)
        self.spawn()
        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