Menu
CoddyTech

أصوات دورية النجوم

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

تعمل لعبة Star Patrol من القسم 3، لكنها صامتة. يضيف هذا الفصل إليها الصوت والإحساس الواردين في هذا القسم، بدءًا من المؤثرات الصوتية والموسيقى.

يرتبط كل صوت بلحظة في اللعبة لها سطر برمجي بالفعل:

  • يُضاف ليزر: laser.ogg، بصوت خافت، لأنه يُشغَّل عدة مرات في الثانية؛
  • يدمّر الليزر عدوًا: explosion.ogg؛
  • تُفقَد حياة، في كلا الموضعين: hit.ogg.

تتبع الموسيقى حالات اللعبة. تتكرر level.ogg من البداية. وتبدأ نغمة الفوز عندما تُزال الموجة الأخيرة:

if not enemies:
    wave += 1
    if wave < len(WAVES):
        ...
    else:
        play_music("assets/music/win.ogg", 0)
if lives <= 0:
    play_music("assets/music/lose.ogg", 0)

يوجد السطران داخل دالة التحديث، التي لا تعمل إلا أثناء تشغيل اللعبة. في الإطار الذي تنتهي فيه اللعبة، يُنفَّذان مرة واحدة؛ ومن الإطار التالي، يتم تخطي التحديث، لذا لا تبدأ النغمة من جديد.

challenge icon

التحدي

متوسط
  1. حمّل laser.ogg (مستوى الصوت 0.4) وexplosion.ogg وhit.ogg من assets/sounds/ قبل الحلقة.
  2. شغّل صوت الليزر عند إضافة ليزر، وصوت الانفجار عندما يدمّر الليزر عدوًا، وصوت الإصابة في كلا الموضعين اللذين تُفقَد فيهما حياة.
  3. اكتب play_music(path, loops=-1) وشغّل assets/music/level.ogg قبل الحلقة، مع ضبط مستوى صوت الموسيقى على 0.5.
  4. شغّل win.ogg مرة واحدة عند اجتياز الموجة الأخيرة، وlose.ogg مرة واحدة عند نفاد الأرواح، من assets/music/.

جرّب بنفسك

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

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 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()
last_shot = 0
score = 0
lives = 3
wave = 0
image, count, speed = WAVES[wave]
spawn_wave(image, count, speed)

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
        lasers.update()
        enemies.update()
        for enemy in enemies:
            if enemy.rect.top > 480:
                enemy.kill()
                lives -= 1
        hits = pygame.sprite.groupcollide(enemies, lasers, True, True, pygame.sprite.collide_mask)
        for enemy in hits:
            score += 10
            explosions.add(Explosion(enemy.rect.center))
        crashes = pygame.sprite.spritecollide(player, enemies, True, pygame.sprite.collide_mask)
        for enemy in crashes:
            lives -= 1
            explosions.add(Explosion(enemy.rect.center))
        if not enemies:
            wave += 1
            if wave < len(WAVES):
                image, count, speed = WAVES[wave]
                spawn_wave(image, count, speed)
    explosions.update()

    screen.fill((12, 14, 30))
    enemies.draw(screen)
    lasers.draw(screen)
    screen.blit(player.image, player.rect)
    explosions.draw(screen)
    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