Making Your Games More Immersive Adding Sound Effects

A big part of making a game more enjoyable is adding in some effects. These could be visual, often times called particles, or they could be audio. Our post today is going to be about discussing audio effects. There are a few steps that I always get mixed up when working with audio in ebitengine. First things first If you’re going to be playing audio in your game, you need to get your hands on some first.


Apr. 19, 2024 784 words ebitengine ·

A big part of making a game more enjoyable is adding in some effects. These could be visual, often times called particles, or they could be audio. Our post today is going to be about discussing audio effects. There are a few steps that I always get mixed up when working with audio in ebitengine.

First things first

If you’re going to be playing audio in your game, you need to get your hands on some first. I’m not well versed in finding it, but there is something to keep in mind first. You want your music to be royalty free. There are different licensing rules for assets you can find, royalty free means you can use it without paying each time it is used (that doesn’t mean at times you wouldn’t be paying for it up front).

Let us look at two places that I’ve used for accessing these resources:

  1. Pixabay.com
  2. Itch.io

Now that you know where you can find some sounds, and you know what ambiance you are looking for for your game (you do know what types of sounds you’re looking for right? right?) you’re ready to go. Pick a few sounds and start downloading.

Great, now that you’ve downloaded your sounds copy over your files to your game directory and jump into your editor.

Jumping into the code

Before we get started, lets take a minute and think about how this will need to work. There are two main workflows that we’ll have with our sounds.

  1. Loading
  2. Playback

Load

Lets breakdown what it takes to load our sounds.

  1. An ebitengine audio context with a sample rate
  2. Loading the file
  3. Create a stream
  4. Create an ebitengine audio player from said stream and audio context
    • Can be an infinite loop
    • Can be a one off sound

Playback

Playback can take one of two forms depending on if it is a loop or a single sound. Neither of them are that difficult, however there is a little thing to know about the single sounds.

Loop

Single

The Code

Now that we have a high level understanding of how the audio works in ebitengine, lets look at some code giving use more detail of how it works.

package main

import (
	"log/slog"
	"github.com/hajimehoshi/ebiten/v2/audio"
	"github.com/hajimehoshi/ebiten/v2/audio/mp3"
)

var audioContext *audio.Context
var loop *audio.Player
var single *audio.Player

func main() {
	audioContext := audio.NewContext(48_000) // This creates our new audio context
	var err error
	loop, err = createLoop()
	if err != nil {
		slog.Info("error loading loop", "error", err)
		os.Exit(2)
	}

	single, err = createSingle()
	if err != nil {
		slog.Info("error loading single", "error", err)
		os.Exit(2)
	}

	loopPlayer.SetVolume(0.33)
	loopPlayer.Play()


	// Of course we'll need an ebitengine game setup as well.
	// ...
}

func gameEvent() {
	// We need to rewind our sound, as it could have played to the end of the byte stream already
	single.Rewind()
	// Now that it is queued up, lets play it
	single.Play()
}

func createLoop() (*audio.Player, error) {
	// Selects the file to open
	input := mustLoadReader("path/to/loop.mp3")
	// Assuming we're working with an mp3, this decodes the bytes of an mp3 into a byte stream
	stream, err := mp3.DecodeWithoutResampling(input)
	if err != nil {
		return nil, err
	}
	// Turns the byte stream into a reader that will loop
	loop := audio.NewInfiniteLoop(stream, 123456)
	// Takes the infinite stream and turns it into a player
	loopPlayer := audioContext.NewPlayer(loop)

	return loopPlayer
}

func createSingle() (*audio.Player, error) {
	// Selects the file to open
	input := mustLoadReader("path/to/single.mp3")
	// Assuming we're working with an mp3, this decodes the bytes of an mp3 into a byte stream
	stream, err := mp3.DecodeWithoutResampling(input)
	if err != nil {
		return nil, err
	}
	// Takes the stream and turns it into a player
	singlePlayer := audioContext.NewPlayer(stream)

	return singlePlayer
}

// Helper function to open the file from disk, will panic if file cannot be opened
func mustLoadReader(path string) io.ReadCloser {
	// Opens the file and acquires an io.ReadCloser
	in, err := os.Open(path)
	if err != nil {
		slog.Error("error opening file", "path", path, "error", err)
		panic(err)
	}
	return in
}

Wrap It Up

When I was first learning to add sounds to my games in ebitengine I struggled trying to understand the different steps that it took to do so. I hope that this post has helped to makes sense for you on the steps of playing sounds and music in your games. So, don’t forget:

  1. Create an audio context
  2. Open the file and get a reader
  3. Decode the stream
  4. Create a player

If you can get those steps down you’ll be well on your way of creating more immersive games.