Beginning Game Development Part X –Direct Sound Part III
Why more sound
You may be wondering why we even need to add longer sound files to the game. As I mentioned in article 8, most games include fully featured soundtracks in addition to the regular game sounds. These sound tracks are often produced specifically for the game much like a movie soundtrack is produced for a movie. A well made sound track can add additional drama to the game and provide a memorable experience. Sometimes these game tracks are also available as separate music CDs or downloads to bring more players to the game. There is really no way that anyone is going to be able to produce a nice sounding soundtrack as a WAV file. Most music uses the MP3 codec or can be converted to this codec.
Audio Playback
Using the DirectSound namespace we were only able to play WAV files. Longer music is normally encoded in more efficient formats such as MP3 or WMA. To play these files we have to access the API in the AudioVideoPlayback namespace. This namespace is the smallest of the namespaces and contains a single class called Audio for playing audio files. (It also contains a Video class which I will not cover)
Using the Audio class to play an audio file is very easy.
1. Create a new Audio class and pass the path to the Audio file to the constructor.
Audio audio = new Audio("AudioFileName"); |
Audio audio = new Audio();
audio.Open("AudioFileName");
audio.Play();
audio.Pause();
audio.Stop();
audio.Dispose();
private void _audio_Ending(object sender, EventArgs e)
{
audio.Play();
}
audio.Open("//www.audiofile.com/music.mp3");
While playing the audio file you can control the volume and balance of the file.
Volume: Volume is expressed as an integer. Somewhat counterintuitive is that a setting of 0 is full volume. Smaller values decrease the volume up to a setting of -10,000 which is silent.
audio.Volume = 0; // Maximum
audio.Volume = -10000; // Silent
audio.Balance = -10000; // Only Left channel
audio.Balance = 10000; // only Right channel
audio.Balance = 0; // balanced
State | Method | Event | Property (Boolean) |
---|---|---|---|
Started | Play | Starting | Playing* |
Paused | Pause | Pausing | Paused |
Stopped | Stop and StopWhenReady | Stopping | Stopped** |
Ended | N/A | Ending |
* While playing the audio class indicates the current position of the stream with theCurrentPosition property.
** The audio class also provides a property, StopPosition, which indicates the position in the audio stream where the file is stopped
You can also use the State property of the Audio class to determine its state. This property returns a StateFlags enumeration with the possible values of: Running,Paused and Stopped.
Also of interest is the SeekingCaps property of the Audio class. This read only property indicates the seeking capabilities of the playback stream. It is represented in the form of the SeekingCaps structure. This structure indicates whether the stream supports the following seek actions:
Also of interest is the SeekingCaps property of the Audio class. This read only property indicates the seeking capabilities of the playback stream. It is represented in the form of the SeekingCaps structure. This structure indicates whether the stream supports the following seek actions:
Action | SeekingCaps property (Boolean) |
---|---|
Current position | CanGetCurrentPostion |
Duration | CanGetDuration |
Stop position | CanGetStopPostition |
Absolute | CanSeekAbsolute |
Seek Forwards | CanSeekForward |
Seek Backward | CanSeekBackward |
You should check the capabilities of the stream before calling SeekCurrentPosition, SeekStopPosition or checking the Duration property.
Ok, let's package this simple class into a Jukebox. I want the Jukebox class to pull files from a predetermined location so I can just point it at a media folder within the games directory structure. I also want the Jukebox to either play all the files it finds in order or play a single file in a looping fashion.
Finally I want this class to play a specific file on demand, for example, when I am on the splash screen I want to play the intro tune, while on the main game screen I want some other background music.
NOTE: I am not providing any audio files with the source code. By default the SoundTrack class reads from C:\Documents and Settings\All Users\Documents\My Music\Sample Music where you should have two WMA files on a standard XP machine. If you do not have those files you need to change the directory and point it at a location that has sound files.
Creating the SoundTrack class
The first step is to add a reference to the Microsoft.DirectX.AudioVideoPlaybackassembly.
Next we add a new class and call it Soundtrack. At the top of the class we need add the using statement for the AudioVideoPlayback namespace.
Next we add a new class and call it Soundtrack. At the top of the class we need add the using statement for the AudioVideoPlayback namespace.
using Microsoft.DirectX.AudioVideoPlayback;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!this._disposed)
{
if (disposing)
{
if (_audio != null)
_audio.Dispose();
}
}
_disposed = true;
}
// Use C# destructor syntax for finalization code.
~SoundTrack()
{
// Simply call Dispose(false).
Dispose(false);
}
private bool _disposed;
public SoundTrack ( string[] songCollection, bool repeat )
{
_songList = songCollection;
_isRepeat = repeat;
if ( _songList.Length == 0 )
_enabled = false;
else
_audio = new Audio ( _songList[ _songCounter ].ToString ( ) );
}
private Audio _audio;
private string[] _songList;
private int _songCounter = 0;
private bool _isRepeat;
private bool _enabled = true;
Now we add the Play method to the class.
public void Play()
{
if ( !_enabled )
return;
_audio.Play();
_audio.Ending += new EventHandler(_audio_Ending);
}
private void _audio_Ending(object sender, EventArgs e)
{
if ( _isRepeat )
{
Stop ( );
Play ( );
}
else
NextSong ( );
}
public void NextSong ( )
{
if ( !_enabled )
return;
_songCounter++;
if ( _songCounter == _songList.Length )
_songCounter = 0;
if ( _audio.Playing == true )
_audio.Stop ( );
_audio.Open ( _songList[ _songCounter ].ToString ( ) );
_audio.Play ( );
}
The only remaining method we now need is to stop the player
public void Stop ( )
{
if ( !_enabled )
return;
if ( _audio != null )
_audio.Stop ( );
}
To allow us to control the balance and volume we simply add two properties to the SoundTrack class that expose the identical properties of the Audio class. Since we know the upper and lower bounds of the values, we add a simple check to the property setter to ensure we only pass valid values. This is more efficient than passing an invalid value and having to check for exceptions. In general you should enforce boundaries whenever possible.
1: public int Volume
2: {
3: get { return _audio.Volume; }
4: set
5: {
6: if ( value > 0 | value < -10000 )
7: return;
8:
9: _audio.Volume = value;
10: }
11: }
12:
13: public int Balance
14: {
15: get { return _audio.Balance; }
16: set
17: {
18: if ( value > 10000 | value < -10000 )
19: return;
20:
21: _audio.Balance = value;
22: }
23: }
Now we can integrate the new class into the game. First we add a reference to the new class to the GameEngine class.
private SoundTrack _mainSoundTrack;
_mainSoundTrack = new SoundTrack ( Directory.GetFiles ( @"C:\Documents" +
"and Settings\All Users\Documents\My Music\Sample Music" ), true );
// Control the Sound
if ( _keyboard.State[ Key.P ] )
_mainSoundTrack.Play ( );
if ( _keyboard.State[ Key.M ] )
_mainSoundTrack.Stop ( );
if ( _keyboard.State[ Key.N ] )
_mainSoundTrack.NextSong ( );
if ( _keyboard.State[ Key.UpArrow ] )
_mainSoundTrack.Volume += 10;
if ( _keyboard.State[ Key.DownArrow ] )
_mainSoundTrack.Volume -= 10;
if ( _keyboard.State[ Key.RightArrow ] )
_mainSoundTrack.Balance += 10;
if ( _keyboard.State[ Key.LeftArrow ] )
_mainSoundTrack.Balance -= 10;
Summary
Now we have pretty much completed the pass through the basic functionality of the all the DirectX namespaces. There are many more advanced topics that could cover additional graphics classes such as HLSL (High Level Shader Language), but in the next articles we are going to focus on two non DirectX parts of game development - Artificial Intelligence (AI) and Physics.
Without good AI and Physics, even the best designed games are no fun to play. Nothing shatters the illusion of reality more than predictable enemies or rocks floating in mid air.
Together with game Physics, AI represents a major portion of the game that has no support in DirectX and limited support in other (free) libraries. We are not going to be able to fill that gap and write a fully featured AI engine, but we can cover most of the principles and techniques behind some of the more basic AI problems, such as chasing and evading and path finding.
Until then: Happy coding.
Derek Pierson is a software developer with 12 years experience designing and developing enterprise applications using a variety of programming languages. He is an enthusiastic teacher of the art of programming and believes that elegance and simplicity are defining features of good software.
0 comments:
Post a Comment