Download CD Content
In the last chapter you saw how to create and edit sounds using Audacity. We're now going to move to the next level, using those sounds for effects initiated by the player, by weapons and ammo, by vehicles, and by places and things in the world at large.
Also, we'll touch on the issue of in-game music and how you can use it. I'm not going to even attempt to teach you how to compose music—that's far out of scope for this book. However, I will include some musical pieces that you are free to experiment with as we look at the issue.
In a first-person shooter-styled game, player sounds add to the sense of "being there," sometimes in a big way. There are two kinds of player sounds: world sounds and client-only sounds.
World sounds are effects that are generated on the server that represent sound effects emitted by your player-avatar in the game world. In this sense, they are much the analogue of the way you emit sounds in real life: walking, talking, firing weapons, banging on doors, and so on. The server places a sound effect "in the world" at your location and then updates all of the affected clients so that they will emit the sound (if the client's player-avatars are close enough to hear the sound) as it was made, with appropriate modifications, such as attenuation because of distance or Doppler effect due to the sound source moving toward or away from the listener.
These sorts of sounds are usually called 3D sounds. The actual sound effects have no inherent 3D characteristics, but the game client handles them in a manner that imparts 3D positional information to each client.
Client-only sounds are those sounds that a player's avatar makes that really only matter to the player. These can be personal noises, like the sounds of heavy breathing to portray exertion, the sound of being hit by a bullet, or the rustle of clothing. Nothing is cut-and-dried though—you might want to use the sounds in some of those examples as world sounds in order to perhaps betray the location of a player who is sneaking around in the dark. It all depends on your game play design.
Some sounds can be attached to the player, like the sounds of footsteps, triggered by a frame in the animation. Unfortunately for us in this book, the MilkShape DTS Exporter does not yet support this capability. I happen to know that this problem will be overcome in the near future, but alas, the capability to trigger footsteps using frames in model animations is not yet available. Triggered footsteps are available if you use the more expensive modeling tools that cost in the range from several hundred to several thousand dollars. The DTS exporters for those tools do support animation triggers. But like I said, the capability will be available soon for the MilkShape DTS Exporter.
However, that being said, there is a way for us to handle footsteps using program code, and that's what we'll be doing a little bit later in the chapter.
Other sounds can be emitted in an ad hoc fashion, wherever and whenever we want. Some examples of these sorts of sounds are utterances or taunts. You press a key and your player's avatar utters a taunt of some sort, such as "Loser!" or "Ha ha, ya missed me!" You can also use ad hoc sounds to issue prerecorded audible commands. The limits are your imagination. Heck, you could have your player carry around a boom box and play annoying music!
When you use world sounds, you also have the choice of using them in a 3D mode, where the sound is played on all the clients as if they were emanating from a specific location in the game world, or you can use them in 2D mode—the server still directs the clients to play the sounds, but they have no positional quality to them.
In our first example, we are going to use the serverPlay3D function to make out footsteps for our player. First you should record some footsteps. Just a single one will be enough, if you like, but if you record a half dozen or so that are all similar though slightly different, you can offer a more natural sound effect by randomly choosing which sound to play for a given footfall. Make sure to record the sound at 22050 Hertz (Hz) or maybe even 11025 Hz to keep the file size fairly small. Save the sound as C:\koob\control\data\sound\footstep1.wav.
After you have the sound effect made, you need to add the following code in C:\koob\ control\server\players\player.cs at the end of the file (after the function HumanMaleAvatar::onDisabled).
datablock AudioProfile(FootStep1) { fileName = "~/data/sound/FootStep1.wav"; description = AudioClosest3d; preload = true; }; function serverCmdStartFootsteps(%client) { %client.player.schedule(200,playFootstep); %client.player.footstepson = true; } function serverCmdStopFootsteps(%client) { %client.player.footstepson = false; } function Player::playFootstep( %this ) { if(%this.footstepson) { serverPlay3D(FootStep1,%this.getTransform()); %this.schedule(500,playFootstep); } }
First, there is an AudioProfile datablock. This datablock tells the engine where the sound effect is and which AudioDescription to use. The particular AudioDescription in question already resides in C:\koob\control\server\misc\sndprofiles.cs and looks like the following (do not type this in):
datablock AudioDescription(AudioClosest3d) { volume = 1.0; isLooping= false; is3D = true; ReferenceDistance= 5.0; MaxDistance= 30.0; type = $SimAudioType; };
The next thing in the new code we added was a message handler for receiving a message from a client. We defined the message in this case to be StartFootsteps, and our only parameter is the handle to the client that sends the message. That handler makes a call to a method of the Player object called schedule. This schedules a function execution event for processing sometime later. We also set the flag %client.player.footstepson to true for future reference. The event delay is set to 500 milliseconds, or half a second, in the future. You can change this value to something else or even have it vary according to running speed. At the appointed time, that function, playFootstep, is called, and provided that the %this.footstepson property is set to true, it executes the serverPlay3D function. Note that in this case, %this is set to be the handle of the object the method is being called for. That object is the Player object, which happens to be exactly the same object as %client.player, and it's a good thing that it is too!
The way serverPlay3D works is that it accepts an AudioProfile and 3D coordinates in the world space. Conveniently we can get those coordinates from a simple call to getTransform. The serverPlay3D function then internally tells all clients to play that sound effect at those world coordinates.
And hey, presto! You have a footstep.
Before exiting playFootstep, the schedule method is called again to schedule another footstep in half a second. It will keep doing this until told to stop. You tell it to stop by using the stopFootsteps message, whose handler merely sets %client.player.footstepson to false, so that the next time playFootstep is called, the flag is found to be false, the sound is played, and the event isn't rescheduled.
So that's the guts of getting the sounds to play, but we still need to deal with when to play them. We want the steps to happen when the player is running and to stop when he stops.
We can easily do this as part of the work-around by trapping the keyboard inputs that tell the player to move and stop. The function that does this is a client-side function located in C:\koob\control\client\misc\presetkeys.cs.
Open that file and locate the function GoAhead, which looks like this:
function GoAhead(%val) //---------------------------------------------------------------------------- // running forward //---------------------------------------------------------------------------- { $mvForwardAction = %val; }
Change it to this:
function GoAhead(%val) //---------------------------------------------------------------------------- // running forward //---------------------------------------------------------------------------- { $mvForwardAction = %val; if (%val) commandToServer('startFootsteps'); else commandToServer('stopFootsteps'); }
In GoAhead, the parameter %val is nonzero when the key that has been mapped to this function is being pressed, and it is zero when the key is released. Therefore, the simple if-else code block will send the startFootsteps message to the server when the GoAhead key is pressed and the stopFootsteps message when it is released. The GoAhead key is defined later in the same file to be the Up Arrow key.
Now if you have been browsing around sndProfiles.cs looking at the datablocks in there, you might have come across another work-around you're tempted to try—the AudioClosestLooping3d datablock. You might look at that and say to yourself, "Self, that has looping built in. No need to fool with scheduling events on a repeating basis." And you would be right in making that deduction. However, there is a problem with that approach: Once you trigger that sound effect at a particular location, it will continue looping, all right—but at the same location. The footsteps won't follow your player.
Like I said earlier, the absolute best way to do these kinds of repetitive player sounds is to attach them to the player's movement animations via triggers in the model. For MilkShape users, that capability will be available soon.
Let's make our avatar guy say something, something that other players can hear, by pressing a key. The process is going to be quite similar to the footsteps.
First, make another recording, at the sample rate of your choosing. Holler something into the mike, like, "Your mother wears army boots!" or something equally endearing. Save it as C:\koob\control\data\sound\insult1.wav or something like that.
Then add the following code in C:\koob\control\server\players\player.cs at the end of the file.
datablock AudioProfile(Insult1) { fileName = "~/data/sound/insult1.wav"; description = AudioClose3d; preload = true; }; function serverCmdHurlInsult(%client) { serverPlay3D(Insult1,%this.getTransform()); }
There is noticeably less stuff than for the footsteps, because we don't need to make the sound effect. We just hurl our insult and maybe run for cover after that! Now, notice that the profile uses a different AudioDescription. This time it's AudioClose3d.(Don't type this in—it's already in sndProfiles.cs.)
datablock AudioDescription(AudioClose3d) { volume = 1.0; isLooping= false; is3D = true; ReferenceDistance= 10.0; MaxDistance= 60.0; type = $SimAudioType; };
The reason for using this datablock is because it defines a sound effect that can be heard from farther away. The ReferenceDistance is 10 world units. This means that the sound effect attenuates (the volume decreases) over a longer distance, so it can be heard from farther away than the footsteps.
Next, we need to send the message from the client to the server so that the server can then notify all the other clients. We'll do that again with a client-side function that we'll call Yell.
Open C:\koob\control\client\misc\presetkeys.cs and add the following to the end:
function Yell(%val) { if (%val) commandToServer('HurlInsult'); } PlayerKeymap.bind(keyboard, "y", Yell);
The function sends the HurlInsult message to the server, but only when the key is pressed (%val is nonzero), not when it's released. Then we need to bind a key to press to trigger the whole thing. We use PlayerKeymap.bind to do that, pointing it to the Yell function.
There you go—you're in business.
One more variation you should try is recording several different insults and saving them as insult1.wav, insult2.wav, and so on. Let's go ahead and record six different insults.
Now make six different AudioProfiles that have incremental names starting with Insult1 and ending with Insult6. Each should uniquely point at one of the six recordings you made. Then in the message handler use a bit of random number code to pick a number between 1 and 6.
%n=getRandom(5) + 1;
This would pick an integer between 0 and 5. Now increment it by 1 so that the result would be between 1 and 6.
Then rewrite serverPlay3D to look like this:
serverPlay3D("Insult" @ %n, %this.getTransform());
This will modify the name of the AudioProfile by putting the random number at the end. Then every time you hurl the insult, a different epithet will be directed with withering precision on your foe. Fun for the whole family!