1
\$\begingroup\$

I understand that all sound playing in LibGDX happens asynchronously (for good reasons), however I'm now struggling to do something as simple as:

(Character dies)

  • Play a sound
  • When the sound has finished playing, play another sound
  • When the second sound has finished, display a button that has to be pressed for the game to start over

My code is as follows:

if (hasDied){
  dieSound.play();
  gameOverSound.play();
  showButton();
}

But what it does is, obviously, play the two sounds at the same time, while displaying the button. It's even possible for the user to press the button while the sounds are playing.

Now, I can put a check in the button listener to not do anything if dieSound.isPlaying() or gameOverSound.isPlaying(), but of course that does nothing to avoid concurrent playing of the two sounds and still displays the button before the sounds have finished playing. Now I see that Music objects, as opposite to Sounds, have a setOnCompletionListener() method, which could perhaps be (ab)used this way:

if (hasDied){

  gameOverMusicCompletionListener = new OnCompletionListener(){
    @Override
    public void onCompletion(Music a) {  
       showButton();  
    }
  }

  dieMusicCompletionListener = new OnCompletionListener(){
    @Override
    public void onCompletion(Music a) {  
       gameOverMusic.setOnCompletionListener(gameOverMusicCompletionListener);
       gameOverMusic.play();  
    }
  }

  dieMusic.setOnCompletionListener(dieMusicCompletionListener);
  dieMusic.play();
}

This looks quite kludgy to me, isn't there another way to accomplish what I'd like?

\$\endgroup\$
3
  • \$\begingroup\$ Because of how openAL handles sounds this is not possible. You have to do it manually. \$\endgroup\$ – Charanor Jul 24 '17 at 14:30
  • \$\begingroup\$ Does "manually" mean the way I showed above using completion listeners? \$\endgroup\$ – persson Jul 24 '17 at 14:35
  • \$\begingroup\$ Actually, I retract my earlier comment. I found a (possible) solution. See my answer in a minute. \$\endgroup\$ – Charanor Jul 24 '17 at 15:11
0
\$\begingroup\$

I may have found a solution, although it is untested and will probably only work on desktop.

You can query the state of a sound instance using AL10.alGetSourcei(id, AL10.AL_SOURCE_STATE). This will return an int constant signifying the state of the source. To check if the source is playing you check if the returned value is AL10.AL_PLAYING like so:

// id = the value returned by sound.play()
boolean isPlaying = AL10.alGetSourcei(id, AL10.AL_SOURCE_STATE) == AL10.AL_PLAYING;

The problem is that we cannot access the AL10 class and methods from the Core project, so we have to delegate the task to a class created in the Desktop project.

  1. In your Core project create an interface like so:

    public interface SoundStateChecker {
        public boolean isPlaying(int id);
    }
    
  2. In your Desktop project define a class like so:

    public class ALStateChecker implements SoundStateChecker {
        @Override
        public boolean isPlaying(int id) {
            return AL10.alGetSourcei(id, AL10.AL_SOURCE_STATE) == AL10.AL_PLAYING;
        }
    }
    
  3. Now in your Lwjgl3Launcher in your Desktop project where you create your Lwjgl3Application you simply supply an instance of your ALStateChecker to your main game class:

    new Lwjgl3Application(new MainClass(new ALStateChecker()), config);
    

You can now use a reference to the state checker to see if the sound is playing:

int dieSoundID = -1;
int gameOverSoundID = -1;
boolean hasShownButton = false;

if (hasDied){
    // If this sound hasn't played before...
    if(dieSoundID == -1)
        dieSoundID = dieSound.play();

    // If this sound hasn't played before AND dieSound isn't playing...
    if(!stateChecker.isPlaying(dieSoundID) && gameOverSoundID == -1)
        gameOverSoundID = gameOverSound.play();

    // If neither sound is playing AND they have been played before 
    // AND we haven't shown the button.
    if(!stateChecker.isPlaying(dieSoundID) && !stateChecker.isPlaying(gameOverSoundID 
       && dieSoundID != -1 && gameOverSoundID != -1 && !hasShownButton) {
        hasShownButton = true;
        showButton();
    }
}

This code is a bit verbose but you should be able to trim it down a bit.


Just a quick comment: There is a way to get how long the audio source is (in seconds) and from this information you can simply run a timer for the same duration as the audio source length and check if it has finished playing that way. But this is only available in the OpenALSound class that you cannot access in the Core project. If the above method doesn't work I can update my answer with an alternative solution that uses audio length and timers instead.


If you're going to go for the Music route you can trim the code down quite a bit by using lambda expressions:

if (hasDied){
    dieMusic.setOnCompletionListener(a -> {
        gameOverMusic.play();
    });
    gameOverMusic.setOnCompletionListener(a -> {
        showButton();
    });
    dieMusic.play();
}
\$\endgroup\$
5
  • \$\begingroup\$ I don't see the need for all the raw AL stuff when one can check if a sound is playing using myMusic.isPlaying() or mySound.isPlaying()? \$\endgroup\$ – persson Jul 24 '17 at 15:44
  • \$\begingroup\$ That checks if a Music instance if playing, not a Sound instance. Are you using Music for your sounds? Music should only be used for long pieces of audio (> 1Mb) because it can stream the audio from the file and therefor doesn't have to store the entire file in memory. Sound is more efficient in general since it doesn't have to stream the audio and should be used when you have short pieces of audio like gunshot sound or hurt sounds etc. \$\endgroup\$ – Charanor Jul 24 '17 at 15:47
  • \$\begingroup\$ You're right. Although I'm currently using Sound instances, I suppose in my case I can just switch to use Music instances although each file is small. These events will not happen often and if there's a slight delay to read the file well, the player just died and the animation froze, so it's not a big deal anyway. Thanks! \$\endgroup\$ – persson Jul 24 '17 at 15:53
  • \$\begingroup\$ Btw, thanks for suggesting the alternative flag-based solution. \$\endgroup\$ – persson Jul 24 '17 at 15:53
  • \$\begingroup\$ No problem, if you decide to go with the music route and think it's very kludgy you can take a look at my answer; i added a way to shorten the code by a large margin. \$\endgroup\$ – Charanor Jul 24 '17 at 16:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.