Sound Effects in LlamaWorks2D


When I first started writing games many years ago for MS-DOS, the only way to make noises was to use the computer's speaker. It was not a particularly straightforward task. If your game played music, and most did, the music had a "dinkity-dink" quality to the sound.

These days, computers come with wonderful sound cards that can produce excellent music and sound effects. However, for the most part, using them is still not particularly easy.

You learned in previous chapters that the computer industry has developed graphic libraries to simplify the process of creating animated graphics on computer screens. The industry has done the same for sounds and music. The two most commonly used libraries for doing sound and music are Microsoft's DirectX Audio and OpenAL. Of the two, DirectX Audio is more powerful and flexible. However, it is also much harder to use than OpenAL. Therefore, the LlamaWorks2D game engine uses OpenAL for both music and sound effects.

Factoid

Microsoft used to distribute its audio libraries as two separate components, called DirectSound and DirectMusic. As you might expect from the names, DirectSound was for sound effects and DirectMusic was for music. These days, Microsoft has combined the two components into a single library called DirectX Audio.


The instructions for installing OpenAL are in the Introduction. If you have not already installed OpenAL, please do so now.

LlamaWorks2D uses the same philosophy for sounds and music that it does for graphics. Just as graphics are all represented by the sprite class, all sounds, whether they are sound effects or music, are represented by the sound class. To play sound effects or music, your game declares a variable of type sound. Listing 8.1 shows the class definition of the LlamaWorks2D's sound class.

Listing 8.1. The sound class

 1    class sound 2    { 3    public: 4        sound(); 5        ~sound(); 6 7        bool LoadWAV( 8            std::string fileName, 9            bool continuousLoop = false); 10       bool Play(); 11       bool Pause(); 12       bool Stop(); 13       bool Rewind(); 14 15   private: 16       ALuint audioBuffer; 17       ALuint audioSource; 18   }; 

The sound class has a constructor and destructor that take care of the OpenAL setup and cleanup. It also has two data members, as shown on lines 1617 of Listing 8.1. These data members contain the information that OpenAL needs to produce sounds.

Note

LlamaWorks2D can only load WAV files for sound effects and music. If you decide you need the ability to use MP3 files, you can purchase LlamaWorks2D XE at www.screamingllamasoftware.com for a modest fee. LlamaWorks2D XE can utilize MP3 files.


The member functions on lines 713 enable your game to load sound data into the sound variable, play the sound, pause it, stop it, or rewind it.

You may notice something unusual about the second parameter to the LoadWAV() function. As you can see from line 9, the prototype sets the parameter to false. This is a C++ feature called a default parameter. If you do not provide a value for the second parameter for the LoadWAV() function, the program automatically assigns it the value false. The result of using a default parameter is that you can leave off the parameter when you call the function. Therefore, some calls to LoadWAV() can have two parameters and some only one. The compiler will not complain. If a game only passes one parameter to the LoadWAV() function, the program sets continuousLoop to false.

Loading Sounds

To demonstrate how to use sounds in games, we'll put a sound effect and two short pieces of music into Ping. Let's start by examining how to load sounds.

The new version of Ping declares its sound variables in its game class, as Listing 8.2 demonstrates.

Listing 8.2. Ping with sounds

 1    class ping : public game 2    { 3    public: 4        bool OnAppLoad(); 5        bool InitGame(); 6        bool InitLevel(); 7        bool UpdateFrame(); 8        bool RenderFrame(); 9 10       ATTACH_MESSAGE_MAP; 11 12       bool OnKeyDown( 13           keyboard_input_message &theMessage); 14 15       bool BallHitPaddle(); 16 17       void SetBallStartPosition(); 18       void SetBallStartDirection(); 19 20       bool DoLevelDone(); 21       bool DoGameOver(); 22 23   private: 24       ball theBall; 25       paddle leftPaddle; 26       paddle rightPaddle; 27       sprite theTrophy; 28 29       sprite scoreMarker; 30       int player1Score; 31       int player2Score; 32 33       bitmap_region areaOfPlay; 34 35       sound ballBounceSound; 36       sound gameWonMusic; 37   }; 

The ping class declares two data members of type sound on lines 3536. If your program contains a variable of type sound, it can call the sound::LoadWAV() function to load WAV file data into the sound variable. As with loading bitmaps, loading sounds takes time. Your game should not load sounds while the player is in a level. Instead, it should load sounds in an initialization function. The new version of Ping loads its sounds in the InitGame() function, which is given in Listing 8.3.

Listing 8.3. Loading sounds in InitGame()

 1    bool ping::InitGame() 2    { 3        // The InitGame() function is really 111 lines long. 4        // I've omitted most of it because it 5        // repeats what you saw in Chapter 7. 6        // The entire InitGame() function is in program 7        // 8.1 on the CD. The statements below demonstrate 8        // how to load and initialize sounds. 9        if (initOK) 10       { 11           initOK = ballBounceSound.LoadWAV("BallBounce.wav"); 12       } 13 14       if (initOK) 15       { 16           initOK = gameWonMusic.LoadWAV("GameWon.wav"); 17 18       } 19 20       if (initOK) 21       { 22           InitLevel(); 23       } 24 25       return (initOK); 26   } 

As the comments in Listing 8.3 indicate, I've omitted most of the InitGame() function because it's just a repeat of what's found in chapter 7. If you look on the CD in the folder Source\Chapter08\Prog_08_01, you'll find the entire InitGame() function in the file Ping.cpp.

Warning

The programs on the CD are numbered independently of the listings in the corresponding chapters. In earlier chapters, there was usually one program per listing so the numbering matched. However, from this chapter forward, that will rarely be the case. One program is often used for several listings. Program 8.1 is used for Listings 8.1 through 8.5. For Listings 8.6 through 8.9, see Program 8.2. Listing 8.10 is taken from Program 8.3.


Although it's not shown here, the InitGame() function does all the tasks it did in past versions. It loads the bitmap images for the ball, the paddles, the score marker, and the trophy. It sets the bounding rectangles and other initialization information. If all of that completes successfully, InitGame() uses the ballBounceSound member of the ping class to call the LoadWAV() function. LoadWAV() loads the sound effect from the file BallBounce.wav and stores the sound data in a buffer. It also prepares the sound to be played. LlamaWorks2D takes care of managing the sound buffer and other related tasks, so all you have to do is tell ballBounceSound when to play itself.

On line 16 of Listing 8.3, the InitGame() function also loads a sound from the file GameWon.wav. This file contains music that the game plays when a player wins. As you can see from the statements on lines 11 and 16, there is absolutely no difference between loading a sound effect and loading music. The game engine handles them both in exactly the same manner.

Another thing to notice before we move on is that the InitGame() function passed only one parameter in its calls to LoadWAV(). Recall that this is allowed because the last parameter to the LoadWAV() function has a default value. If the game does not need to change the default value for the second parameter, it can omit the value entirely.

Playing Sounds

Playing a sound generally occurs as soon as the event that generates the sound occurs. For example, Ping plays a sound for the ball bounce at the point in the code where the bounce occursin the UpdateFrame() function. It may seem as if I'm stating the obvious here, but I have seen programmers new to games try to wait to play sounds until the RenderFrame() function. They think the sound will synchronize better with the graphics. On the surface, that would seem to be a sensible approach; however, it complicates life unnecessarily.

To understand why delaying sound processing until the RenderFrame() function is not a good idea, let's first examine how Ping handles the ball bounce sound. Listing 8.4 shows Ping's UpdateFrame() function.

Listing 8.4. The UpdateFrame() function for the ping class

 1    bool ping::UpdateFrame() 2    { 3        bool updateOK = true; 4 5        if (!GameOver()) 6        { 7            theBall.Move(); 8            rightPaddle.Move(); 9            leftPaddle.Move(); 10 11           if (BallHitPaddle()) 12           { 13               theBall.Bounce(ball::X_DIRECTION); 14               ballBounceSound.Play(); 15           } 16           // The majority of this function is omitted also. 17           // If you want to see the rest of it, you'll find 18           // it in sample program 8.1. 19 20       } 21 22       return (updateOK); 23   } 

As with Listing 8.3, I've omitted most of the function so that we can focus on handling sounds. If you look at lines 1115, you'll see that the UpdateFrame() function calls the sound::Play() function to play the bounce sound. It makes this call when it detects that the ball has hit the paddle. If Ping were to wait until the RenderFrame() function to play the sound, it would have to detect the collision between the paddle and the ball again. That requires two calls to the BallHitPaddle() function in every frame, which is a waste of time. In a complex game with many sounds, it would most likely produce a slowdown in the frame rate. It's better for your game to play the sound as soon as it discovers the sound needs to be played. Experience shows that it does not cause a loss of synchronization with the action.

The call to the sound::Play() function in Listing 8.4 plays a sound effect. Playing music is no different, as Listing 8.5 demonstrates.

Listing 8.5. Playing music when a player wins

 1    bool ping::DoGameOver() 2    { 3        bool noError = true; 4 5        if (player1Score >= 5) 6        { 7            theTrophy.X(areaOfPlay.right * 3/4); 8            theTrophy.Y(areaOfPlay.bottom/2); 9        } 10       else 11       { 12           theTrophy.X(areaOfPlay.right * 1/4); 13           theTrophy.Y(areaOfPlay.bottom/2); 14       } 15 16       gameWonMusic.Play(); 17       theApp.BeginRenderNow(); 18       theTrophy.Render(); 19       theApp.EndRenderNow(); 20 21       ::Sleep(27000); 22 23       game::DoGameOver(); 24 25       return (noError); 26   } 

Line 16 of Listing 8.5 shows that your game can also play music by calling sound::Play(). The music plays until the end of the file and then quits. The DoGameOver() function calls the Windows Sleep() function to pause for 27 seconds, which is how long it takes to play the triumphal music for the winner.

Adding Background Music

Many games have looping background music. That is, they play a song over and over while the player is in a level. When the level ends, they usually end the music as well. LlamaWorks2D enables you to do the same thing in your games. To demonstrate how this is done, we'll add looping background music to Ping. The first thing to do is to add another data member to the game class, as shown in Listing 8.6.

Listing 8.6. The ping class with a member for background music

 1    class ping : public game 2    { 3    public: 4        bool OnAppLoad(); 5        bool InitGame(); 6        bool InitLevel(); 7        bool UpdateFrame(); 8        bool RenderFrame(); 9 10       ATTACH_MESSAGE_MAP; 11 12       bool OnKeyDown( 13           keyboard_input_message &theMessage); 14 15       bool BallHitPaddle(); 16 17       void SetBallStartPosition(); 18       void SetBallStartDirection(); 19 20       bool DoLevelDone(); 21       bool DoGameOver(); 22 23   private: 24       ball theBall; 25       paddle leftPaddle; 26       paddle rightPaddle; 27       sprite theTrophy; 28 29       sprite scoreMarker; 30       int player1Score; 31       int player2Score; 32 33       bitmap_region areaOfPlay; 34 35       sound ballBounceSound; 36       sound backgroundMusic; 37       sound gameWonMusic; 38   }; 

Line 36 of Listing 8.6 declares a data member of type sound for the background music. This class comes from program 8.2 on the CD.

To get a piece of music to loop over and over, your game must set the second parameter of the sound::LoadWAV() function to true. Listing 8.7 gives a version of the InitGame() function that loads a piece of looping music.

Listing 8.7. Making music loop

 1    bool ping::InitGame() 2    { 3        // Most of this function has been omitted as well. To 4        // see the rest of it, look in program 8.2 on the CD. 5        if (initOK) 6        { 7            initOK = backgroundMusic.LoadWAV("BackMusic. wav",true); 8        } 9 10       if (initOK) 11       { 12           initOK = ballBounceSound.LoadWAV("BallBounce. wav"); 13       } 14 15       if (initOK) 16       { 17           initOK = gameWonMusic.LoadWAV("GameWon.wav"); 18 19       } 20 21       if (initOK) 22       { 23           InitLevel(); 24       } 25 26       return (initOK); 27   } 

As with previous listings, Listing 8.7 omits a large section of the InitGame() function. You can find the entire function in the file Ping.cpp on the CD, in the folder Source\Chapter08\Prog_08_02.

If you look on line 7 of Listing 8.7, you'll see that InitGame() loads the background music by calling LoadWAV(). As stated previously, it sets the second parameter of LoadWAV() to true to tell LlamaWorks2D that the song will loop when played.

Warning

LlamaWorks2D does not provide a way to turn song looping on and off while the game is in progress. You can set it only when you load a song file.


After a file is loaded as a looping song, your game starts it playing by calling the Play() function. This is usually done at the beginning of the level, which is what Ping does. Listing 8.8 demonstrates this.

Listing 8.8. Starting a looping song

 1     bool ping::InitLevel() 2     { 3         bool initOK = true; 4         bitmap_region boundingRect = 5             leftPaddle.BoundingRectangle(); 6 7         leftPaddle.X(5); 8         int paddleHeight = boundingRect.bottom - boundingRect.top; 9         int initialY = 10            ((areaOfPlay.bottom - areaOfPlay.top)/2) - 11            (paddleHeight/2); 12        leftPaddle.Y(initialY); 13 14        int bitmapWidth = rightPaddle.BitmapWidth(); 15        int initialX = areaOfPlay.right - bitmapWidth - 5; 16        rightPaddle.X(initialX); 17        rightPaddle.Y(initialY); 18 19        SetBallStartPosition(); 20        SetBallStartDirection(); 21 22        backgroundMusic.Play(); 23 24        LevelDone(false); 25        return (initOK); 26   } 

This version of InitLevel(), which is also from Program 8.2 on the CD, starts the background music playing each time a level is initialized. You can see the call to the Play() function on line 22. That's all it takes. The song will play over and over until the level ends.

When Ping finishes a level, the game stops the background music and rewinds it, as shown in Listing 8.9.

Listing 8.9. Stopping and rewinding music

 1     bool ping::DoLevelDone() 2     { 3         backgroundMusic.Stop(); 4         backgroundMusic.Rewind(); 5         ::Sleep(2000); 6         return (true); 7     } 

The game engine automatically calls DoLevelDone() as soon as a level finishes. The DoLevelDone() function calls the sound::Stop() function on backgroundMusic to stop the background music from playing. It then calls sound::Rewind() to reset play to the beginning of the song. When the game begins the next level and plays the background music, the song starts over from its beginning.

If your game uses background music in the same manner as Ping, other sounds can occur while the background music plays. The fact that the game plays multiple sounds makes no difference at all to the game engine. All of the sounds are automatically mixed together to play at the same time. Sound mixing is not something you need to worry about or pay attention to. Just play sounds whenever you need to. LlamaWorks2D plays multiple sounds properly.

Controlling the Volume

Almost all modern games provide a way to set the sound volume. In fact, many games enable you to separately control the volume of various types of sounds. Most games that do this feature individual volume controls for special effects, music, and other sounds. For instance, I have a flight combat simulator game in which the commanding officer assigning the missions to the player often shouts out instructions during the mission in a (stereotypical) grizzly soldier voice. I find it extremely annoying and unnecessary because the same instructions are printed along the bottom of the screen. Fortunately, the game has a volume slider in its setup screen that enables me to completely turn off that sound.

Tip

I strongly recommend that you provide a way for players to set the volume of your game. It's even better to let them set the volumes of different types of sounds, such as sound effects, background music, and so forth.


LlamaWorks2D makes it easy for your game to control the volume of sounds pro-grammatically. The sound class provides a Gain() function for setting the volume.

Sound engineers call the sound volume the gain. Your game can set a sound's gain at any time after it is loaded.

Listing 8.10 gives a version of Ping's InitGame() function that sets the gain of the sounds it loads. Once again, I've omitted the parts of the InitGame() function that don't deal with sounds. If you want to see the entire function, you'll find it on the CD in the folder Source\Chapter08\Prog_08_03 in the file Ping.cpp.

Listing 8.10. Setting the gain

 1     bool ping::InitGame() 2     { 3 4         // Most of this function has been omitted. To see the 5         // rest of it, look in program 8.3 on the CD. 6         if (initOK) 7         { 8             initOK = backgroundMusic.LoadWAV("BackMusic. wav", true); 9         } 10 11        if (initOK) 12        { 13            backgroundMusic.Gain(0.4f); 14            initOK = ballBounceSound.LoadWAV("BallBounce. wav"); 15        } 16 17        if (initOK) 18        { 19            ballBounceSound.Gain(0.4f); 20            initOK = gameWonMusic.LoadWAV("GameWon.wav"); 21 22        } 23 24        if (initOK) 25        { 26            gameWonMusic.Gain(0.4f); 27            InitLevel(); 28        } 29 30        return (initOK); 31    } 

This version of the InitGame() function sets the volume of the game's sounds by calling the sound::Gain() function after it loads each sound. The gain is always a floating point number between 0.0 and 1.0, inclusive. As you might expect, 0.0 means no sound, 1.0 is the maximum volume, 0.5 is half volume, 0.75 is 3/4 volume, and so on.

Note

Setting the gain on individual sounds does not change the volume of the player's computer; it only changes the volume of that particular sound.




Creating Games in C++(c) A Step-by-Step Guide
Creating Games in C++: A Step-by-Step Guide
ISBN: 0735714347
EAN: 2147483647
Year: N/A
Pages: 148

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net