Menu
CoddyTech

Tiefer in die Festung

Teil des Abschnitts Projekte der Pygame-Journey von Coddy. Lektion 28 von 31.

Die Festung hat drei Räume. Durch die offene Tür kommt der Ritter beim P des nächsten Raums heraus. X ist neu: eine Spinne, die nach oben und unten klettert.

MAP2 = [
    "##############",
    "#P...#...X...#",
    "#....#.......#",
    "#..S.#..###..#",
    "#....#..#K...#",
    "#.......#....#",
    "##.######X####",
    "#....S.......#",
    "#.H..........#",
    "###D##########",
]
MAP3 = [
    "##############",
    "#....#...X...#",
    "#.P..#.......#",
    "#....#..S....#",
    "#..S.....###.D",
    "#....#...#K.S#",
    "###.###X.#...#",
    "#........#...#",
    "#..H.....#...#",
    "##############",
]
ROOMS = [MAP, MAP2, MAP3]

level ist die Nummer des Raums, in dem sich der Ritter befindet, beginnend bei 0. Wenn er durch die Tür geht, wird der nächste Raum anhand seiner Karte erstellt und der Ritter an dessen Startposition gesetzt:

level += 1
room = Room(ROOMS[level])
knight.rect.center = room.start

Deshalb gibt es die Klasse Room: Eine Zeile erstellt einen ganzen Raum mit Wänden, Schlüssel, Tür, Gegnern und Tränken. Der Ritter bleibt dasselbe Objekt, also kommen seine Herzen mit ihm mit. Vorläufig führt die Tür des letzten Raums nirgendwohin: Gehe nur weiter, solange level < len(ROOMS) - 1 gilt.

Eine Spinne ist ein Enemy mit dx = 0 und einem dy. Dasselbe update lässt sie an Wänden umkehren.

challenge icon

Aufgabe

Schwer
  1. Füge MAP2, MAP3 und ROOMS von oben unter MAP hinzu.
  2. Setze vor der Schleife level = 0 und room = Room(ROOMS[level]).
  3. In Room fügt ein X Enemy(spider_img, cell_rect(col, row), 0, speed) mit speed = random.choice([-2, 2]) hinzu.
  4. Nachdem sich die Tür geöffnet hat: Wenn room.door_open gilt, der Ritter room.door berührt und level < len(ROOMS) - 1 gilt, erhöhe level um 1, erstelle den Raum und bewege den Ritter zu dessen Startposition.
  5. Zeige f"Room {level + 1} of {len(ROOMS)}" in (240, 240, 240) mit der rechten oberen Ecke bei (660, 8) an: label.get_rect(topright=(660, 8)).

Probier es selbst

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")
ANGLES = {(0, -1): 0, (-1, 0): 90, (0, 1): 180, (1, 0): -90}


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.potions = []
        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))
                elif char == "H":
                    self.potions.append(cell_rect(col, row))

    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
        self.hearts = 3
        self.hurt = 0
        self.facing = (1, 0)
        self.swing = 0

    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

    def sword(self):
        box = pygame.Rect(0, 0, 36, 36)
        box.centerx = self.rect.centerx + self.facing[0] * 34
        box.centery = self.rect.centery + self.facing[1] * 34
        return box


room = Room(MAP)
knight = Knight(room.start)
state = "playing"

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE and state == "playing" and knight.swing == 0:
                knight.swing = 12

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

        if knight.swing > 0:
            knight.swing -= 1
            for enemy in room.enemies:
                if enemy.rect.colliderect(knight.sword()):
                    enemy.kill()

        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"

        index = knight.rect.collidelist(room.potions)
        if index != -1 and knight.hearts < 3:
            knight.hearts += 1
            room.potions.pop(index)

    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 potion in room.potions:
        draw(potion_img, potion)
    for enemy in room.enemies:
        draw(enemy.image, enemy.rect)
    if knight.hurt % 10 < 5:
        draw(knight.image, knight.rect)
    if knight.swing > 0:
        blade = pygame.transform.rotate(sword_img, ANGLES[knight.facing])
        draw(blade, knight.sword())
    for i in range(3):
        if i < knight.hearts:
            screen.blit(heart_img, (8 + i * 40, 6))
        else:
            screen.blit(empty_img, (8 + i * 40, 6))
    if room.has_key:
        screen.blit(key_img, (136, 6))
    if state == "lost":
        message = font.render("Game over", True, (240, 240, 240))
        screen.blit(message, message.get_rect(center=(336, 240)))
    pygame.display.flip()
    clock.tick(60)

pygame.quit()
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Projekte

Übe selbstständig: Pygame-Playground