Menu
CoddyTech

The Key and the Door

Part of the Projects section of Coddy's Pygame journey. Lesson 23 of 31.

The door in the right wall is locked. The knight picks up the key by walking over it, and with the key, the door opens when he touches it.

A locked door is one more thing that blocks the knight. A method on Room gives the list of Rects that stop him:

def blocks(self):
    if self.door_open:
        return self.walls
    return self.walls + [self.door]

Touching the door has a catch: move pushes the knight out of it every frame, so his Rect never overlaps the door. knight.rect.inflate(8, 8) is a copy of his Rect grown by 4 pixels on every side, and that copy does overlap the door he is pressed against.

if room.has_key and knight.rect.inflate(8, 8).colliderect(room.door):
    room.door_open = True

Past the open door is the edge of the window, so keep the knight inside with clamp_ip. In a later lesson the doorway leads to the next room. Show the key at the top while he carries it.

challenge icon

Challenge

Medium
  1. In Room, set self.has_key = False and self.door_open = False. A K sets self.key = cell_rect(col, row); a D sets self.door to the 48 by 48 Rect of its tile.
  2. Add the blocks method above and move the knight with knight.move(dx, dy, room.blocks()).
  3. After moving: knight.rect.clamp_ip(screen.get_rect()); when he has no key and touches room.key, set room.has_key = True; then open the door as above.
  4. Draw the door with screen.blit(door_img, room.door) while it is closed, the key with draw(key_img, room.key) until it is picked up, and key_img at (136, 6) while room.has_key is true.

Try it yourself

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


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

    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)
    draw(knight.image, knight.rect)
    pygame.display.flip()
    clock.tick(60)

pygame.quit()

All lessons in Projects

Practice on your own: Pygame playground