Contact

 
Google
Web www.alanphipps.com

 
   
 
   
www.alanphipps.com

.: Sound and MultiThreading

 
 

 

In the last tutorial, I ended by telling you that I would work on either adding sound or multithreading for this tutorial. After giving it a little more thought, I realised that it was the sound itself that I wanted to thread and hence this tutorial will include both. So, as usual, copy the source code folder from the last tutorial and rename it to AudioAndMultithreading, however, this time we won't open the project just yet.

In order to use Sound in XNA, microsoft has provided us with the Cross-Platform Audio Creation Tool - XACT, which was included with Game Studio Express. Start the program and you will be shown the following window:

 

XNA in VB.NET - XACT New Project

Before we discuss how to use the XACT Tool, I'll explain the basics. XNA at present uses only windows wave (.wav) or .aif files (as far as I remember the aiff file is the MacIntosh version of the wav file, but i could be wrong, anyway.). Hopefully in the future, this will be expanded to include other audio files, but we will have to wait and see. The wavs, once selected, are complied into a wave bank (.xwb file) that is preloaded into the game before they are used. A Sound Engine (.xgs file) and a sound bank (.xsb) are also necessary, all files must be initialized before any sound can be played.

So, now that we know what we are gong to be creating, lets make them now. Right-Click on the Wave Banks icon and select "New Wave Bank", do the same for Sound Banks:

XNA in VB.NET - XACT - New Wave Bank

Right-Click on the Wave Bank window that appeared and select "Insert Wave File(s)", then drag that file onto the Sound Bank window, underneath Cue Name:

XNA in VB.NET - Wav Added

As you can see the wav file is now loaded into both the Wave Bank and the Sound Bank. Remember the cue name because that is how you will refer to the file from the code.

XNA in VB.NET - Looping

Next, locate the section shown above and left click on the words "Play Wave". If this particular wav files is used for background music then on the bottom left of the screen locate the property LoopEvent:

XNA in VB.NET - LoopEvent

and change it to infinite. This wav file will now play continuously.

Add any more wavs that you need and save the file to any location of your choosing, the project will be saved as a .xap file. Lastly, go to File/Build:

XNA in VB.NET - Building

The files will be saved in the same location as the xap file. There will be a Win folder and an XBox folder.

Place all files into a folder called Audio and place this folder into your project folder, so that it is at the same level as the images folder.

Next, onto the code.

First, we will create a new Class called Sound: and add the following code:

Imports Microsoft.Xna.Framework
Imports Microsoft.Xna.Framework.Audio
Imports System.Threading

Public Class Sound

#Region "Objects and Variables"
Private Cue As Cue
Public CueName As String
#End Region

Public Sub Play(ByVal obj As Object)

Try
Cue = SoundBank.GetCue(CueName)
Cue.Play()
Catch ex As Exception
End Try

End Sub

Public Sub StopAudio()

Try
Cue.Stop(AudioStopOptions.Immediate)
Catch ex As Exception
End Try

End Sub

Public Shared Sub InitializeEngine()

AudioEngine = New AudioEngine("..\..\Audio\gameaudio.xgs")
WaveBank = New WaveBank(AudioEngine, "..\..\Audio\Wave Bank.xwb")
SoundBank = New SoundBank(AudioEngine, "..\..\Audio\Sound Bank.xsb")

'Initialize background Music
MusicTrack.CueName = "Laurent Garnier - The Man With The Red Face"
MusicThread = New Thread(New ParameterizedThreadStart(AddressOf MusicTrack.Play))

End Sub

Public Shared Sub DisposeEngine()

SoundBank.Dispose()
WaveBank.Dispose()
AudioEngine.Dispose()

End Sub

Public Shared Sub UpdateEngine()
AudioEngine.Update()
End Sub

End Class

Module SoundModule
Public AudioEngine As AudioEngine
Public WaveBank As WaveBank
Public SoundBank As SoundBank
' Thread and Sound to be used for playing the background music
Public MusicThread As Thread
Public MusicTrack As New Sound
End Module

This code basically defines the objects and methods of the Sound class. A cue is the XNA term for the Wav file, The cuename, if you remember, was determined when we inserted the wav file into the Sound Bank. The methods Play and StopAudio, are self explanatory. InitializeEngine creates the objects that are necessary to play all sounds, this will be called from IntializeGraphics() Sub. DisposeEngine destroys the Sound Objects, should that be necessary. UpdateEngine, I'm told is necessary and should be called at regular intervals. Lastly is a Module that holds the Engine objects and the background music thread and track.

The InitializeGraphics Sub, has an addition as mentioned above:

Public Function IntializeGraphics()

Try
Dim presentParams As New PresentationParameters
presentParams.SwapEffect = SwapEffect.Discard
Dim XNAGraphicsAdapater As Microsoft.Xna.Framework.Graphics.GraphicsAdapter = Graphics.GraphicsAdapter.Adapters.Item(0)
Device = New Graphics.GraphicsDevice(XNAGraphicsAdapater, DeviceType.Hardware, Me.Handle, CreateOptions.SoftwareVertexProcessing, presentParams)
AddHandler Device.DeviceReset, AddressOf OnDeviceReset

Sound.InitializeEngine()

Return True

Catch ex As Exception

Return False
End Try

End Function

This creates the objects that are necessary to play sounds in XNA. Next we have a change to the OnPaint method:

Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)

'If Device has been lost or reset
OnDeviceReset(Device, Nothing)

'Display framerate on window title bar
Me.Text = String.Format("The framerate is {0}", Framerate.CalculateFrameRate())

'Update Audio Engine
Sound.UpdateEngine()

'Render graphics to screen
Render()

'Force windows to redraw the window
Me.Invalidate()

End Sub

The Audio Engine is updated every time the window is redrawn. The Form1_Load code becomes:

Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

'Create Objects and Variables
DefineObjectDetails()
'Setup the Text bitmaps that are static and wont chnage during the game
WriteText("Loading", "Mirror", 26, FontStyle.Bold, Brushes.Black, _
320, 300, LoadingTexture, LoadingVector)

'forces windows to only use the Onpaint method to draw the window
Me.SetStyle(ControlStyles.AllPaintingInWmPaint Or ControlStyles.Opaque, True)

'Get High Score from High Score File
If My.Computer.FileSystem.FileExists(Application.StartupPath & "\HighScore.txt") = True Then
Try
Using sr As StreamReader = New StreamReader(Application.StartupPath & "\HighScore.txt")
Dim x As String = sr.ReadToEnd
sr.Close()
If IsNumeric(x) = True Then
HighScore = CInt(x)
End If
End Using
Catch ex As Exception

End Try
End If

'Play Background Music
MusicThread.Start()

End Sub

This new code starts the thread that holds the Background Music Cue, and therefore starts the music. No changes to the Render loop this time so onto the Graphics Module, where there is a single change to the CheckForCollision Sub:

'Check for Ball Collision
Public Function CheckForCollision(ByVal Singleball As Ball)
Dim Result As Boolean = False

'Each ball will be enclosed in a bounding sphere. The BoundingSphere.Intersects method allows us to tell when
' two balls collide. The following objects are part of this process. The objects are declared here so that they are
' not declared in every loop, which would cause more work for the program
'One BoundingSphere for the Moving Ball

Dim TempBoundingSphereMovingBall As BoundingSphere = New BoundingSphere(New Vector3(BallsArray(IndexOfMovingBall).BallRect.X, _
BallsArray(IndexOfMovingBall).BallRect.Y, 0), 18)
Dim TempBoundingSphereOtherBall As BoundingSphere ' One BoundingSphere for the Other ball in the collision.

'For Each Singleball As Ball In BallsArray ' check each ball for a collision
If Singleball.IsDeleted = False Then 'Only those balls taht are still inplay
If Singleball.IsDropped = False Then 'Only those balls that have not been dropped
If Singleball.MarkedForDeletionDrop = False Then
If Singleball.MarkedForDeletionExplode = False Then
If Singleball.IsMoving = True Then ' If the current ball is the ball that has just been launched then
Return Result
Exit Function ' goto to the next ball
End If
If Singleball.IsInLauncher = True Then ' If the current ball is lower than the ball launcher
Return Result
Exit Function ' goto the next ball
End If

'Set the boundingsphere to the boundaries of the current ball
TempBoundingSphereOtherBall = New BoundingSphere(New Vector3(Singleball.BallRect.X, Singleball.BallRect.Y, 0), 18)

If TempBoundingSphereMovingBall.Intersects(TempBoundingSphereOtherBall) = True Then 'Check for a collision

'Set Public variable to be used in PositionBallCorrectly()
BoundingSphereOtherBallPub = TempBoundingSphereOtherBall

BallsArray(IndexOfMovingBall).BallXVel = 0 ' Stop the Ball in the X direction
BallsArray(IndexOfMovingBall).BallYVel = 0 ' Stop the ball in the Y direction
BallsArray(IndexOfMovingBall).IsMoving = False ' Set the ball's IsMoving variable to False
IndexOfMovingBallFound = False ' Tell the program to find the next moving ball
ABallHasBeenLaunched = False ' Set the Ball Moving Global variable to false
TempBoundingSphereMovingBall = Nothing ' Empty the variables
TempBoundingSphereOtherBall = Nothing
ABallNeedsPositionedCorrectly = True 'Ball will now be positioned correctly

'Play Collision Sound
Dim Track1 As New Sound
Track1.CueName = "PoolBallHit"
Dim Thread1 As New Thread(New ParameterizedThreadStart(AddressOf Track1.Play))
Thread1.Start()

Result = True
Return Result
Exit Function

End If
End If
End If
End If
End If
NextBall:

' The loop is finished and are disposed
TempBoundingSphereMovingBall = Nothing
TempBoundingSphereOtherBall = Nothing

Return Result

End Function

The new code here creates an instance of the sound class, sets it to the wav file PoolBallHit.wav and then allocates it to Thread1. Thread1 is then started. PoolBallHit.wav is another wav file that I added earlier and plays when two balls collide.

If you wanted to play "PoolBallHit" without using a thread, the the following code would work:

Dim Track1 As New Sound
Track1.CueName = "PoolBallHit"
Track1.Play(New Object)

When creating a new thread, as above, you have to use a delegate of the Sound.Play Sub. A delegate is basically a variable that holds the memory address of a function, and they can be quite frustrating to learn, so, I'm not going to teach you here. A delegate takes an object as a parameter and hence, so must the Play sub, so, that is why the Track1.play() sub above shows a New Object parameter. The new object is not used, assigned or modified in any way, but the function signature must match that of the delegate.

Lastly, add the following line to the Imports section of the Graphics Module

Imports System.Threading

 

IMPORTANT***** - I have not included the Wave Bank.xwb file in the source code as it was 45Mb. I have also commented out a line of code in the Sound.InitializeEngine so that the game does not attempt to load the wave bank file. If you want to hear the music you will have to create your own Wave Bank and un-comment the line. Remember to change the CueName in the code to that of your wav file.

I think our game is nearly finished, in the next tutorial, I'm going to work on making our game look nice, do a few more textures, download some Video Game Music, and work on the game playability. Once all assets are done I'll give the content pipeline a shot and upload it all to the tutorials page, so, until then, Enjoy.

 

SoundAndMultiThreading Source Code - 410Kb
Next Tutorial - Game Aesthetics

 

 

     
 
 
     

 

Web site contents © Copyright Alan Phipps 2006, All rights reserved.
Website templates
   
 
 

 

__PayPal

PayPal - Any Amount is Welcome
 
Please Donate to the Nvidia Geforce Go 7950 GTX Fund, All donations welcome. Thanks.

 

XNA in C#

 
 

Games at Amazon