Menu
CoddyTech

Âm thanh đội tuần tra sao

Thuộc phần Âm thanh và cảm giác chơi trong hành trình Pygame của Coddy. Bài học 20 trên 23.

Star Patrol ở phần 3 đã hoạt động, nhưng không có âm thanh. Chương này sẽ thêm âm thanh và cảm giác như trong phần này, bắt đầu với hiệu ứng âm thanh và nhạc.

Mỗi âm thanh ứng với một khoảnh khắc mà game đã có sẵn một dòng lệnh:

  • thêm tia laser: laser.ogg, âm lượng nhỏ vì nó phát nhiều lần mỗi giây;
  • tia laser phá hủy kẻ địch: explosion.ogg;
  • mất một mạng, ở cả hai nơi: hit.ogg.

Nhạc thay đổi theo trạng thái của game. level.ogg lặp lại từ đầu. Giai điệu chiến thắng bắt đầu khi đợt cuối cùng bị tiêu diệt hết:

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)

Cả hai dòng lệnh đều nằm trong hàm update, vốn chỉ chạy khi game đang hoạt động. Chúng chạy một lần ở khung hình game kết thúc; từ khung hình tiếp theo, hàm update bị bỏ qua nên giai điệu không phát lại.

challenge icon

Thử thách

Trung bình

Thêm âm thanh và nhạc cho Star Patrol.

  1. Tải laser.ogg (âm lượng 0.4), explosion.ogg và hit.ogg từ assets/sounds/; phát chúng khi bắn tia laser, tiêu diệt kẻ địch và mỗi khi mất một mạng.
  2. Viết play_music(path, loops=-1); phát assets/music/level.ogg với âm lượng nhạc 0.5.
  3. Phát win.ogg một lần khi chiến thắng và lose.ogg một lần khi thua, từ assets/music/.

Nhấn Chơi và bắn: bạn sẽ nghe tiếng tia laser xen lẫn tiếng nhạc. Sau đó nhấn Kiểm tra.

Tự làm thử

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

Tất cả bài học trong Âm thanh và cảm giác chơi

Tự luyện tập: Playground Pygame