DEV Community

Kajiru
Kajiru

Posted on

Getting Started with 2D Games Using Pyxel (Part 12): Playing Sound Effects

Playing Sound Effects

In this article, we will play sound effects (SE) in a game.

Instead of using external audio files, we will introduce how to create simple sound effects directly in code by writing musical notes.

The complete source code is provided at the end.

1. Registering a Sound

First, register a sound inside the constructor of the Game class.

The argument of pyxel.sound() specifies the sound ID.

  • Sound ID: This number is used later when playing the sound.

Next, pass the following data to the set() method:

  • Notes (score): Specifies the pitch
  • tones: Type of waveform (e.g. p = pulse wave)
  • volumes: Volume
  • effects: Sound effect (e.g. f = fade out)
  • speed: Playback speed (lower values play faster)
# main.py
pyxel.sound(0).set(
    "c4g3",
    tones="p",
    volumes="76",
    effects="f",
    speed=20
)
Enter fullscreen mode Exit fullscreen mode

2. Playing a Sound

With pyxel.play(), up to four sounds can be played simultaneously.

  • The first argument specifies the channel number (0–3).
    • If the same channel is used, the new sound overwrites the previous one.
  • The second argument specifies the sound ID.
# main.py
pyxel.play(0, 0)
Enter fullscreen mode Exit fullscreen mode

Complete Code

Below is the complete code implementing the features introduced in this article.

(The sprite.py file is the same as in the previous chapter.)

# main.py
import pyxel
import math
import random
import sprite

W, H = 160, 120
SHIP_SPD = 1.4

ASTEROID_INTERVAL = 20
ASTEROID_LIMIT = 30

ASTEROID_SPD_MIN = 1.0
ASTEROID_SPD_MAX = 2.0
ASTEROID_DEG_MIN = 30
ASTEROID_DEG_MAX = 150

BULLET_SPD = 3

# Game
class Game:
    def __init__(self):
        """ Constructor """

        # Game over flag
        self.game_over_flg = False

        # Initialize score
        self.score = 0

        # Initialize player
        self.ship = sprite.ShipSprite(W / 2, H - 40)
        deg = 0 if random.random() < 0.5 else 180
        self.ship.move(SHIP_SPD, deg)

        # Asteroids
        self.asteroid_time = 0
        self.asteroids = []

        # Bullets
        self.bullets = []

        # Initialize Pyxel
        pyxel.init(W, H, title="Hello, Pyxel!!")
        pyxel.load("shooter.pyxres")

        # Sound (shot)
        pyxel.sound(0).set(
            "c4g3",
            tones="p",
            volumes="76",
            effects="f",
            speed=20
        )

        # Sound (hit)
        pyxel.sound(1).set(
            "e4d4c4",
            tones="p",
            volumes="76",
            effects="n",
            speed=20
        )

        pyxel.run(self.update, self.draw)

    def update(self):
        """ Update process """

        # Game over
        if self.game_over_flg:
            return

        # Update player
        self.ship.update()
        self.control_ship()
        self.overlap_spr(self.ship)

        self.check_interval()  # Add asteroids

        # Update asteroids
        for asteroid in self.asteroids:
            asteroid.update()
            self.overlap_spr(asteroid)
            # Collision check (asteroid x player)
            if asteroid.intersects(self.ship):
                self.game_over_flg = True

        # Update bullets (reverse order)
        for bullet in self.bullets[::-1]:
            bullet.update()
            # Remove if outside screen
            if bullet.y < 0:
                self.bullets.remove(bullet)
                continue
            # Collision check (bullet x asteroid)
            for asteroid in self.asteroids[::-1]:
                if asteroid.intersects(bullet):
                    self.score += 1
                    self.bullets.remove(bullet)
                    self.asteroids.remove(asteroid)

                    # Sound (hit)
                    pyxel.play(1, 1)
                    return

    def draw(self):
        """ Draw process """
        pyxel.cls(0)

        # Game over
        if self.game_over_flg:
            msg = "GAME OVER"
            pyxel.text(W / 2 - len(msg) * 2, H / 2, msg, 13)

        # Draw score
        pyxel.text(10, 10, f"SCORE:{self.score:04}", 12)

        # Draw player
        self.ship.draw()

        # Draw asteroids
        for asteroid in self.asteroids:
            asteroid.draw()

        # Draw bullets
        for bullet in self.bullets:
            bullet.draw()

    def control_ship(self):
        """ Player action """
        if pyxel.btnp(pyxel.KEY_SPACE):
            self.ship.flip_x()

            # Fire bullet
            bullet = sprite.BulletSprite(self.ship.x, self.ship.y)
            bullet.move(BULLET_SPD, 270)
            self.bullets.append(bullet)

            # Sound (shot)
            pyxel.play(0, 0)

    def overlap_spr(self, spr):
        """ Wrap sprite around the screen """
        if spr.x < -spr.w:
            spr.x = W
            return
        if W < spr.x:
            spr.x = -spr.w
            return
        if spr.y < -spr.h:
            spr.y = H
            return
        if H < spr.y:
            spr.y = -spr.h
            return

    def check_interval(self):
        # Asteroid spawn interval
        self.asteroid_time += 1
        if self.asteroid_time < ASTEROID_INTERVAL:
            return
        self.asteroid_time = 0

        # Limit number of asteroids
        if ASTEROID_LIMIT < len(self.asteroids):
            return

        # Add asteroid
        x = random.random() * W
        y = 0
        spd = random.uniform(ASTEROID_SPD_MIN, ASTEROID_SPD_MAX)
        deg = random.uniform(ASTEROID_DEG_MIN, ASTEROID_DEG_MAX)
        asteroid = sprite.AsteroidSprite(x, y)
        asteroid.move(spd, deg)
        self.asteroids.append(asteroid)

def main():
    """ Main process """
    Game()

if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

Final Words

Thank you for reading this far.

I hope this series becomes a starting point for your own game development journey.

If you enjoyed it, a 👍 would mean a lot!

Top comments (0)