Menu
CoddyTech

اهتزاز عند الاصطدام

جزء من قسم الصوت وإحساس اللعب في رحلة Pygame على Coddy. الدرس 22 من 23.

والآن إلى الاهتزاز. تضيف إصابة الليزر اهتزازًا بسيطًا، shake = min(shake + 6, 15)، لكي تظل سلسلة عمليات القتل واضحة. أما الاصطدام فيكلّف حياة، وينبغي أن يكون أثره أوضح: إذ يعيّن shake = 15.

لإحداث الاهتزاز، ترسم اللعبة على سطح world ثم تعرضه عند إزاحة، كما في عرض الصندوق:

world.fill((12, 14, 30))
enemies.draw(world)
# ... السفينة وأشعة الليزر والانفجارات والشرارات على world أيضًا

offset = (0, 0)
if shake > 0:
    amount = shake // 3
    offset = (random.randint(-amount, amount), random.randint(-amount, amount))
    shake -= 1
screen.fill((0, 0, 0))
screen.blit(world, offset)

لا يُرسم على السطح world سوى ساحة اللعب. تُرسم النتيجة والأرواح والموجة على الشاشة بعد العرض، فتظل ثابتة وواضحة بينما يهتز كل شيء آخر.

challenge icon

التحدي

متوسط
  1. قبل الحلقة، أنشئ world = pygame.Surface((640, 480)) وshake = 0.
  2. عندما يدمّر الليزر عدوًا، أضف shake = min(shake + 6, 15). وعندما يصطدم عدو بالسفينة، عيّن shake = 15.
  3. ارسم تعبئة الخلفية والأجسام المتحركة والانفجارات والشرارات على world بدلًا من screen.
  4. بعد ذلك، اختر الإزاحة كما هو موضّح، واملأ الشاشة باللون الأسود، ثم ارسم world عند الإزاحة. أبقِ النص بعد ذلك على الشاشة.

جرّب بنفسك

import random
import pygame

pygame.init()
screen = pygame.display.set_mode((640, 480))
pygame.display.set_caption("Star Patrol")
clock = pygame.time.Clock()
font = pygame.font.Font(None, 32)

ship_image = pygame.image.load("assets/images/ship.png").convert_alpha()
laser_image = pygame.image.load("assets/images/laser.png").convert_alpha()
red_image = pygame.image.load("assets/images/enemy_red.png").convert_alpha()
green_image = pygame.image.load("assets/images/enemy_green.png").convert_alpha()
yellow_image = pygame.image.load("assets/images/enemy_yellow.png").convert_alpha()
explosion_frames = []
for number in range(1, 5):
    explosion_frames.append(pygame.image.load(f"assets/images/explosion_{number}.png").convert_alpha())

laser_sound = pygame.mixer.Sound("assets/sounds/laser.ogg")
laser_sound.set_volume(0.4)
explosion_sound = pygame.mixer.Sound("assets/sounds/explosion.ogg")
hit_sound = pygame.mixer.Sound("assets/sounds/hit.ogg")

WAVES = [(red_image, 6, 2), (green_image, 8, 2), (yellow_image, 10, 3)]


class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = ship_image
        self.rect = self.image.get_rect(midbottom=(320, 470))
        self.mask = pygame.mask.from_surface(self.image)

    def update(self):
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT]:
            self.rect.x -= 6
        if keys[pygame.K_RIGHT]:
            self.rect.x += 6
        self.rect.clamp_ip(screen.get_rect())


class Laser(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super().__init__()
        self.image = laser_image
        self.rect = self.image.get_rect(midbottom=(x, y))
        self.mask = pygame.mask.from_surface(self.image)

    def update(self):
        self.rect.y -= 10
        if self.rect.bottom < 0:
            self.kill()


class Enemy(pygame.sprite.Sprite):
    def __init__(self, image, x, y, speed):
        super().__init__()
        self.image = image
        self.rect = self.image.get_rect(center=(x, y))
        self.mask = pygame.mask.from_surface(self.image)
        self.speed = speed

    def update(self):
        self.rect.y += self.speed


class Explosion(pygame.sprite.Sprite):
    def __init__(self, center):
        super().__init__()
        self.index = 0
        self.timer = 0
        self.image = explosion_frames[0]
        self.rect = self.image.get_rect(center=center)

    def update(self):
        self.timer += 1
        if self.timer == 5:
            self.timer = 0
            self.index += 1
            if self.index == len(explosion_frames):
                self.kill()
            else:
                self.image = explosion_frames[self.index]


def play_music(path, loops=-1):
    pygame.mixer.music.load(path)
    pygame.mixer.music.play(loops)


def burst(center):
    for i in range(16):
        particles.append({
            "x": center[0],
            "y": center[1],
            "vx": random.uniform(-4, 4),
            "vy": random.uniform(-4, 4),
            "life": random.randint(15, 30),
        })


def spawn_wave(image, count, speed):
    for i in range(count):
        x = random.randint(40, 600)
        enemies.add(Enemy(image, x, -40 - i * 120, speed))


player = Player()
lasers = pygame.sprite.Group()
enemies = pygame.sprite.Group()
explosions = pygame.sprite.Group()
particles = []
last_shot = 0
score = 0
lives = 3
wave = 0
image, count, speed = WAVES[wave]
spawn_wave(image, count, speed)
pygame.mixer.music.set_volume(0.5)
play_music("assets/music/level.ogg")

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

    if lives > 0 and wave < len(WAVES):
        player.update()
        keys = pygame.key.get_pressed()
        now = pygame.time.get_ticks()
        if keys[pygame.K_SPACE] and now - last_shot >= 250:
            lasers.add(Laser(player.rect.centerx, player.rect.top))
            last_shot = now
            laser_sound.play()
        lasers.update()
        enemies.update()
        for enemy in enemies:
            if enemy.rect.top > 480:
                enemy.kill()
                lives -= 1
                hit_sound.play()
        hits = pygame.sprite.groupcollide(enemies, lasers, True, True, pygame.sprite.collide_mask)
        for enemy in hits:
            score += 10
            explosion_sound.play()
            burst(enemy.rect.center)
            explosions.add(Explosion(enemy.rect.center))
        crashes = pygame.sprite.spritecollide(player, enemies, True, pygame.sprite.collide_mask)
        for enemy in crashes:
            lives -= 1
            hit_sound.play()
            explosions.add(Explosion(enemy.rect.center))
            burst(enemy.rect.center)
        if not enemies:
            wave += 1
            if wave < len(WAVES):
                image, count, speed = WAVES[wave]
                spawn_wave(image, count, speed)
            else:
                play_music("assets/music/win.ogg", 0)
        if lives <= 0:
            play_music("assets/music/lose.ogg", 0)
    explosions.update()
    for p in particles:
        p["x"] += p["vx"]
        p["y"] += p["vy"]
        p["life"] -= 1
    particles = [p for p in particles if p["life"] > 0]

    screen.fill((12, 14, 30))
    enemies.draw(screen)
    lasers.draw(screen)
    screen.blit(player.image, player.rect)
    explosions.draw(screen)
    for p in particles:
        pygame.draw.circle(screen, (255, 170, 60), (p["x"], p["y"]), p["life"] // 10 + 1)
    score_text = font.render(f"Score: {score}", True, (240, 240, 240))
    screen.blit(score_text, (10, 10))
    lives_text = font.render(f"Lives: {max(lives, 0)}", True, (240, 240, 240))
    screen.blit(lives_text, lives_text.get_rect(topright=(630, 10)))
    if wave < len(WAVES):
        wave_text = font.render(f"Wave {wave + 1}", True, (240, 240, 240))
        screen.blit(wave_text, wave_text.get_rect(midtop=(320, 10)))
    if lives <= 0:
        message = font.render("Game over", True, (240, 240, 240))
        screen.blit(message, message.get_rect(center=(320, 240)))
    elif wave == len(WAVES):
        message = font.render("You win!", True, (240, 240, 240))
        screen.blit(message, message.get_rect(center=(320, 240)))
    pygame.display.flip()
    clock.tick(60)

pygame.quit()

جميع دروس الصوت وإحساس اللعب

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