12 - Sound and Music
Our game is really coming together now, but it's still missing something… there's no sound yet!
For this game, we're going to keep it simple (as usual). We're going to have a single, continuously looping track that plays while our game is running. We will also have several, simple sound effects that play for different actions. Playing sounds and music in HaxeFlixel is pretty easy, so this will go quickly!
First, you'll need to make your music and sounds. Patrick Crecelius from Fat Bard has provided some music for this tutorial - feel free to use it for this tutorial, or make your own.
We've also created some sound effects using Bfxr, which you can use if you like, or, make your own!
If you've been following along since the UI and Combat section, you already downloaded a few of these, but be sure you have them all.
-
coin.wav - new! to be used when the player picks up a coin
-
combat.wav - to be used when combat starts
-
fled.wav - will play when the player successfully flees from combat
-
hurt.wav - will play whenever either the player or the enemy hits with an attack
-
lose.wav - will play when the player dies in combat
-
miss.wav - will play whenever either the player or the enemy misses with an attack
-
select.wav - used by buttons and when the player makes a selection
-
step.wav - new! used by the player and the enemies for 'footstep' sounds
-
win.wav - used when the player wins in combat
Once you have your music, place it in assets/music
, and your sound files should go in assets/sounds
.
Now let's change our code to use these sounds:
-
First, open up
MenuState.hx
. Since we want our music to start as soon as the game starts, and loop continuously no matter what happens, we're going to add this tocreate()
.if (FlxG.sound.music == null) // don't restart the music if it's already playing { FlxG.sound.playMusic(AssetPaths.HaxeFlixel_Tutorial_Game__ogg, 1, true); }
HAXEWe're also checking if the music is already playing, since we don't want to restart it unnecessarily in that case.
If you try your game out right now, it should play music!
-
Next, we want to make our buttons all make a sound when they get clicked. This is simple, we just tell the button's
onUp
to load our sound. In theMenuState
'screate()
, after you initialize the play-button, add this:playButton.onUp.sound = FlxG.sound.load(AssetPaths.select__wav);
HAXE -
Now, you can do the same for the options button (changing
playButton
tooptionsButton
).For each of the other buttons in our game - four of them in
OptionsState
, and one inGameOverState
- the code already exists, but as a learning exercise, you can go through those files and see what was done. -
Next, let's give our player some footsteps. We don't want to create and destroy a new sound object every time we want to play the same sound, so we will create a
FlxSound
object to be used over and over. At the top of thePlayer
class, add:var stepSound:FlxSound;
HAXE -
Then, we need to load the footstep sound somewhere in the constructor:
stepSound = FlxG.sound.load(AssetPaths.step__wav);
HAXE -
Now go to our
updateMovement()
function, and, after we check if the player is moving (if ((velocity.x != 0 || velocity.y != 0) && touching == NONE)
), add:stepSound.play();
HAXEA neat little property of
FlxSound
objects is that if you ever tell one to play, if it's already playing (and you haven't set theforceRestart
flag), it won't play again. This means we can easily call play on our sound every frame, and it will sound as if the sound is just being looped - for as long as the player is moving, but will finish if the player has stopped moving, and not start up again while they are stationary. -
Now, let's give enemies their own footsteps, too. The difference is, instead of just always playing the step sound at full volume, we're going to change the volume based on the proximity of the enemy to the player. This will be easier than it sounds. First, add our sound variable to the top of Enemy.hx:
var stepSound:FlxSound;
HAXE -
And then, similarly to how we setup the
Player
class, add this to our constructor:stepSound = FlxG.sound.load(AssetPaths.step__wav, 0.4); stepSound.proximity(x, y, FlxG.camera.target, FlxG.width * 0.6);
HAXEYou'll notice that we are setting the volume to
0.4
(40%) this is because there will be plenty of enemies on the map, and there footsteps can get kind of annoying and loud (besides, they're probably walking around the dungeon barefoot, right?). -
We then setup our proximity for our sounds, setting it's position to the
x
andy
position of this enemy, and telling it to target theFlxG.camera.target
object (which happens to be our player!). Finally, we say that the radius of our footstep sound is a little bit more than half of the screen's width - so we should be able to hear enemies that are just off the screen, and all the enemies' footsteps will sound louder/softer based on their distance from the camera target. -
Next, similarly to where we added the player's step sounds, we're going to have the enemy play sounds, when it's playing it's walk animation. For these sounds, however, we will give the sound a position in the world.
stepSound.setPosition(x + frameWidth / 2, y + height); stepSound.play();
HAXE -
Next, let's head over to
PlayState
. We really only need one sound to be in thePlayState
itself, and that's the one for picking up a coin. While you could put this into theCoin
class, because there could be a lot of coins loaded at once, and because we really can't pick up more than one coin at a time (so the sounds don't need to overlap), putting a single coin sound effect in ourPlayState
saves us a bit of overhead.So, just like our other sounds, initialize the variable:
var coinSound:FlxSound;
HAXELoad the sound in
create()
:coinSound = FlxG.sound.load(AssetPaths.coin__wav);
HAXEAnd in
playerTouchCoin()
, inside theif
-statement, add:coinSound.play(true);
HAXEThis time we will use
forceRestart
so that if the player happens to pickup several coins close to each other the sound will keep up with them. -
All of the rest of our sounds, because they deal with combat, will be in our
CombatHUD
class.Assuming you downloaded the
CombatHUD
file, the sounds should already be there, but as a learning exercise, it's a good idea to go through and check. This will help you better understand sounds for when you're working on your next game.To initialize them:
var fledSound:FlxSound; var hurtSound:FlxSound; var loseSound:FlxSound; var missSound:FlxSound; var selectSound:FlxSound; var winSound:FlxSound; var combatSound:FlxSound;
HAXETo load them:
fledSound = FlxG.sound.load(AssetPaths.fled__wav); hurtSound = FlxG.sound.load(AssetPaths.hurt__wav); loseSound = FlxG.sound.load(AssetPaths.lose__wav); missSound = FlxG.sound.load(AssetPaths.miss__wav); selectSound = FlxG.sound.load(AssetPaths.select__wav); winSound = FlxG.sound.load(AssetPaths.win__wav); combatSound = FlxG.sound.load(AssetPaths.combat__wav);
HAXEYou can probably figure out where they all go, but I'll go through them anyway.
In
initCombat()
:combatSound.play();
HAXEIn
finishFadeIn()
:selectSound.play();
HAXEIn
update()
, inside each of our three if statements related to button presses (if (_fire), else if (up), else if (down)
):selectSound.play();
HAXEIn
makeChoice()
, in our logic for a 'hit' (after_damages[1].text = "1";
):hurtSound.play();
HAXEand in our miss logic:
missSound.play();
HAXEFurther down, if the player escapes (after
outcome = ESCAPE
):fledSound.play();
HAXEIn
enemyAttack()
, if the enemy hits:hurtSound.play();
HAXEand if they miss:
missSound.play();
HAXEFinally, in
doneDamageOut()
, afteroutcome = DEFEAT
:loseSound.play();
HAXEand after
outcome = VICTORY
:winSound.play();
HAXE
And that's it for sound! Play your game now, and you should hear all of the effects we've added (make sure your volume is up high enough, too!) It's starting to look like a real game! Next time, we'll get it working on multiple platforms!