Menu
CoddyTech

Herzen

Lektion 7 von 20 im Kurs Pygame-Projekt: Verteidige den Bergfried von Coddy.

Ein Monster, das den Ritter erreicht, kostet ein Herz. Ohne eine Pause nach einem Treffer würde ein Monster, das auf dem Ritter steht, in jedem Frame ein Herz abziehen, alle fünf innerhalb einer Zwölftelsekunde. Ein Treffer startet also einen Zähler, hurt, und solange er herunterzählt, kann nichts dem Ritter schaden:

def take_hits(self):
    knight = self.knight
    if knight.hurt > 0:
        knight.hurt -= 1
    elif pygame.sprite.spritecollideany(knight, self.monsters):
        knight.hearts -= 1
        knight.hurt = 75

spritecollideany(sprite, group) gibt das erste Sprite in der Gruppe zurück, dessen Rect überlappt, oder None.

Das Spiel hat jetzt Zustände: "playing" und "lost". Die Schleife führt night.update nur während des Spielens aus, sodass alles dort stehen bleibt, wo es ist, wenn die Herzen aufgebraucht sind.

Solange hurt herunterzählt, zeichne den Ritter nur, wenn knight.hurt % 10 < 5: Er blinkt, und der Spieler sieht, warum nichts Schaden anrichtet.

challenge icon

Aufgabe

Mittel
  1. Gib dem Ritter hearts = 5, max_hearts = 5 und hurt = 0; setze bei Night state = "playing".
  2. Schreibe take_hits wie oben; wenn die Herzen 0 erreichen, setze self.state = "lost". Rufe die Funktion zuletzt in update auf.
  3. Aktualisiere in der Schleife nur bei if night.state == "playing" und zeichne den Ritter nur, wenn knight.hurt % 10 < 5.
  4. Lade heart.png und heart_empty.png als heart_img und empty_img. Zeichne für jedes der max_hearts ein volles oder leeres Herz an die Position (8 + i * 34, 6).
  5. Erstelle big_font = pygame.font.Font("assets/fonts/pixel.ttf", 48) und zeichne bei einer Niederlage "The keep has fallen" zentriert bei (360, 200).

Probier es selbst

import random
import pygame

WIDTH, HEIGHT = 720, 480
TILE = 48
NIGHT = 60 * 60 * 4
WHITE = (240, 240, 240)

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


floor_img = load("floor.png")
wall_img = load("wall.png")
knight_img = load("knight.png")
MONSTERS = {
    "slime": {"image": load("slime.png"), "speed": 1.0},
}

courtyard = pygame.Rect(TILE, TILE, WIDTH - 2 * TILE, HEIGHT - 2 * TILE)
background = pygame.Surface((WIDTH, HEIGHT))
for row in range(HEIGHT // TILE):
    for col in range(WIDTH // TILE):
        x = col * TILE
        y = row * TILE
        if courtyard.collidepoint(x, y):
            background.blit(floor_img, (x, y))
        else:
            background.blit(wall_img, (x, y))


def draw(image, center):
    screen.blit(image, image.get_rect(center=center))


def clock_text(frames):
    seconds = frames // 60
    return f"{seconds // 60}:{seconds % 60:02d}"


def edge_point():
    side = random.randint(0, 3)
    if side == 0:
        return (random.randint(0, WIDTH), -24)
    if side == 1:
        return (random.randint(0, WIDTH), HEIGHT + 24)
    if side == 2:
        return (-24, random.randint(0, HEIGHT))
    return (WIDTH + 24, random.randint(0, HEIGHT))


class Knight(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = knight_img
        self.rect = pygame.Rect(0, 0, 24, 24)
        self.rect.center = courtyard.center
        self.speed = 3

    def move(self, keys):
        if keys[pygame.K_LEFT]:
            self.rect.x -= self.speed
        if keys[pygame.K_RIGHT]:
            self.rect.x += self.speed
        if keys[pygame.K_UP]:
            self.rect.y -= self.speed
        if keys[pygame.K_DOWN]:
            self.rect.y += self.speed
        self.rect.clamp_ip(courtyard)


class Monster(pygame.sprite.Sprite):
    def __init__(self, kind, pos):
        super().__init__()
        self.kind = kind
        self.image = MONSTERS[kind]["image"]
        self.speed = MONSTERS[kind]["speed"]
        self.pos = pygame.Vector2(pos)
        self.rect = pygame.Rect(0, 0, 28, 28)
        self.rect.center = self.pos

    def update(self, target):
        step = pygame.Vector2(target) - self.pos
        if step.length() > 0:
            self.pos += step.normalize() * self.speed
        self.rect.center = self.pos


class Night:
    def __init__(self):
        self.knight = Knight()
        self.time = 0
        self.monsters = pygame.sprite.Group()

    def spawn(self):
        if self.time % 60 == 0:
            self.monsters.add(Monster("slime", edge_point()))

    def update(self, keys):
        self.time += 1
        self.knight.move(keys)
        self.spawn()
        self.monsters.update(self.knight.rect.center)


night = Night()

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

    night.update(pygame.key.get_pressed())

    screen.blit(background, (0, 0))
    knight = night.knight
    for monster in night.monsters:
        draw(monster.image, monster.rect.center)
    draw(knight.image, knight.rect.center)
    left = font.render(clock_text(max(0, NIGHT - night.time)) + " until dawn", True, WHITE)
    screen.blit(left, left.get_rect(center=(WIDTH // 2, 24)))
    pygame.display.flip()
    clock.tick(60)

pygame.quit()

Alle Lektionen in Pygame-Projekt: Verteidige den Bergfried

Übe selbstständig: Pygame-Playground