Menu
CoddyTech

Cánh cửa bị khóa

Thuộc phần Thế giới trò chơi trong hành trình Pygame của Coddy. Bài học 35 trên 36.

Cánh cửa chặn đường hầm gần cuối hang, và chỉ có chìa khóa mới mở được cửa. Cánh cửa gồm hai ô D. Thêm các Rect của chúng vào walls để code va chạm chặn người chơi, đồng thời thêm chúng vào tiles cùng với door_img để vẽ chúng. Đồng thời lưu chúng trong danh sách doors.

Người chơi bị đẩy sát vào cửa sẽ chạm cửa nhưng không chồng lên cửa, vì vậy hãy kiểm tra bằng Rect rộng hơn 4 pixel ở mỗi bên:

if has_key and doors and player.inflate(8, 0).colliderect(doors[0]):
    for door in doors:
        walls.remove(door)
    tiles = [(image, rect) for image, rect in tiles if rect not in doors]
    doors = []

Cánh cửa bị xóa khỏi cả hai danh sách nên không còn là vật cản và cũng không được vẽ nữa. Cú pháp rút gọn tạo danh sách mới tiles với mọi cặp có Rect không phải là cửa.

challenge icon

Thử thách

Khó

Chặn lối vào đường hầm bằng một cánh cửa mở bằng chìa khóa.

  1. Tải door.png vào door_img và tạo doors = [].
  2. Với mỗi D, thêm Rect vào walls và doors, đồng thời thêm (door_img, rect) vào tiles.
  3. Sau bước kiểm tra chìa khóa: khi có chìa khóa, chạm vào cửa sẽ xóa tất cả các cửa như hình minh họa.

Nhấn Chơi: cánh cửa sẽ chặn bạn cho đến khi bạn mang theo chìa khóa. Sau đó nhấn Kiểm tra.

Tự làm thử

import pygame

TILE = 36
WIDTH, HEIGHT = 576, 360
SPEED = 4
GRAVITY = 1
JUMP = -15
MAX_FALL = 12

LEVEL = [
    "................................................................",
    "................................................................",
    "................................................................",
    "................................................................",
    "#..............................................................#",
    "#....................C.........................#####...........#",
    "#......CC...C................CC.....CC.........#####.....CC....#",
    "#....................#.........................D...............#",
    "#.P.........#...E....#...^.......K......E......D.C....^......F.#",
    "#######..####################..##########################..#####",
]

pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Moss Caves")
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()


background = pygame.image.load("assets/backgrounds/forest.png").convert_alpha()
grass_img = load("grass.png")
grass_left_img = load("grass_left.png")
grass_right_img = load("grass_right.png")
dirt_img = load("dirt.png")
player_img = load("player.png")
coin_img = load("coin.png")
spikes_img = load("spikes.png")
heart_img = load("heart.png")
heart_empty_img = load("heart_empty.png")
blob_img = load("blob.png")
blob_walk_img = load("blob_walk.png")
key_img = load("key.png")


def solid(row, col):
    if row < 0 or row >= len(LEVEL) or col < 0 or col >= len(LEVEL[0]):
        return False
    return LEVEL[row][col] == "#"


class Blob(pygame.sprite.Sprite):
    def __init__(self, midbottom):
        super().__init__()
        self.frames = [blob_img, blob_walk_img]
        self.image = blob_img
        self.rect = pygame.Rect(0, 0, 30, 26)
        self.rect.midbottom = midbottom
        self.direction = 1
        self.timer = 0

    def update(self):
        self.timer += 1
        self.image = self.frames[self.timer // 10 % 2]
        self.rect.x += self.direction
        if self.direction > 0:
            ahead = self.rect.right
        else:
            ahead = self.rect.left - 1
        hit_wall = any(self.rect.colliderect(wall) for wall in walls)
        ground_ahead = any(wall.collidepoint(ahead, self.rect.bottom) for wall in walls)
        if hit_wall or not ground_ahead:
            self.rect.x -= self.direction
            self.direction = -self.direction


walls = []
tiles = []
coins = []
spikes = []
blobs = pygame.sprite.Group()
for row, line in enumerate(LEVEL):
    for col, char in enumerate(line):
        rect = pygame.Rect(col * TILE, row * TILE, TILE, TILE)
        if char == "#":
            if solid(row - 1, col):
                image = dirt_img
            elif not solid(row, col - 1) and solid(row, col + 1):
                image = grass_left_img
            elif solid(row, col - 1) and not solid(row, col + 1):
                image = grass_right_img
            else:
                image = grass_img
            walls.append(rect)
            tiles.append((image, rect))
        elif char == "P":
            start = rect.midbottom
        elif char == "C":
            coins.append(rect)
        elif char == "^":
            spikes.append(pygame.Rect(rect.x, rect.y + 18, TILE, 18))
        elif char == "E":
            blobs.add(Blob(rect.midbottom))
        elif char == "K":
            key = rect

level_width = len(LEVEL[0]) * TILE
player = player_img.get_rect(midbottom=start)
vel_y = 0
on_ground = False
camera_x = 0
coins_collected = 0
lives = 3
has_key = False

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN and event.key in (pygame.K_SPACE, pygame.K_UP):
            if on_ground:
                vel_y = JUMP
        elif event.type == pygame.KEYUP and event.key in (pygame.K_SPACE, pygame.K_UP):
            if vel_y < -4:
                vel_y = -4

    keys = pygame.key.get_pressed()
    dx = 0
    if keys[pygame.K_LEFT]:
        dx -= SPEED
    if keys[pygame.K_RIGHT]:
        dx += SPEED

    player.x += dx
    for wall in walls:
        if player.colliderect(wall):
            if dx > 0:
                player.right = wall.left
            elif dx < 0:
                player.left = wall.right

    vel_y = min(vel_y + GRAVITY, MAX_FALL)
    player.y += vel_y
    on_ground = False
    for wall in walls:
        if player.colliderect(wall):
            if vel_y > 0:
                player.bottom = wall.top
                on_ground = True
            elif vel_y < 0:
                player.top = wall.bottom
            vel_y = 0

    hurt = False
    if player.top > HEIGHT:
        hurt = True

    for coin in coins[:]:
        if player.colliderect(coin):
            coins.remove(coin)
            coins_collected += 1
    for spike in spikes:
        if player.colliderect(spike):
            hurt = True

    blobs.update()
    for blob in blobs:
        if player.colliderect(blob.rect):
            if vel_y > 0 and player.bottom < blob.rect.centery:
                blob.kill()
                vel_y = -8
            else:
                hurt = True

    if not has_key and player.colliderect(key):
        has_key = True

    if hurt:
        lives -= 1
        player.midbottom = start
        vel_y = 0

    camera_x = player.centerx - WIDTH // 2
    camera_x = max(0, min(camera_x, level_width - WIDTH))

    shift = camera_x // 3 % 72
    for y in range(0, HEIGHT, 72):
        for x in range(-shift, WIDTH, 72):
            screen.blit(background, (x, y))
    for image, rect in tiles:
        screen.blit(image, (rect.x - camera_x, rect.y))
    for coin in coins:
        screen.blit(coin_img, (coin.x - camera_x, coin.y))
    for spike in spikes:
        screen.blit(spikes_img, (spike.x - camera_x, spike.y - 18))
    for blob in blobs:
        spot = blob.image.get_rect(midbottom=(blob.rect.centerx - camera_x, blob.rect.bottom))
        screen.blit(blob.image, spot)
    if not has_key:
        screen.blit(key_img, (key.x - camera_x, key.y))
    screen.blit(player_img, (player.x - camera_x, player.y))
    for i in range(3):
        if i < lives:
            screen.blit(heart_img, (10 + i * 40, 8))
        else:
            screen.blit(heart_empty_img, (10 + i * 40, 8))
    if has_key:
        screen.blit(key_img, (130, 8))
    text = font.render(f"Coins: {coins_collected}", True, (255, 255, 255))
    screen.blit(text, text.get_rect(topright=(WIDTH - 12, 14)))
    pygame.display.flip()
    clock.tick(60)

pygame.quit()

Tất cả bài học trong Thế giới trò chơi

Tự luyện tập: Playground Pygame