Pygame RPG Tutorial – Music and Sound

It’s about time we added some flair to our Pygame RPG with the addition of Music and Sound. You can’t hope to create an immersive and engaging game without an appropriate soundtrack. Similarly, the various features and actions in your game will always remain bland without good sound effects which emphasize the action being carried out.


Music Manager

I started this game as a smaller project, which is why I didn’t start by separating everything into individual header files for each object. That approach didn’t really age very well, and I’ve decided to begin putting new features into other files (where possible).

The Music Manager we create in this tutorial will be created in a separate file which we will import into the main file.

import pygame
from pygame.locals import *

class MusicManager:
    def __init__(self):
        super().__init__()
        self.volume = 0.05  # Default Volume

    def playsoundtrack(self, music, num, vol):
        pygame.mixer.music.set_volume(vol)
        pygame.mixer.music.load(music)
        pygame.mixer.music.play(num)

    def playsound(self, sound, vol):
        sound.set_volume(vol)
        sound.play()

    def stop(self):
        pygame.mixer.music.stop()

It’s not much honestly, but I got tired of adding in three to four lines every time I wanted to play a sound. Instead I just created two functions, one for sound and one for music. All you have to do is pass in the correct parameters, and you’re done.

Importing the File

music_manager is the name of the file I saved earlier. It’s upto you whatever you call it, but it’s generally standard practice to have the name of the file match the class in it.

from music_manager import MusicManager

# freq, size, channel, buffsize
pygame.mixer.pre_init(44100, 16, 1, 512)
pygame.init() 

You’ll notice an additional line that we added right before the pygame.init() function. This pre-initializes the pygame mixer, improving and eliminating several problems that occur. In my case, without it I face audio lag (delayed sound effects).

The settings you use don’t matter too much. Don’t mess around with the first or second generally. The third is either 1 for mono, or 2 for stereo. And of course, the 4th is the size of the audio buffer.


Loading in the Sound

Here I have created a few arrays and variables in which we have stored several sounds and music file names. We will be using these later on in the tutorial.

# Music and Sound
soundtrack = ["background_village.wav", "battle_music.wav", "gameover.wav"]
swordtrack = [pygame.mixer.Sound("sword1.wav"), pygame.mixer.Sound("sword2.wav")]
fsound = pygame.mixer.Sound("fireball_sound.wav")
hit = pygame.mixer.Sound("enemy_hit.wav")

mmanager = MusicManager()
mmanager.playsoundtrack(soundtrack[0], -1, 0.05)

In the last two lines, we create the MusicManager() object, and then call the playsoundtrack function using the first soundtrack in the soundtrack array, which is the background music. The second parameter states that it should repeat infinitely, and the third sets the volume. You might want to adjust this, based on your

If you have any trouble, at any point in this tutorial with the Pygame Mixer library, refer to our complete tutorial on it.


Adding Sound

The second place I want to insert music is when we load up a world. Basically, I want to switch from the light background music to a more battle heavy music. To implement this, I’ll add a single line into each one of the world functions in the Event Handler.

      def world1(self):
            self.world = 1
            ...
            mmanager.playsoundtrack(soundtrack[1], -1, 0.05)
            

      def world2(self):
            self.world = 2
            ...
            mmanager.playsoundtrack(soundtrack[1], -1, 0.05)
 

      def world3(self):
            self.world = 3
            ...
            mmanager.playsoundtrack(soundtrack[1], -1, 0.05)

I’ve copied the same line over into each of the world functions, but you might want to have separate themes for each stage, so I left it this way.


Player Attacking Sound

The Player’s sword is an integral part of his character, and having some sword slashing sound is an absolute must. We’re going to do something interesting here where we swap between two different sounds as the player attacks. You can see that earlier I imported two sounds into the swordtrack array. This is method also allows us to add in many more sounds.

Here is the new and modified attack function (only showing the relevant parts of the code). Focus on the last 5 lines, which have the sound related code.

We’ll be using a new variable, self.slash, which I like to refer to as the slash counter. This will store the current sword sound to be playing.

    def attack(self):
          if cursor.wait == 1: return
          
          # If attack frame has reached end of sequence, return to base frame      
          if self.attack_frame > 10:
                self.attack_frame = 0
                self.attacking = False

          if self.attack_frame == 0:
                mmanager.playsound(swordtrack[self.slash], 0.05)
                
                self.slash += 1
                if self.slash >= 2:
                      self.slash = 0

Basically the the first if statement (if self.attack_frame == 0), ensures that the sound is only played on the first attack frame. The next if statement ensures that the self.slash counter doesn’t exceed too. It resets the self.slash counter back to zero.

If you want to increase the number of sounds, just increase the limit from two to whatever number of you wish. Remember to actually have an equal number of sounds. This technique is pretty nice for sword combos, where you want a different sound produced on each attack in the combo.


Fireball Sound

It would be really bland and boring if we were to just fire of a powerful attack like the fireball without some accompanying sound. It’s a nice long whooshing sound that gives the impression of something being launched powerfully.

        if event.type == pygame.KEYDOWN and cursor.wait == 0:
            if event.key == pygame.K_m and player.magic_cooldown == 1:
                  if player.mana >= 6:
                        player.mana -= 6
                        player.attacking = True
                        fireball = FireBall()
                        Fireballs.add(fireball)
                        mmanager.playsound(fsound, 0.3)

Again, due to our Music Manager code, this is super simple and we just have to add a single line.


Enemy Death Sound

It’s just a small “blip” sort of noise, like you would see in one of those 8 bit games. Nothing fancy, but much better than the complete silence as you chop down your foes.

            # Activates upon either of the two expressions being true
            if hits and player.attacking == True or f_hits:
                  self.kill()
                  mmanager.playsound(hit, 0.05)

Game Over Music

Over here we are going to add some Game Over Music. Once the player health drops to zero, he will call the playsoundtrack() function, playing the third soundtrack we have available.

            if self.health <= 0:
                self.kill()
                mmanager.stop()
                mmanager.playsoundtrack(soundtrack[2], -1, 0.1)
                pygame.display.update()

Video Demonstration

Don’t mind the choppy framerate. It just looks like that when I’m screen recording it. It’s actually a lot smoother when you actually run it for yourself.


Downloads & Next Tutorial

You can download the sounds used in this tutorial using the download button below. You can also move to the next tutorial in the series where we will be discussing how to improve performance for our game.


This marks the end of the Pygame RPG Music and Sound tutorial. Any suggestions or contributions for CodersLegacy are more than welcome. Questions regarding the tutorial content can be asked in the comments section below.

Subscribe
Notify of
guest
6 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments