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

Published: (January 15, 2026 at 10:00 AM EST)
3 min read
Source: Dev.to

Source: Dev.to

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:

ParameterDescription
notes (score)Specifies the pitch
tonesType of waveform (e.g. p = pulse wave)
volumesVolume
effectsSound effect (e.g. f = fade out)
speedPlayback speed (lower values play faster)
# main.py
pyxel.sound(0).set(
    "c4g3",
    tones="p",
    volumes="76",
    effects="f",
    speed=20
)

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)

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

# Run the game
Game()

Kajiru

Fullscreen Controls

  • 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!

Back to Blog

Related posts

Read more »