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() لإزالة كل عدو يتصادم مستطيله مع 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اختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس المشاريع

تدرّب بنفسك: Playground لـ Pygame