Building the SoundManager class

You might recall that in the previous project, all the sound code took up quite a few lines of code. Now consider that with spatialization, it's going to get longer still. To keep our code manageable, we will code a class to manage the playing of all our sound effects. In addition, to help us with spatialization, we will add a function to the Engine class as well, but we will discuss that when we come to it, later in the chapter.

Coding SoundManager.h

Let's get started by coding and examining the header file.

Right-click Header Files in the Solution Explorer and select Add | New Item.... In the Add New Item window, highlight (by left-clicking) Header File ( .h ) and then in the Name field, type SoundManager.h. Finally, click the Add button. We are now ready to code the header file for the SoundManager class.

Add and examine the following code:

#pragma once 
#include <SFML/Audio.hpp> 
 
using namespace sf; 
 
class SoundManager 
{ 
   private: 
      // The buffers 
      SoundBuffer m_FireBuffer; 
      SoundBuffer m_FallInFireBuffer; 
      SoundBuffer m_FallInWaterBuffer; 
      SoundBuffer m_JumpBuffer; 
      SoundBuffer m_ReachGoalBuffer; 
 
      // The Sounds 
      Sound m_Fire1Sound; 
      Sound m_Fire2Sound; 
      Sound m_Fire3Sound; 
      Sound m_FallInFireSound; 
      Sound m_FallInWaterSound; 
      Sound m_JumpSound; 
      Sound m_ReachGoalSound; 
 
      // Which sound should we use next, fire 1, 2 or 3 
      int m_NextSound = 1; 
 
   public: 
 
      SoundManager(); 
 
      void playFire(Vector2f emitterLocation,  
         Vector2f listenerLocation); 
 
      void playFallInFire(); 
      void playFallInWater(); 
      void playJump(); 
      void playReachGoal(); 
}; 

There is nothing tricky in the code we just added. There are five SoundBuffer objects and eight Sound objects. Three of the Sound objects will play the same SoundBuffer. This explains the reason for the different number of Sound/SoundBuffer objects. We do this so that we can have multiple roaring sound effects playing, with different spatialized parameters, simultaneously.

Notice there is the m_NextSound variable that will help us keep track of which of these potentially simultaneous sounds we should use next.

There is a constructor, SoundManager, where we will set up all our sound effects, and there are five functions that will play the sound effects. Four of these functions simply play normal sound effects and their code will be really simple.

One of the functions, playFire, will handle the spatialized sound effects and will be a bit more in-depth. Notice the parameters of the playFire function. It receives a Vector2f, which is the location of the emitter, and a second Vector2f, which is the location of the listener.

Coding the SoundManager.cpp file

Now we can code the function definitions. The constructor and the playFire functions have a fair amount of code, so we will look at them individually. The other functions are short and sweet so we will handle them all at once.

Right-click Source Files in the Solution Explorer and select Add | New Item.... In the Add New Item window, highlight (by left-clicking) C++ File ( .cpp ) and then in the Name field, type SoundManager.cpp. Finally, click the Add button. We are now ready to code the .cpp file for the SoundManager class.

Coding the constructor

Add the following code for the include directives and the constructor to SoundManager.cpp:

#include "stdafx.h" 
#include "SoundManager.h" 
#include <SFML/Audio.hpp> 
 
using namespace sf; 
 
SoundManager::SoundManager() 
{ 
   // Load the sound in to the buffers 
   m_FireBuffer.loadFromFile("sound/fire1.wav"); 
   m_FallInFireBuffer.loadFromFile("sound/fallinfire.wav"); 
   m_FallInWaterBuffer.loadFromFile("sound/fallinwater.wav"); 
   m_JumpBuffer.loadFromFile("sound/jump.wav"); 
   m_ReachGoalBuffer.loadFromFile("sound/reachgoal.wav"); 
 
   // Associate the sounds with the buffers 
   m_Fire1Sound.setBuffer(m_FireBuffer); 
   m_Fire2Sound.setBuffer(m_FireBuffer); 
   m_Fire3Sound.setBuffer(m_FireBuffer); 
   m_FallInFireSound.setBuffer(m_FallInFireBuffer); 
   m_FallInWaterSound.setBuffer(m_FallInWaterBuffer); 
   m_JumpSound.setBuffer(m_JumpBuffer); 
   m_ReachGoalSound.setBuffer(m_ReachGoalBuffer); 
          
   // When the player is 50 pixels away sound is full volume 
   float minDistance = 150; 
   // The sound reduces steadily as the player moves further away 
   float attenuation = 15; 
 
   // Set all the attenuation levels 
   m_Fire1Sound.setAttenuation(attenuation); 
   m_Fire2Sound.setAttenuation(attenuation); 
   m_Fire3Sound.setAttenuation(attenuation); 
 
   // Set all the minimum distance levels 
   m_Fire1Sound.setMinDistance(minDistance); 
   m_Fire2Sound.setMinDistance(minDistance); 
   m_Fire3Sound.setMinDistance(minDistance); 
 
   // Loop all the fire sounds 
   // when they are played 
   m_Fire1Sound.setLoop(true); 
   m_Fire2Sound.setLoop(true); 
   m_Fire3Sound.setLoop(true); 
} 

In the previous code, we loaded five sound files into the five SoundBuffer objects. Next, we associated the eight Sound objects with one of the SoundBuffer objects. Notice that m_Fire1Sound, m_Fire2Sound, and m_Fire3Sound are all going to be playing from the same SoundBuffer, m_FireBuffer.

Next, we set the attenuation and minimum distance for the three fire sounds.

Tip

The values of 150 and 15, respectively, were arrived at through experimentation. Once the game is running, I encourage you to experiment with these values by changing them around and seeing (or rather hearing) the difference.

Finally, for the constructor, we used the setLoop function on each of the fire-related Sound objects. Now when we call play, they will play continuously.

Coding the playFire function

Add the playFire function shown in the following code, and then we can discuss it:

void SoundManager::playFire( 
   Vector2f emitterLocation, Vector2f listenerLocation) 
{ 
   // Where is the listener? Thomas. 
   Listener::setPosition(listenerLocation.x,  
      listenerLocation.y, 0.0f); 
 
   switch(m_NextSound) 
   { 
 
   case 1: 
      // Locate/move the source of the sound 
      m_Fire1Sound.setPosition(emitterLocation.x,  
         emitterLocation.y, 0.0f); 
 
      if (m_Fire1Sound.getStatus() == Sound::Status::Stopped) 
      { 
         // Play the sound, if its not already 
         m_Fire1Sound.play(); 
      } 
      break; 
 
   case 2: 
      // Do the same as previous for the second sound 
      m_Fire2Sound.setPosition(emitterLocation.x,  
         emitterLocation.y, 0.0f); 
 
      if (m_Fire2Sound.getStatus() == Sound::Status::Stopped) 
      { 
         m_Fire2Sound.play(); 
      } 
      break; 
 
   case 3: 
      // Do the same as previous for the third sound 
      m_Fire3Sound.setPosition(emitterLocation.x,  
         emitterLocation.y, 0.0f); 
 
      if (m_Fire3Sound.getStatus() == Sound::Status::Stopped) 
      { 
         m_Fire3Sound.play(); 
      } 
      break; 
   } 
 
   // Increment to the next fire sound 
   m_NextSound++; 
 
   // Go back to 1 when the third sound has been started 
   if (m_NextSound > 3) 
   { 
      m_NextSound = 1; 
   } 
} 

The first thing we do is call Listener::setPosition and set the location of the listener based on the Vector2f that is passed in as a parameter.

Next, the code enters a switch block based on the value of m_NextSound. Each of the case statements does the exact same thing, but to either m_Fire1Sound, m_Fire2Sound, or m_Fire3Sound.

In each of the case blocks, we set the position of the emitter using the passed-in parameter with the setPosition function. The next part of the code in each case block checks whether the sound is currently stopped, and if it is, plays the sound. We will see quite soon how we arrive at the positions for the emitter and listener that are passed into this function.

The final part of the playFire function increments m_NextSound and ensures that it can only be equal to 1, 2, or 3, as required by the switch block.

Coding the rest of the SoundManager functions

Add these four simple functions:

void SoundManager::playFallInFire() 
{ 
   m_FallInFireSound.setRelativeToListener(true); 
   m_FallInFireSound.play(); 
} 
 
void SoundManager::playFallInWater() 
{ 
   m_FallInWaterSound.setRelativeToListener(true); 
   m_FallInWaterSound.play(); 
} 
 
void SoundManager::playJump() 
{ 
   m_JumpSound.setRelativeToListener(true); 
   m_JumpSound.play(); 
} 
 
void SoundManager::playReachGoal() 
{ 
   m_ReachGoalSound.setRelativeToListener(true); 
   m_ReachGoalSound.play(); 
} 

The playFallInFire, playFallInWater, and playReachGoal functions do just two things. First, they each call setRelativeToListener, so the sound effect is not spatialized, making the sound effect normal, not directional, and then they call play on the appropriate Sound object.

That concludes the SoundManager class. Now we can use it in the Engine class.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.134.229