FXGL Audio System
Asset Placement
Audio files go in src/main/resources/assets/:
- Sounds →
assets/sounds/— short clips (WAV recommended for low latency) - Music →
assets/music/— long tracks (MP3 or OGG for size efficiency)
Quickstart — DSL Shortcuts
// Play a sound effect (one-shot, auto-managed)
FXGL.play("sounds/shoot.wav");
// Loop background music
FXGL.loopBGM("music/theme.mp3");
// Stop / pause / resume background music
FXGL.stopBGM();
FXGL.pauseBGM();
FXGL.resumeBGM();
Loading and Playing Manually
// Load once (cached), play many times
Sound jumpSound = getAssetLoader().loadSound("sounds/jump.wav");
Music themeMusic = getAssetLoader().loadMusic("music/theme.mp3");
// Play
getAudioPlayer().playSound(jumpSound);
getAudioPlayer().playMusic(themeMusic);
// Loop a specific music object
getAudioPlayer().loopMusic(themeMusic);
// Stop / pause / resume specific music
getAudioPlayer().stopMusic(themeMusic);
getAudioPlayer().pauseMusic(themeMusic);
getAudioPlayer().resumeMusic(themeMusic);
Volume Control
// Global volumes (0.0 = mute, 1.0 = full)
getSettings().setGlobalSoundVolume(0.8);
getSettings().setGlobalMusicVolume(0.5);
// At runtime via AudioPlayer
getAudioPlayer().setGlobalSoundVolume(0.7);
getAudioPlayer().setGlobalMusicVolume(0.4);
// Individual sound volume (set before playing)
Sound s = getAssetLoader().loadSound("sounds/explosion.wav");
s.setVolume(0.5); // half volume for this instance
getAudioPlayer().playSound(s);
// Individual music volume
Music bgm = getAssetLoader().loadMusic("music/boss.mp3");
bgm.setVolume(0.6);
getAudioPlayer().loopMusic(bgm);
Volume Slider UI Pattern
@Override
protected void initUI() {
Slider sfxSlider = new Slider(0, 1, getSettings().getGlobalSoundVolume());
sfxSlider.valueProperty().addListener((obs, oldVal, newVal) ->
getAudioPlayer().setGlobalSoundVolume(newVal.doubleValue()));
Slider musicSlider = new Slider(0, 1, getSettings().getGlobalMusicVolume());
musicSlider.valueProperty().addListener((obs, oldVal, newVal) ->
getAudioPlayer().setGlobalMusicVolume(newVal.doubleValue()));
addUINode(sfxSlider, 100, 400);
addUINode(musicSlider, 100, 440);
}
Music Crossfade Pattern
private Music currentMusic;
public void crossfadeTo(String musicFile) {
Music nextMusic = getAssetLoader().loadMusic("music/" + musicFile);
if (currentMusic != null) {
// Fade out current
animationBuilder()
.duration(Duration.seconds(1))
.animate(new AnimatedValue<>(currentMusic.getVolume(), 0.0))
.onProgress(v -> currentMusic.setVolume(v))
.build()
.setOnFinished(() -> {
getAudioPlayer().stopMusic(currentMusic);
nextMusic.setVolume(0);
getAudioPlayer().loopMusic(nextMusic);
// Fade in next
animationBuilder()
.duration(Duration.seconds(1))
.animate(new AnimatedValue<>(0.0, 1.0))
.onProgress(v -> nextMusic.setVolume(v))
.buildAndPlay();
})
.start();
} else {
getAudioPlayer().loopMusic(nextMusic);
}
currentMusic = nextMusic;
}
Event-Driven Sound Effects
// In initPhysics() collision handlers — most common pattern
onCollisionBegin(EntityType.PLAYER, EntityType.COIN, (player, coin) -> {
play("sounds/coin.wav");
coin.removeFromWorld();
});
// In component onUpdate for continuous sounds
// (guard with a flag to avoid playing every frame)
public class EngineComponent extends Component {
private boolean enginePlaying = false;
@Override
public void onUpdate(double tpf) {
double speed = entity.getComponent(PhysicsComponent.class).getSpeed();
if (speed > 10 && !enginePlaying) {
engineSound = getAssetLoader().loadSound("sounds/engine_loop.wav");
getAudioPlayer().playSound(engineSound);
enginePlaying = true;
} else if (speed <= 10 && enginePlaying) {
getAudioPlayer().stopSound(engineSound);
enginePlaying = false;
}
}
}
Text-to-Speech (fxgl-intelligence module)
// Add to pom.xml: com.github.almasb:fxgl-intelligence:VERSION
// Register service
settings.addEngineService(TextToSpeechService.class);
// Use (in any init* hook or at runtime)
TextToSpeechService tts = getService(TextToSpeechService.class);
tts.speak("Welcome to the dungeon, adventurer!");
// Configure voice
tts.setRate(1.0); // 0.1 (slow) to 10.0 (fast)
tts.setPitch(1.0); // 0.0 (deep) to 2.0 (high)
tts.setVolume(1.0);
// Wait for speech to finish before proceeding
tts.speakAndWait("Choose your path wisely.");
Stopping All Audio (e.g., on game over)
// Stop BGM
stopBGM();
// Stop all playing sounds (FXGL 21+)
getAudioPlayer().stopAllSounds();
getAudioPlayer().stopAllMusic();
Gotchas
- File extensions matter —
play("shoot.wav")works, butplay("sounds/shoot.wav")only works if the file is actually atassets/sounds/shoot.wav. Omit thesounds/prefix when using theFXGL.play()shortcut — it automatically resolves toassets/sounds/for WAV andassets/music/for MP3/OGG. - WAV for short SFX, MP3/OGG for music — JavaFX's MediaPlayer (used for music) has higher latency than AudioClip (used for sounds). Explosions and footsteps should always be WAV to avoid noticeable delay.
loopBGM()replaces the current BGM — only oneloopBGMtrack plays at a time. UsegetAudioPlayer().loopMusic()directly if you need multiple simultaneous music tracks.- Volume is not persisted automatically — save volume settings to
DataFileinwriteSaveState()or usegetSystemBundle()for per-session persistence. - TTS blocks the calling thread with
speakAndWait()— call it on a background thread or usespeak()(non-blocking) inside dialogue callbacks. - FXGL on headless environments (CI, testing) —
AudioPlayeris replaced with a no-op stub when no audio hardware is detected. Audio calls are silently ignored.