Skip to main content Link Search Menu Expand Document (external link)

Theme: auto

Sound Effects and Volume

For testing purposes, change the starting level to "Assets/Level03.json" as that will demonstrate some of the initial sounds we will add.

Player Sound Effects

Because the player’s sound effects play on the player, we don’t need to do any special volume calculations. You can just play the sounds like you did in earlier labs.

You’ll add all the sounds to PlayerMove.

First, add a looping sound for footsteps:

  • Use the "FootstepLoop.ogg" and start playing it in the constructor, saving the sound handle
  • Immediately pause it after starting the sound
  • At the end of PlayerMove::Update, check if the current state is OnGround and mVelocity.Length() is greater than 50, in which case you should resume the sound. If those conditions are not met, pause the sound.
  • Make sure you stop the sound in the PlayerMove destructor, or you’ll end up with multiple instances of this sound when changing/reloading the level

Next, add the following non-looping sounds:

  • "Jump.ogg" when you start a jump
  • "Land.ogg" when you change state from Falling to OnGround
  • "PortalTeleport.ogg" when you teleport through a portal
  • "PortalShootBlue.ogg" when you create a blue portal
  • "PortalShootOrange.ogg" when you create an orange portal
  • "PortalFail.ogg" when you try to create a portal but you can’t (either because the SegmentCast doesn’t hit anything, or it hits something that’s not a Block)
  • "PortalClose.ogg" when you press R to reset the portals, and there’s at least one portal that will close

So that you don’t hear the landing sound right when the level loads, change it so PlayerMove begins in the OnGround state rather than the falling state.

Try the various player sounds. They should sound like this:

Adding Actor Tracking to AudioSystem

To support having sounds get quieter the further they are from the player, we need to add some new functionality to the AudioSystem.

First, add a class Game* pointer to the AudioSystem member data, and change the constructor so it takes in a game pointer as a first parameter (keeping the number of channels as an optional second parameter), and sets the game pointer member variable to what’s passed in.

Then in Game::Initialize, change the line that dynamically allocates mAudio to this:

mAudio = new AudioSystem(this, 32);

This will set the game pointer and additionally pass in 32 as the number of channels you want (up from the default of 8).

Member Data Changes

In order to make the sound falloff work, we need to be able to track if a sound is playing for an Actor and if so, we can update the volume based on the distance of the Actor to the player.

First, add the following member variables to the declaration of HandleInfo:

  • class Actor* mActor = nullptr; to track the actor, if any (defaults to null)
  • bool mStopOnActorRemove = true; to note whether the sound should stop if the actor is removed (defaults to true)

Next, in the member data of AudioSystem we need to add a map to track which sound handles are associated with a particular actor:

// Map for actors to their handles
std::unordered_map<class Actor*, std::set<SoundHandle>> mActorMap;

The key in this map is the pointer to the actor, and the value is an set that contains all the sound handles associated with that actor. You’ll need to add an include for <set> for this to work.

PlaySound Changes

Next, change the signature of PlaySound to the following:

SoundHandle PlaySound(const std::string& soundName, bool looping = false,
						  class Actor* actor = nullptr, bool stopOnActorRemove = true,
						  int fadeTimeMS = 0);

This will allow callers to optionally specify an actor when playing the sound.

Then, in the implementation of PlaySound, set the mActor and mStopOnActorRemove variables in the HandleInfo when you play the sound.

Furthermore, if actor is not nullptr, you also want to add the SoundHandle for the new sound you’re playing to the mActorMap’s set for that actor.

RemoveActor

Next, add this public member function to AudioSystem:

void RemoveActor(class Actor* actor);

The purpose of this function is that we’ll call it whenever an actor is destroyed, in which case RemoveActor will see if there are any sounds associated with the actor which now need to be halted.

The implementation of RemoveActor should do the following:

  1. Try to find the actor in mActorMap (not all actors will be in the map). If the actor is in the map:
    1. Iterate over each handle in the set for that actor
      1. Try to find the handle in the mHandleMap (not all handles may be here, if they’ve already stopped). If the handle is in the mHandleMap:
        1. Set its corresponding handle info’s mActor to nullptr
        2. If the handle info has mStopOnActorRemove and Mix_Playing says the sound is playing, call Mix_HaltChannel on the sound. (Remember that all Mix_* functions take in the mChannel in the handle info NOT the sound handle)
    2. Erase the actor from the map

Call RemoveActor at the start of the Actor destructor.

Update Changes

Recall that AudioSystem::Update checks to see if an existing sound has stopped, in which case it erases the handle info from mHandleMap.

Before you call mHandleMap.erase, you should check if the SoundHandle has an actor associated with it (which you can find out from the HandleInfo for it). If it does have an actor associated with it, you should erase the SoundHandle from the actor’s corresponding set.

Testing Actor Tracking

We’ll now add some additional non-looping sounds, but associated with specific actors. In general, to do this, you’ll call PlaySound , passing in false as the second parameter (for non-looping) this as the third parameter (for the corresponding actor):

  • "DoorOpen.ogg" in Door when it opens
  • "PelletFire.ogg" in EnergyLauncher when it fires a pellet
  • "EnergyCaught.ogg" in EnergyCatcher when it catches a pellet
  • "PelletDeath.ogg" in Pellet when it dies BUT you need to pass in false as the fourth parameter so that the sound doesn’t cut off when the pellet is destroyed (since that happens immediately thereafter)

If you play now, you’ll hear sounds for the above. However, without changing the volume, it just sounds really bad since you’ll hear the energy launchers and pellets from the next room even when you’re far away.

Adding Volume Falloff

Now we’ll add a volume falloff function so that sounds that are further away become quieter.

CalculateVolume

Add the following private helper function to AudioSystem:

int CalculateVolume(class Actor* actor, class Actor* listener) const;

This function returns a volume from 0 to 128 based on the distance between the actors and a inner/outer radius function:

  • If either actor or listener is nullptr, just return 128.
  • For the position of the actors you MUST use GetWorldPosition() since there may be actor parenting involved, and then calculate the distance between these world positions
  • If the distance is >= 600.0f, CalculateVolume should return 0
  • If the distance is <= 25.0f, CalculateVolume should return 128
  • For any distance between 25 to 600, you should set the volume as a linear function from volume of 128 to volume of 0. For example:
    • A distance of 25 should yield 128
    • A distance of 600 should yield 0
    • A distance of 312.5, which is half way between 25 and 600, should yield 64
    • A distance of 168.75, which is 25% of the way between 25 and 600, should yield 96
    • If you’re not sure how to do this, check the slides for further information

Using CalculateVolume

For sounds that have an associated actor, we need to use CalculateVolume both when we first start playing a sound and then on every frame, so that the volume can update as the player moves from the actor.

First, in PlaySound immediately after the call to Mix_PlayChannel, use CalculateVolume to get the correct volume. You want to pass in the actor as the first parameter, and the player as the second parameter.

Once you have the correct volume, use Mix_Volume, passing in the channel as the first parameter and the volume you got back as the second parameter.

Since CalculateVolume has null checks for actor/player, it’s safe to call it even if the actor pointer is null.

Next, in AudioSystem::Update, if a particular sound handle is still playing, check to see if it has an actor associated with it. If it does, call CalculateVolume and Mix_Volume again so that the volume updates dynamically.

The energy launcher, pellet, energy catcher, and door sounds should now all have their volume dynamically change as you move through the level. It’ll sound like this:

Turret Sounds

For testing purposes, change the starting level to "Assets/Level04.json" as that has turrets.

State Changing VO Sounds

In TurretHead, add a map where the key is the TurretState and the value is the name of the sound file you want to play:

  • Idle is "TurretIdle.ogg"
  • Search is "TurretSearch.ogg"
  • Priming is "TurretPriming.ogg"
  • Firing is "TurretFiring.ogg"
  • Falling is "TurretFalling.ogg"
  • Dead is "TurretDead.ogg"

Add a SoundHandle to track the current VO sound it’s playing. Then, in ChangeState:

  • Check to make sure the new state is different from the old one
    • If another VO sound is still playing, stop it first
    • Play the sound from the map corresponding to the state you’re changing to, passing in this for the third parameter to PlaySound, and save the handle in the member variable

This should sound like this:

Friendly Fire Sound

Add a public TakeDamage function to TurretHead that takes in no parameters. The first first time this function gets called for that particular TurretHead:

  • It should stop the current VO sound, if there is one playing
  • Play "TurretFriendlyFire.ogg" on this actor

For the HealthComponent in TurretBase, you will need to set the OnDamage callback to call the TakeDamage function on the TurretHead. The syntax for setting the callback is something like this:

health->SetOnDamage([this](const Vector3& location) {
	// Code you want to run
});

Bullet Sound

Every time the turret fires a bullet, you want to play "Bullet.ogg". However, instead of playing it on this actor, you should play it on the target you’re firing at.

You should now have the full set of turret sounds:

Once you’ve pushed this code, you’re ready to move on to part 3.