Menu
CoddyTech

Trái tim và đòn đánh

Thuộc phần Dự án trong hành trình Pygame của Coddy. Bài học 27 trên 33.

Hiệp sĩ có 3 trái tim. Chạm phải slime sẽ mất một trái tim, và khi còn 0 trái tim, trò chơi kết thúc.

pygame.sprite.spritecollide(knight, room.enemies, False) là danh sách kẻ địch đang chạm vào hiệp sĩ. False giữ chúng trong nhóm.

Slime trượt qua hiệp sĩ sẽ chạm vào anh ấy trong khoảng 30 khung hình. Nếu mất một trái tim ở mỗi khung hình, trò chơi sẽ kết thúc chỉ sau nửa giây. Vì vậy, sau khi bị đánh, hiệp sĩ sẽ an toàn trong 60 khung hình, tức một giây: knight.hurt đếm ngược số khung hình này, và không tính lần chạm nào khi giá trị còn lớn hơn 0.

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"

Cho người chơi biết hiệp sĩ đang an toàn: khi bị thương, chỉ vẽ hiệp sĩ nếu knight.hurt % 10 < 5. Hiệp sĩ nhấp nháy, hiện trong 5 khung hình rồi ẩn trong 5 khung hình.

state hoạt động giống như trong Sky Runner: mọi thứ chuyển động đều nằm trong if state == "playing":, nên khi ở trạng thái "lost", pháo đài sẽ đứng yên.

challenge icon

Thử thách

Trung bình

Khi chạm vào slime, hiệp sĩ sẽ mất tim.

  1. Thêm cho hiệp sĩ self.hearts = 3 và self.hurt = 0.
  2. Đặt state = "playing"; thêm phần cập nhật bên dưới if state == "playing":, kiểm tra trạng thái bị thương sau phần cập nhật kẻ địch.
  3. Vẽ hiệp sĩ khi knight.hurt % 10 < 5 và vẽ số tim.
  4. Khi ở trạng thái "lost", hiển thị Game over ở giữa tại tọa độ (336, 240).

Nhấn Chơi rồi chạm vào slime: một tim sẽ cạn. Sau đó nhấn Kiểm tra.

Tự làm thử

import random
import pygame

TILE = 48
MAP = [
    "##############",
    "#............#",
    "#.P......K...#",
    "#............#",
    "#....##......D",
    "#....##......#",
    "##.#######.###",
    "#............#",
    "#...S....H...#",
    "##############",
]

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


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

    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

    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


room = Room(MAP)
knight = Knight(room.start)

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

    keys = pygame.key.get_pressed()
    dx = 0
    dy = 0
    if keys[pygame.K_LEFT]:
        dx = -3
    if keys[pygame.K_RIGHT]:
        dx = 3
    if keys[pygame.K_UP]:
        dy = -3
    if keys[pygame.K_DOWN]:
        dy = 3
    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

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

    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 enemy in room.enemies:
        draw(enemy.image, enemy.rect)
    draw(knight.image, knight.rect)
    if room.has_key:
        screen.blit(key_img, (136, 6))
    pygame.display.flip()
    clock.tick(60)

pygame.quit()

Tất cả bài học trong Dự án

Tự luyện tập: Playground Pygame