Developing Multiplatform Game with LibGDX, part 26: Music!

Lesson 26 – Background Music

So in the previous lesson, we implemented the game sounds. But there’s no background music and no way to control it (turn it off / make it quieter).

Playing Music is actually pretty simple. Here’s what I added to SoundManager class:

public static Music bMusic = null;
public static void StopBattleMusic()
{
    if (bMusic != null)
    {
        bMusic.stop();
        bMusic = null;
    }
}

public static void PlayBattleMusic()
{
    bMusic = Gdx.audio.newMusic(Gdx.files.internal("music/music" + MathUtils.random(5) + ".mp3"));
    bMusic.setLooping(true);
    bMusic.play();
}

Music does not really work like sounds. We preload sounds, but music is different: since music files can be quite big, they are simply being read in realtime. We’ll simply pick the random tune from music0…music6, set it to looping and then play it. Now, just add a call to start music at the initialization of GameScreen and Stop the music at the disposal.

…
public GameScreen(DodgingHero _game) {
    super(_game);
    batch = new SpriteBatch();
    bg = new Background();
    SoundManager.PlayBattleMusic();
…
@Override
public void dispose()
{
    SoundManager.StopBattleMusic();
    super.dispose();
…

Alright. This part is done. Now let’s make sure we have a button to control the sound. I have four textures with sound button, to indicate different sound/music volume. 0%, 33%, 66%, 100%, called sound0, sound1, sound2 and sound3 respectively.

Adjust our Resources class by adding TextureRegionDrawable array called soundBtn;

public TextureRegionDrawable soundBtn[];

Then, load them:

soundBtn = new TextureRegionDrawable[4];
for (int i = 0; i < soundBtn.length; i++)
{
    soundBtn[i] = new TextureRegionDrawable(gameSprites.findRegion("sound" + i));
}

Now, let’s make a setting for the sound. We could make a Settings file similar to GameProgress file, but since we only have one setting (sound), let’s implement it in GameProgress. If you plan on adding more – I strongly suggest separating into your own settings file.

In our GameProgress file, add a constant to indicate MAX_SOUND_VALUE (that would be 3, 0..3), then add a new static variable called soundVolume; Also, add a save key for it.

public static final int MAX_SOUND_VOLUME = 3;
public static int soundVolume = MAX_SOUND_VOLUME;
private static final String SAVE_KEY_SOUND_VOLUME = "soundvolume";

Make sure to save/load them. In Load():

soundVolume = prefs.getInteger(SAVE_KEY_SOUND_VOLUME, MAX_SOUND_VOLUME);

In Save:

prefs.putInteger(SAVE_KEY_SOUND_VOLUME, soundVolume);

And add a new static function ToggleVolume();

public static void ToggleVolume() {
    soundVolume += 1;
    if (soundVolume > MAX_SOUND_VOLUME)
    {
        soundVolume = 0;
    }
}

Adjust the volume, if it goes over maximum value, just start anew (by disabling it, setting it to 0). Finally, let’s integrate the change to our SoundManager. When we play music/sound, we need to take the value of the soundVolume into account. First, the volume:

private static void playSoundRandomVolume(Sound sound, float min, float max)
{
    if (sound != null)
    {
        sound.play(MathUtils.random(min, max) * GameProgress.soundVolume / GameProgress.MAX_SOUND_VOLUME);
    }
}

Just multiply randomly generated value with soundVolume. Do something similar for music.

public static void PlayBattleMusic()
{
    bMusic = Gdx.audio.newMusic(Gdx.files.internal("music/music" + MathUtils.random(5) + ".mp3"));
    bMusic.setLooping(true);
    bMusic.setVolume((float)GameProgress.soundVolume / GameProgress.MAX_SOUND_VOLUME);
    bMusic.play();
}

Casting to float is important! Otherwise the division happens between two integer values and you end up dividing 1 with 3 and get zero as a result. So you have to be careful with things like these. Taking the current music volume is great. But what if we change the volume during the game? We’ll need to modify the current music volume. Create a new static function, called AdjustVolume. It will call GameProgress.ToggleVolume and then change the volume of the currently playing music to a new value.

public static void AdjustVolume()
{
    GameProgress.ToggleVolume();
    if (bMusic != null)
    {
        bMusic.setVolume((float)GameProgress.soundVolume / GameProgress.MAX_SOUND_VOLUME);
    }
}

That part is done! Now let’s make the sound button. In our GameScreen, add a new variable called

ImageButton sndBtn;

Then initialize it in our GameScreen constructor:

sndBtn = new ImageButton(game.res.soundBtn[GameProgress.soundVolume]);
sndBtn.setPosition(gameStage.getWidth() - sndBtn.getWidth() - 10, 10);
sndBtn.addListener(new ClickListener() {
    public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
        SoundManager.AdjustVolume();
        sndBtn.getStyle().imageUp = game.res.soundBtn[GameProgress.soundVolume];
        super.touchUp(event, x, y, pointer, button);
    }
});

gameStage.addActor(sndBtn);

The idea is simple: change the image after the button has been pressed, indicating the current state of the volume.

Now, if you run the game, you’ll notice that the button is there but it is not clickable. The reason is that we set up our input to GameScreen and not gameStage. We catch events in our gamescreen, but we should be doing it in our stage. Thus, we have to move our keydown function into gamestage. First, remove the InputProcessor implementation from the GameScreen and move keyDown function to stage. Make sure to change the parameters from int keyCode to InputEvent event, int keyCode.

Gdx.input.setInputProcessor(gameStage);
gameStage.addListener(new InputListener(){
      @Override
      public boolean keyDown(InputEvent event, int keyCode) {
          switch (keyCode)
          {
              case Input.Keys.RIGHT:
                  AttemptMove(1, 0);
                  break;
              case Input.Keys.LEFT:
                  AttemptMove(-1, 0);
                  break;
              case Input.Keys.UP:
                  AttemptMove(0, 1);
                  break;
              case Input.Keys.DOWN:
                  AttemptMove(0, -1);
                  break;
              default:
                  break;
          }

          return false;
      }

});

Now, try running the game and pressing the button. You’ll see that both sound/music are adjusted! Awesome! We have a sound button and we can adjust the game volume now!

Disclaimer: I’ve also tampered with game ending functions (not describing them here) to change transitions in GameScreen for html version because it was bugged otherwise. I don’t think that’s relevant to the lesson, but you can check the git commit with full code here (look for endgame boolean):

https://github.com/vladimirslav/dodginghero/commit/223be4e0ecd6518608e0207196b28642b755f8e0

This concludes our second part of lessons, where we polished the game. Next, I’m going to focus on the Android-specific stuff implementation: ads, in-app purchases.

If you want to expand the game idea – think how you can make a special abilities for every character or add more bonuses or stats (gold per bonus picked?). The effects can also be adjusted, floating numbers on damage or when player gains gold. Never stop thinking about the things that can be improved!

Play the game online: http://coldwild.com/dodge2/

Leave a Reply

Your email address will not be published. Required fields are marked *