Menu
CoddyTech

הנף את החרב

חלק מהיחידה פרויקטים במסלול ה-Pygame של Coddy. שיעור 26 מתוך 31.

מקש הרווח מניף את חרבו של האביר במשך 12 פריימים, בצד שאליו הוא פונה. רפש שהלהב נוגע בו נעלם.

האביר זוכר את הכיוון שאליו הלך לאחרונה בתוך knight.facing: (1, 0) הוא ימינה, (0, -1) למעלה. הלהב הוא תיבה בגודל 36 על 36, במרחק 34 פיקסלים ממרכזו בכיוון הזה:

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

knight.swing סופר לאחור את הפריימים של ההנפה עד 0, ומקש הרווח מתחיל הנפה חדשה רק כשהערך הוא 0, כך שהחזקת מקש הרווח לא משאירה את הלהב בחוץ. בזמן שההנפה מתבצעת, כל אויב שנוגע בתיבה מוסר באמצעות enemy.kill(), שמסירה ספרייט מכל קבוצה שהוא שייך אליה.

sword.png פונה למעלה. pygame.transform.rotate מסובבת אותו נגד כיוון השעון בזווית המתאימה לכל כיוון:

ANGLES = {(0, -1): 0, (-1, 0): 90, (0, 1): 180, (1, 0): -90}
challenge icon

אתגר

קשה
  1. הוסיפו את ANGLES מתחת לתמונות.
  2. תנו לאביר את self.facing = (1, 0), את self.swing = 0 ואת המתודה sword שלמעלה.
  3. כל חץ גם מגדיר את knight.facing: שמאלה (-1, 0), ימינה (1, 0), למעלה (0, -1), למטה (0, 1).
  4. אירוע KEYDOWN של מקש הרווח, כאשר state == "playing" וגם knight.swing == 0, מגדיר את knight.swing = 12.
  5. לפני בדיקת הפגיעה: כל עוד knight.swing > 0, הפחיתו ממנו 1 והפעילו kill() על כל אויב שה־rect שלו מתנגש עם knight.sword().
  6. בזמן ההנפה, ציירו את pygame.transform.rotate(sword_img, ANGLES[knight.facing]) במרכז של knight.sword(), אחרי האביר.

נסו בעצמכם

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


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

    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

    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)
state = "playing"

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

    if state == "playing":
        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

        room.enemies.update(room.walls + [room.door])

        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"

    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 enemy in room.enemies:
        draw(enemy.image, enemy.rect)
    if knight.hurt % 10 < 5:
        draw(knight.image, knight.rect)
    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 iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה פרויקטים

תרגלו בעצמכם: Pygame אונליין