Bleeps and bloops

Imagine watching a move that has no sound. As the main character runs down the alley, there are no footsteps. There is no swishing sound as his arms rub his jacket. There is no screech as a car comes to a halt just before hitting him.

A movie without sound would be pretty boring, and so would most games. Sounds bring games to life. The best sound design is one where the player doesn't actually realize there is a sound design. This means crafting sound effects and music in a way that complement the game without being obnoxious.

Sound effects

Sound effects generally correspond to some event or action that is happening in the game. A particular sound often corresponds to something that the player can see, but sound effects may also occur for something that the player cannot see, perhaps just round the corner.

Let's add our first sound effects to the game. We'll keep it simple and add the following sounds:

  • A rolling sound as Robo moves across the screen
  • A sound when Robo jumps up or jumps down
  • A happy sound when he collides with an oil can
  • A not-so-happy sound when he collides with a water bottle

Setting up the sounds

We'll start by setting up some variables to act as pointers to our sounds. Open RoboRacer2D.cpp and add the following code in the variable declarations section:

FMOD::Sound* sfxWater;
FMOD::Sound* sfxOilcan;
FMOD::Sound* sfxJump; 
FMOD::Sound* sfxMovement;
FMOD::Channel* chMovement;

We have three pointers to sound and one pointer to a channel. We only need one channel pointer because only one sound (sfxMovement) will be a looping sound. Looping sounds need a persistent channel pointer while one-shot sounds do not.

Next, we will load these sounds. Add the following function to RoboRacer2D.cpp:

const bool LoadAudio()
{
  FMOD_RESULT result;
  result = audiomgr-> createSound ("resources/oil.wav", FMOD_DEFAULT, 0, &sfxOilcan);
  result = audiomgr-> createSound ("resources/water.wav", FMOD_DEFAULT, 0, &sfxWater);
  result = audiomgr-> createSound ("resources/jump.wav", FMOD_DEFAULT, 0, &sfxJump);
  result = audiomgr->createSound("resources/movement.wav", FMOD_LOOP_NORMAL | FMOD_2D | FMOD_HARDWARE, 0, &sfxMovement);
  result = audiomgr->playSound(FMOD_CHANNEL_FREE, sfxMovement, true, &chMovement);
return true; }

Tip

You can download these sounds from the book's website or you can replace them with your own. Just be sure that you are using very short sounds for oil, water, and jump because they are intended to play quickly.

This function loads our three sound effects files into the audio system.

  • The createSound function allocates memory for the sound and sets the FMOD properties for the sound.
  • FMOD_DEFAULT sets up the following FMOD properties:
    • FMOD_LOOP_OFF: The sound plays once and does not loop
    • FMOD_2D: This is a 2D sound
    • FMOD_HARDWARE: This uses the hardware features of the device to handle audio
  • The result variable catches return value. In production games, you would test this each time to make sure that the sound had successfully loaded (we leave those error checks off here to save space).
  • Notice that we call playSound on the movement SFX. We are going to start this sound, assign it to the next free hardware channel (FMOD_CHANNEL_FREE), but tell FMOD to immediately pause it (thus the true parameter). When we want to play the sound, we will play it, and when we want it to stop, we will pause it.
  • We will call playSound on the other SFX as needed. As they are not looping sounds, we do not have to manage their paused state.

Notice that we set sfxJump, sfxOilcan, and sfxWater to use the FMOD_DEFAULT settings. However, we will need sfxMovement to loop, so we had to set its setting flags individually.

There are several flags that you can use to set the properties of a sound, and you can use the OR operator (|) to combine flags:

  • FMOD_HARDWARE: This uses the device hardware to handle the audio.
  • FMOD_SOFTWARE: This uses FMOD's software emulation to handle the audio (slower, but could give access to features not supported by the device).
  • FMOD_2D: This is a 2D sound. This is the format we will use for this game!
  • FMOD_3D: This is a 3D sound. 3D sounds can be placed in 3D space and appear to have both distance (for example, the sound gets softer as it is further away) and position (left, right, in front of, behind).
  • FMOD_LOOP_OFF: The sound plays once and does not loop.
  • FMOD_LOOP_NORMAL: The sound plays and then starts over again, looping indefinitely.

There are many other flags that can be set. Take a look at the FMOD documentation for additional details.

Now that we have a function to load our sounds, we have to wire it into the initialization for the game. Modify the GameLoop function, adding the following highlighted line:

void GameLoop(const float p_deltatTime)
{
  if (m_gameState == GameState::GS_Splash)
  {
    InitFmod();
    LoadAudio();
    BuildFont();
    LoadTextures();
    m_gameState = GameState::GS_Loading;
  }
  Update(p_deltatTime);
  Render();
}

Playing sounds

Now, we need to trigger the sound effects at the appropriate time. Let's start with Robo's movement SFX. Basically, we want to play this sound any time Robo is actually moving.

We are going to modify the CM_STOP, CM_LEFT, and CM_RIGHT cases in the ProcessInput function. Update the code inserting the highlighted lines indicated as follows:

case Input::Command::CM_STOP:
player->SetVelocity(0.0f);
background->SetVelocity(0.0f);
chMovement->setPaused(true);
break;

case Input::Command::CM_LEFT:
if (player == robot_right)
{
  robot_right->IsActive(false);
  robot_right->IsVisible(false);
  robot_left->SetPosition(robot_right->GetPosition());
  robot_left->SetValue(robot_right->GetValue());
}
player = robot_left;
player->IsActive(true);
player->IsVisible(true);
player->SetVelocity(-50.0f);
background->SetVelocity(50.0f);
chMovement->setPaused(false);
break;

case Input::Command::CM_RIGHT:
if (player == robot_left)
{
  robot_left->IsActive(false);
  robot_left->IsVisible(false);
  robot_right->SetPosition(robot_left->GetPosition());
  robot_right->SetValue(robot_left->GetValue());
}
player = robot_right;
player->IsActive(true);
player->IsVisible(true);
player->SetVelocity(50.0f);
background->SetVelocity(-50.0f);
chMovement->setPaused(false);
break;

Remember, we already loaded sfxMovement and assigned it to a virtual channel (chMovement), then told it to start playing as a paused sound. Actually, in FMOD, you pause and play the channel, not the sound. So, all we have to do now is call chMovement->setPaused(true) when Robo is moving and chMovement->setPaused(false) when he is not moving.

Now, we need to handle the oil and water pickups. These can both be handled in the CheckCollisions function. Modify CheckCollisions by adding the following highlighted lines of code:

void CheckCollisions()
{
  if (player->IntersectsCircle(pickup))
  {
    FMOD::Channel* channel;
    audiomgr->playSound(FMOD_CHANNEL_FREE, sfxOilcan, false, &channel);
    pickup->IsVisible(false);
    pickup->IsActive(false);
    player->SetValue(player->GetValue() + pickup->GetValue());
    pickupSpawnTimer = 0.0f;
    pickupsReceived++;
  }
  
  if (player->IntersectsRect(enemy))
  {
    FMOD::Channel* channel;
    audiomgr->playSound(FMOD_CHANNEL_FREE, sfxWater, false, &channel);
    enemy->IsVisible(false);
    enemy->IsActive(false);
    player->SetValue(player->GetValue() + enemy->GetValue());
    enemySpawnTimer = 0.0f;
  }
}

Finally, we will add a sound effect for Robo when he jumps up or jumps down. These changes will be applied to the CM_UP and CM_DOWN cases in the ProcessInput function. Modify the existing code with the following highlighted lines:

case Input::Command::CM_UP:
{
  FMOD::Channel* channel;
  audiomgr->playSound(FMOD_CHANNEL_FREE, sfxJump, false, &channel);
  player->Jump(Sprite::SpriteState::UP);
}
break;

case Input::Command::CM_DOWN:
{
  FMOD::Channel* channel;
  audiomgr->playSound(FMOD_CHANNEL_FREE, sfxJump, false, &channel);
  player->Jump(Sprite::SpriteState::DOWN);
}
break;

These sound effects are one-shot sounds. When they are done playing, we don't need to worry about them any more until it is time to play them again. For this type of sound, we create a channel (FMOD::channel* channel), then call playSound using:

  • FMOD_CHANNEL_FREE: This lets FMOD pick the next available hardware sound channel.
  • Sound pointer: sfxWater for the water bottle, sfxOilcan for the oil, and sfxJump for the jump SFX.
  • false: Don't pause the sound!
  • &channel: This is the virtual channel handle. Notice that this is just a local variable. We don't need to store this anywhere for one-shot SFX.

That's it! If you play the game now, the four SFX should trigger according to our design.

UI feedback

So far, we created sound effects to respond to events and actions in the game. Sound effects are also used to provide feedback from the user interface. For example, when the player clicks a button, there should be some kind of audio that plays so that the player immediately knows that the click was registered.

Fortunately, we already trap each time the user has clicked a UI button, so it's easy to trigger a sound each time it happens. Let's start by adding a new sound pointer. In RoboRacer2D.cpp, add the following line to the variable declarations:

FMOD::Sound* sfxButton;

Then add the following code to LoadAudio:

result = audiomgr->createSound("resources/button.wav", FMOD_DEFAULT, 0, &sfxButton);

Finally, add the following highlighted lines of code to the CM_UI case in ProcessInput:

case Input::Command::CM_UI:
FMOD::Channel* channel;
if (pauseButton->IsClicked())
{
  audiomgr->playSound(FMOD_CHANNEL_FREE, sfxButton, false, &channel);
  pauseButton->IsClicked(false);
  pauseButton->IsVisible(false);
  pauseButton->IsActive(false);
  
  resumeButton->IsClicked(false);
  resumeButton->IsVisible(true);
  resumeButton->IsActive(true);
  m_gameState = GS_Paused;
}

if (resumeButton->IsClicked())
{
  audiomgr->playSound(FMOD_CHANNEL_FREE, sfxButton, false, &channel);
  resumeButton->IsClicked(false);
  resumeButton->IsVisible(false);
  resumeButton->IsActive(false);
  
  pauseButton->IsClicked(false);
  pauseButton->IsVisible(true);
  pauseButton->IsActive(true);
  m_gameState = GS_Running;
}

if (playButton->IsClicked())
{
  audiomgr->playSound(FMOD_CHANNEL_FREE, sfxButton, false, &channel);
  playButton->IsClicked(false);
  exitButton->IsActive(false);
  playButton->IsActive(false);
  creditsButton->IsActive(false);
  m_gameState = GameState::GS_Running;
}

if (creditsButton->IsClicked())
{
  audiomgr->playSound(FMOD_CHANNEL_FREE, sfxButton, false, &channel);
  creditsButton->IsClicked(false);
  exitButton->IsActive(false);
  playButton->IsActive(false);
  creditsButton->IsActive(false);
  m_gameState = GameState::GS_Credits;
}

if (exitButton->IsClicked())
{
  audiomgr->playSound(FMOD_CHANNEL_FREE, sfxButton, false, &channel);
  playButton->IsClicked(false);
  exitButton->IsActive(false);
  playButton->IsActive(false);
  creditsButton->IsActive(false);
  PostQuitMessage(0);
}

if (menuButton->IsClicked())
{
  audiomgr->playSound(FMOD_CHANNEL_FREE, sfxButton, false, &channel);
  menuButton->IsClicked(false);
  menuButton->IsActive(false);
  m_gameState = GameState::GS_Menu;
}

if (continueButton->IsClicked())
{
  audiomgr->playSound(FMOD_CHANNEL_FREE, sfxButton, false, &channel);
  continueButton->IsClicked(false);
  continueButton->IsActive(false);
  m_gameState = GameState::GS_Running;
}

if (replayButton->IsClicked())
{
  audiomgr->playSound(FMOD_CHANNEL_FREE, sfxButton, false, &channel);
  replayButton->IsClicked(false);
  replayButton->IsActive(false);
  exitButton->IsActive(false);
  RestartGame();
  m_gameState = GameState::GS_Running;
}
break;

At this point, when you run the game you will now hear an SFX each time a button is clicked.

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

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