COMP 160 Lab 7: Sounds and Text in C#

Provided code
This lab covers adding audio to your game, as well as text that you can change within the code (as opposed to making an image of text, and drawing the image). The former can add ambiance or a finishing touch to a game, while the latter makes things like menus, scores, and exposition much easier on both programmer and artist.

Text

All text drawing is handled by the SpriteFont class, in conjunction with the SpriteBatch class we've been using frequently. There are two ways to create a SpriteFont:

If you create a font the first way (or look at the Arial.spritefont file included in the provided code), you can open it up in Visual Studio and see its contents. You may notice that it is simply XML, with very helpful comments. Here is the Arial.spritefont file from the provided code:
<?xml version="1.0" encoding="utf-8"?>
<!--
This file contains an XML description of a font, and will be read by the XNA
Framework Content Pipeline.  Follow the comments to customize the appearance
of the font in your game, and to change the characters which are available
to draw with.
-->
<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
  <Asset Type="Graphics:FontDescription">

    <!--
    Modify this string to change the font that will be imported.
    Redistributable sample fonts are available at
    http://go.microsoft.com/fwlink/?LinkId=104778&clcid=0x409.
    -->
    <FontName>Arial</FontName>

    <!--
    Size is a float value, measured in points.  Modify this value to change
    the size of the font.
    -->
    <Size>14</Size>

    <!--
    Spacing is a float value, measured in pixels.  Modify this value to change
    the amount of spacing in between characters.
    -->
    <Spacing>0</Spacing>

    <!--
    UseKerning controls the layout of the font.  If this value is true,
    kerning information will be used when placing characters.
    -->
    <UseKerning>true</UseKerning>

    <!--
    Style controls the style of the font.  Valid entries are "Regular",
    "Bold", "Italic", and "Bold, Italic", and are case sensitive.
    -->
    <Style>Regular</Style>
    
    <!--
    CharacterRegions control what letters are available in the font.  Every
    character from Start to End will be built and made available for drawing.
    The default range is from 32, (ASCII space), to 126, ('~'), covering the
    basic Latin character set.  The characters are ordered according to
    the Unicode standard.  See the documentation for more information.
    -->
    <CharacterRegions>
      <CharacterRegion>
        <Start>&#32;</Start>
        <End>&#126;</End>
      </CharacterRegion>
    </CharacterRegions>
  </Asset>
</XnaContent>
The comments explain the use of each tag quite well. The FontName tag must contain the name of a font installed to the computer you compile with, but need not be installed to the machine that plays the game.

With this XML, Visual Studio can create a *.xnb file for your font at compile time. *.xnb is a file type used by XNA for many things; in this case, the XNB is an image containing all of the characters specified by the Start and End elements in the XML, in the font specified by FontName, at the specified size, etc. Because the font becomes an image at compile time, a player does not need to have your arbitrary font installed in order to play.

Sometimes, for whatever reason, this method fails to work correctly, or the license for the font you've bought may not allow you to do this. An alternative is to generate the image of the font yourself; the second method mentioned above. If you look at Lindsey.bmp, youll see what the image looks like. This is the same image format (a "bitmap") generated by the compiler when you use your XML font description. By setting the Content Processor to Sprite Font Texture, the compiler will do everything to the bitmap that it did to the XML, except for generating the bitmap (since the bitmap has already been generated).

The specification for this sprite font texture is a bit too complicated to draw one from scratch (and if you open the image in Photoshop, for example, you'll notice there is also an alpha layer that will be used by the game). Fortunately, helpful people have solved that problem, by creating the TTF to BMP Converter tool. This program will allow you to select a font from the list of fonts installed on the computer, and set all of the values you would be able to change with the XML file, and then saves the resulting bitmap for you.

Once you've got either a SpriteFont XML file or a Sprite Font Texture loaded into your game's content pipeline, using the font is simple. First, you'll load a SpriteFont object in the same way we've loaded all of our Texture2Ds so far — with the Content.Load<SpriteFont> method. Then, rather than SpriteBatch.Draw, we'll use SpriteBatch.DrawString. Many of the parameters for DrawString are the same as those in Draw. Simply remember to pass a SpriteFont instead of a Texture2D, and a String that you want to write!

Sound

XNA has only one format that it knows how to use by default for sounds. While it is possible to create importers for other formats, the process would be complicated, and would require know the file format specifications of whatever you wished to use. However, XNA can simply and easily utilize files created with XACT (Cross-Platform Audio Creation Tool), which is what we will be using in this lab.

First, you need sound files. XACT needs files in *.wav, *.aif, or *.aiff format, but you can find tools to convert sounds files between audio formats. The two WAV files included in the provided code were originally MP3s, converted with Audacity (Audacity 1.3 is installed on the OwlNet computers).

You can find XACT in the start menu on the OwlNet machines under Programs > Microsoft Visual Express > Microsoft XNA Game Studio 4.0 > Tools > Microsoft Cross-Platform audio Creation Tool (XACT). To make sounds that you can use in your XNA game, create a new project (File > New Project), and save it in your Content directory. Create a new Wave Bank and a new Sound Bank (right click on "Wave Banks" and "Sound Banks" and select New). With your Wave Bank window selected, choose "Insert Wave Files(s)" (Ctrl+W) from the Wave Banks menu. Drag the WAV files you included to the lower half of the Sound Banks window ("Cue Names" list).

This is the extent of what is required of you in the XACT program. However, you can also use XACT to modify many attributes of your sounds — feel free to experiment. Note: If you wish to make modifications to your sounds, you must run the XACT Auditioning Utility (found in the Tools folder where you ran XACT from), and then select Audition > Connect To… in your XACT project. Select whichever item in the list is named "local". If you do not connect to an auditioning server, your sounds will fail to play inside of XACT, and if you do not have the Auditioning Tool running, you will be unable to connect to your local computer as an auditioning server.

Save your XACT project, and make sure the *.xap file and all of the audio files included in the project are in your Content directory. Add the *.xap file to your game with the Add Existing Item command. (Do not add your audio files to your Content subproject.) When you next compile your game, the compiler will generate a *.xgs, a *.xsb, and a *.xwb file for the General Settings, Sound Bank, and Wave Bank, respectively. You will need to use all three of these files to get your sounds to work.

To make use of your sounds, you will need three objects added to your game: an AudioEngine, a SoundBank, and a WaveBank. If you wish to change your sound volume in code or stop multiple sounds simultaneously, you will also need at least one AudioCategory object.

Unlike all of the other content we've loaded thus far, we cannot use Content.Load for our audio:

engine = new AudioEngine(@"Content\Audio\SoundTextGame.xgs");
soundBank = new SoundBank(engine, @"Content\Audio\Sound Bank.xsb");
waveBank = new WaveBank(engine, @"Content\Audio\Wave Bank.xwb");
You may notice that the names of the .xsb and .xwb files correspond to the names of the Sound Bank and Wave Bank in the XACT project. If you wish to use an AudioCategory, instantiate it with the AudioEngine.GetCatgory method. (By default, your XACT project will have categories "Default" and "Music", and all of your sounds will be placed in the default category. You can create more categories and put sounds in different categories from within XACT.)

To simply play one of your sounds until it is complete, use SoundBank.PlayCue. If you wish to have finer control over the sound (such as pause, resume, stop, or modifying things such as pitch and doppler distance), create a Cue object with SoundBank.GetCue, and then use that created Cue object to play, stop, or alter the sound.

To modify the volume of your sound, use AudioCategory.SetVolume — this will change the volume of all sounds in the category.

Common Gotchas with XACT sound

The first one is a simple one. Call AudioEngine.Update. Your sound may work for a long time without calling it, but eventually it will crash and burn. Place a call to your audio engine's Update in your game's Update method, and all should be well.

The second one is a little bit more difficult. Consider the following:

Cue laser = soundBank.GetCue("laser_fire");
laser.SetVariable("Distance", Position);
laser.Play();
What's wrong with it? Seems simple enough — we want to set the distance of our sound based on our position, to slightly alter the sound of the laser fire. However, this code comes with consequences, particularly if used on a sound longer than the one second laser_fire. Each time this code runs, a new instance of the Cue is created, and then all references to that Cue are lost after the method completes. Because there are no references to the Cue, the garbage collector knows that it's ok to remove the Cue from memory. But if the garbage collector runs and deletes the Cue before the Cue finishes playing, the sound will cut off in the middle.

The solution to this second gotcha is to either use SoundBank.PlayCue (in which case, you cannot choose to stop the Cue prematurely, and cannot modify any variables attached to it), or hold on to your Cue reference so that the garbage collector does not dispose of it.

Lab Assignment

Alternatively, adding sound and text to your own arcade game by next week is also sufficient, as long as you're using the same technology.