When it’s time to play sound effect enhancing your game’s feeling, you need some flexible control over playback of the sound itself.
Then it come to SoundEntity with this post. Imagine when you start your game with the welcome mainmenu screen with only 2 UI Buttons available for users to click on which are “Play”, and “Quit”. Also you want some kind of special setup, when the mouse cursor is on the button, you want to play a “MouseOver” sound only one time. And also when users click the mouse, you want to play a “MouseClicked” sound as well. But apart from those two situations, when the mouse is not on those two buttons, you didn’t play any sound thus just stop any currently in playing state of those sounds.
See the concept?
Yep, you can do just like that with the simple code even not bother creating another class to do the job, but the code will be surely mess and dirty as you need to manage and handle all those dirty variables along the line, and they will be left in the game logic space unnecessary !
So by creating a sound wrapper control class we could do the trick. I named it “SoundEntity” (as we all have Entity, Entity2D, or Entity3D, why can’t be any SoundEntity right?
Let me point out first before we go down the line. The approach used here is not touch with XACT, just plain SoundEffect class. It’s more convenience as you don’t need much more special stuff, just load and play it with some optionals properties to set prior to playing -> volume, pitch, pan, and looping. With those setting, we are just enough in happy mood!!
The code below is from my Wekarusung Engine’s SoundEntity class. Let’s see the code below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 | //----------------------------------------------------------------------------- // SoundEntity.cs // Wekarusung Engine // // Description: Represents the single sound control, and playback. // // Programmer: Haxpor (haxpor@gmail.com) //----------------------------------------------------------------------------- using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Graphics; using Wekarusung.Framework; using Wekarusung.Framework.Interface; namespace Wekarusung.Framework.Sound { /// <summary> /// Represents the single sound control, and playback. /// </summary> public class SoundEntity { /// <summary> /// Sound effect. /// </summary> private SoundEffect sound; private SoundEffectInstance instance; private bool isLoop = false; private float volume = 1.0f; private float pitch = 0.0f; private float pan = 0.0f; // application-related variables private bool markPlayed = false; /// <summary> /// Get whether this sound entity is playing. /// </summary> public bool IsPlaying { get { if (instance != null) return instance.State == SoundState.Playing ? true : false; else return false; } } /// <summary> /// Get whether this sound entity is stopped. /// </summary> /// <remarks>If the sound is not played yet, this property also returns true.</remarks> public bool IsStopped { get { if (instance != null) return instance.State == SoundState.Stopped ? true : false; else if (GetState() == null) return true; else return false; } } /// <summary> /// Get whether this sound entity is paused. /// </summary> public bool IsPaused { get { if (instance != null) return instance.State == SoundState.Paused ? true : false; else return false; } } /// <summary> /// Get or set the loop play for this sound effect. /// </summary> public bool IsLoop { get { return isLoop; } set { isLoop = value; } } /// <summary> /// Get or set volume of this sound entity. /// </summary> /// <remarks>The value can range from 0.0f (silence) to 1.0f (full volume)(</remarks> public float Volume { get { return volume; } set { volume = value; } } /// <summary> /// Get or set the picth of this sound entity. /// </summary> public float Pitch { get { return pitch; } set { pitch = value; } } /// <summary> /// Get or set pan of this sound entity. /// </summary> /// <remarks>The value can range from -1 (full left) to 1 (full right).</remarks> public float Pan { get { return pan; } set { pan = value; } } /// <summary> /// Gets whether this sound entity is marked as played. /// </summary> public bool IsMarkedAsPlayed { get { return markPlayed; } } /// <summary> /// Gets whether this sound entity is marked as stopped. /// </summary> public bool IsMarkedAsStopped { get { return !markPlayed; } } /// <summary> /// Create a sound entity. /// </summary> /// <param name="path">Path of the sound file</param> public SoundEntity(string path) { sound = Engine.Content.Load<SoundEffect>(path); instance = null; } /// <summary> /// Release the resource used by this sound entity. /// </summary> public void Dispose() { sound.Dispose(); instance = null; } /// <summary> /// Play this sound entity from the specification of the setting. /// </summary> public void Play() { instance = sound.Play(volume, pitch, pan, isLoop); } /// <summary> /// Play this sound entity from the specification of the setting but in loop. /// </summary> public void PlayLoop() { instance = sound.Play(volume, pitch, pan, true); } /// <summary> /// Resume playing this sound entity. /// </summary> public void Resume() { if (instance != null && instance.State == SoundState.Paused) { instance.Resume(); } } /// <summary> /// Stop this sound entity. /// </summary> public void Stop() { if (instance != null) { instance.Stop(); } } /// <summary> /// Pause this sound entity. /// </summary> public void Pause() { if (instance != null && instance.State == SoundState.Playing) { instance.Pause(); } } /// <summary> /// Get the current state of the sound. /// </summary> /// <returns>SoundState if its instace is not null, otherwise returns null.</returns> public SoundState? GetState() { if (instance != null) return instance.State; else return null; } /// <summary> /// Mark this sound as it's played. /// </summary> /// <remarks>Users must manually handle and call this method in order to apply playing this sound entity for one time in particular /// circumstance.</remarks> public void MarkAsPlayed() { markPlayed = true; } /// <summary> /// Mark this sound as it's stopped. /// </summary> /// <remarks>Users must manually handle and call this method in order to apply playing this sound entity for one time in particular /// circumstance.</remarks> public void MarkAsStopped() { markPlayed = false; } } } |
From the code, when we play sound with SoundEffect class, we would call Play() method. It will return the SoundEffectInstance, this instance you will be working with to track the sound’s current state and do playback. Please note that if you decide to play the sound again immediately after playing it for the first time, you are risk to mess the quality of sound, and mess with the tracking state of the sound itself. So from the game programming point of view, that bit of working must be ensured and done by programmers, they should know what they are doing. If doing just like that, keep in mind that the sound should be played as 1 round, so if we just play it immediately for the second time, the first play is not concerned anymore as we know it will be stopped in a predictable time.
Now let’s see how we can put this SoundEntity into real use.
We need to create an instance of the SoundEntity first by the following code.
SoundEntity explosive = new SoundEntity("my/sound/folder/explosive");
Above line of code will create an instance of SoundEntity with explosive .wav file indicated to use.
In addition, you can even preset its properties, see the example below.
explosive.IsLoop = true; explosive.Volume = 0.80f; explosive.Pan = -0.50f;
The above setting up tells us that explosive sound will played as loop, with 80% of volume, and play bias to the left of the speaker. Yep, we could do something special with this SoundEffect class wrapped by SoundEntity.
When it’s time to play, then just invoke.
explosive.Play();
Or you could see the definition from the class and call PlayLoop() to play it as loop, this could pose some real useful method here, as some sounds may be regarded as a single play, but in some case that we just want to play it as loop so we could use PlayLoop() method.
When you don’t want to use that sound anymore, then just call.
explosive.Dispose();
This will release the resource used by that sound.
Okay, just back to our initial purpose, the mainmenu case.
See the code below for doing that kind of thing.
....
// play sound effect on button
if (Engine.MouseManager.IsOnSight((Entity2D)play))
{
if (Engine.MouseManager.LeftClicked)
{
if (SM.MouseClicked.IsStopped)
{
SM.MouseClicked.Play();
}
}
else
{
if (SM.MouseOver.IsStopped && SM.MouseOver.IsMarkedAsStopped)
{
SM.MouseOver.Play();
SM.MouseOver.MarkAsPlayed();
}
}
}
else if (Engine.MouseManager.IsOnSight((Entity2D)quit))
{
if (Engine.MouseManager.LeftClicked)
{
if (SM.MouseClicked.IsStopped)
{
SM.MouseClicked.Play();
}
}
else
{
if (SM.MouseOver.IsStopped && SM.MouseOver.IsMarkedAsStopped)
{
SM.MouseOver.Play();
SM.MouseOver.MarkAsPlayed();
}
}
}
else
{
//clear state of both sound effects
SM.MouseOver.Stop();
SM.MouseOver.MarkAsStopped();
SM.MouseClicked.Stop();
}
...The above code I extracted it from my demo game “Junk Master: The Journey To Junk Lord”. But it could be easily understand with the sense of pseudocode.
From the code, MarkAsPlayed(), and MarkAsStopped() method will come in handy as they’re used in the application layer. They are used to check and control playing the sound only once in certain circumstance. We only need those methods in the case when the state-changing is immediate, and not be well separated, I am sure by just using it for a few time, you will realize when or not when to use it.
And that’s it for the sound stuff for this post.
The sound’s playback won’t be mess anymore, thanks to SoundEntity. (be sure to change the namespace to match with your project first, and cut out some reference at the top of the code also :)
Download the SoundEntity class.
Til next time.
Haxpor

Like










