Menu
CoddyTech

Slime tuần tra

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

Mỗi S trên bản đồ là một con slime trượt dọc theo hàng của nó, 2 pixel mỗi khung hình, rồi quay lại khi gặp tường.

Sprite Enemy tự lưu tốc độ của nó trong dx và dy, nên sau này cùng một lớp có thể tạo ra những quái vật di chuyển lên xuống. Phương thức update di chuyển nó, và nếu lúc đó nó chồng lên một khối thì nó lùi lại rồi đổi hướng:

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

rect.collidelist(rects) trả về chỉ số của Rect đầu tiên trong danh sách bị chồng lên, hoặc -1 nếu không có Rect nào bị chồng lên.

Slime nằm trong một Group, room.enemies. room.enemies.update(room.walls + [room.door]) gọi update trên từng slime với danh sách đó. Cửa luôn nằm trong danh sách, nên slime không bao giờ đi ra ngoài qua cửa đang mở.

random.choice([-2, 2]) khiến mỗi slime bắt đầu di chuyển sang trái hoặc phải, vì vậy không có hai ván chơi nào giống nhau.

challenge icon

Thử thách

Trung bình

Thêm slime tuần tra trong phòng.

  1. Thêm import random và lớp Enemy ở phía trên: __init__(self, image, rect, dx, dy) giữ cả bốn giá trị, cùng với update.
  2. Trong Room, tạo self.enemies = pygame.sprite.Group(); ký tự S thêm Enemy(slime_img, cell_rect(col, row), speed, 0), với speed = random.choice([-2, 2]).
  3. Sau phần xử lý cửa, gọi room.enemies.update(room.walls + [room.door]); vẽ kẻ địch trước hiệp sĩ.

Nhấn Chơi: slime tuần tra theo hàng của nó. Sau đó nhấn Kiểm tra.

Tự làm thử

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 Room:
    def __init__(self, lines):
        self.walls = []
        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)

    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

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