Location Settings
written by demagogue
This describes a method of doing ambient sounds in which a trigger turns on an omni ambient when you enter a zone, then turns it off when you leave, possibly starting a new ambient for the new zone. It uses a script, a custom soundshader, and some in-map entities.
It's useful for situations when you want one ambient sound to cover a very large or complex area that might be unweildy with multiple overlapping speakers, or any other situation where using speakers causes an issue. On the other hand, in a lot of situations it's much easier to just use speakers, so it's not for every use. (For the method using speakers, see this tutorial: Adding_ambient_Sounds_to_your_Map#Localized_Ambient_Sounds_in_one_Area_.28with_Speakers.29).
- Pros: A robust way to keep sound from leaking into areas you don't want it, or losing sound where you do. For a very big area, or inside buildings or winding paths, a few triggers will fully cover the whole area with one seamless ambient, whereas it might take many speakers that have to overlap and aren't as seamless. Also, you can change the ambient for a whole area by changing the property of one button (as opposed to many speakers).
- Cons: More involved setup than a speaker. An ambient fading in at the start is possible but easier with speakers. You need to make sure the player can't enter/leave the area without going through a trigger to turn on/off the ambient (a speaker doesn't care). And at the beginning you should test it some to make sure everything is working (speakers are more drop and go).
Mechnically, it works like a radio, where touching the trigger changes the station. For that reason, the method is also a good template for any type of machine with various "states" that each have their own sound, e.g., pressing different buttons turns on their own unique sound, stopping the previous ones, including a button to turn all sounds off. But I'll only deal with using it for zoning ambient sounds here.
Set up
The Blueroom
- Say there are 3 ambients you want to use in your map with the zone approach (it's easy to change the number once you get the principles, and you can even set up for more than you might actually need. You don't have to use every channel, but it's set up in case you want to add some more ambients later).
- In your map, make a blueroom (i.e., a room off to the side the player will never enter) and create a set of 4 "command" buttons in it, the "radio stations", one for each sound you want to use, plus an extra one reserved for silence and turning all ambient sounds off. Create a button by right-click in the map window, create model, darkmod->mechanical->numberwheel_button.lwo->ok, then change the classname to "atdm:mover_button", then copy it as many times as you need.
- Also set up an ambient speaker in the blueroom (right-click, create-entity, Darkmod->Sound->speaker_ambient_music).
- (Two asides: 1. A blueroom is normally textured with common/caulk. You might temporarily put the buttons on a brush, light the blueroom, and have some other textures, all of which you can get rid of later, so you can put the StartingPoint inside and easily see and test the buttons later, which I recommend you do if you have any trouble. 2. The buttons would be your "machine" if you just wanted to use this method for machine sound-management.)
- The Ambient Speaker
- Name the ambient speaker you created "ambient_player" (If you want a different name, you'll need to change the name in the script below to correspond.)
- The ambient speaker, at least for the purposes of this tutorial, works differently from a normal speaker. Instead of each speaker having its own individual soundshader to play using the s_shader property, our one ambient speaker is going to contain multiple properties covering all the sounds we want to play with the zone method. Again, it's like one radio with multiple stations.
- Now you're going to add consecutive properties in the following form for as many ambients as you want to use with this approach, with values that I will explain below.
- snd_station1
- snd_station2
- snd_station3
- etc.
- Now you're going to add consecutive properties in the following form for as many ambients as you want to use with this approach, with values that I will explain below.
- For each snd_station* property, you need to enter the value as the soundshader name of the sound you want to play. Since you cannot add separate play instructions like a normal speaker (like looping, global, omni, etc), this information needs to be in the soundshader itself. I have created a soundshader with instructions typical of ambient sounds (that covers all of the Darkmod ambients_ambience sounds) which you can create and drop into the Darkmod/sound folder (custom_ambient_trig.sndshd, I'll make a new wiki page for it for now Custom ambient trig so it doesn't get lost). For its naming convention, it basically uses the normal ambient name (listed in sound/tdm_ambient_ambience.sndshd), and adds the suffix "_trig" so there isn't a conflict.
- You can listen to the Darkmod ambients in the folder "Darkmod\sound\ambient\ambience" to choose the one you like (if your music player can play .ogg's), then the snd_station* value is going to (should) be the filename plus the "_trig" suffix after it. But the name is literally defined in the custom_ambient_trig.sndshd file I linked to above, so use that name (e.g., in case there's a typo).
- So some typical properties/values in your speaker_ambient_music entity might be:
snd_station1 alien01_loop_trig
snd_station2 haunted_revenants01a_trig
snd_station3 mansion_piano02a_trig
etc...
(Strange, some of those underscores seem to be disappearing in the code tag.)
- Do this for each snd_station* that you want, and that's all you have to do with the ambient speaker itself. You're done with it. Everything else will be handled by the soundshader and the script. (I hope to add a feature in the future to handle things like looping and volume in the ambient_player, however.) By the way, notice that you do NOT need to create a snd_station0 for the silence channel. That's handled all by the script itself.
- You will probably typically want to use a shader name from custom_ambient_trig.sndshd for the value of your snd_stationX property. But technically you can use other names, e.g., from any soundshader in the sound folder, or even make your own. Just be very sure they will work like you want. I'll discuss more about a Sound Shader at the end of this tutorial to help you if you want to do something like that.
- Command Buttons
- Name each command button (except for one exception below) whatever you want (e,g., the name of the zone it's covering, like: factory_zone, streets_zone, forest, whatever), just remember what it is so you can "target" it with a trigger entity later.
- The one exception is that the first button, which is "station 0", should be reserved for turning all sounds off, that is, radio silence. This is important because it is the initial state of the setup when the game starts, even if you set up a trigger to turn on a station the moment after it starts. (So even though you don't have a "station0" listed in the ambient_player, you still have the button for it to activate silence in the script). Name the button something that lets you know it is for having no ambient sound, such as "silence" or "nosound".
- Add a "state_change_callback" property to each command button, with the value being "station_0", "station_1", "station_2", etc., sequentially for each button. This name needs to be *exactly* like that (including the underscore; and don't forget that the first button is station_0 and is reserved for "no sound").
The Gameworld
- You trigger ambients in the gameworld with a trigger_multiple. To make one, first create a brush the size of the entrance to the zone you want to do (e.g., door-sized, or cave-entrance sized, or street-wide-to-the-sky-sized). It should be quite thick so that they player cannot run through it without it registring a hit.
- Footnote: Roughly speaking, a trigger checks in pulses, and if the player is inside during a pulse there's a hit (at least, I think that's how it works). To get an idea of how quickly the checks pulse, when you have everything set up you can stand inside of one and watch how fast a message like "Station 2 is already on." repeats itself. Try to make it thick enough that a player could not get lucky and run through without that pulse registering him inside it. I'm still experimenting myself with the ideal thickness, 8 occassionally misses if I try, so maybe something like 16 or 24 or 32 just to be safe? (I will update this with a recommended number as I test more.) Just be sure to test your setup a lot, running in and out, to make sure the trigger never fails to start the ambient.
- Note you can also make multiple trigger_multiple's to cover the entrance how you want, e.g., if it's L-shaped or something. It doesn't have to be just one wall. Whatever works. It also doesn't have to be a perfect fit (like a visPortal), just that the player cannot possibly enter the zone without touching a trigger, but it doesn't hurt making it a perfect fit either.
- Texture it with common/trigmulti, then with the brush highlighted in Dark Radiant, right click on it, create-entity, Darkmod/Triggers/Trigger_multiple, ok. If you have others the same size (e.g., door sized) you can copy-paste it for them.
- In the map, set up a trigger_multiple entity at every entrance to an area you want covered by a single omni ambient sound, completely covering the entrance so the player has to pass through it to get in the area, each targeting the command button of the station you want to play in that zone (e.g., property/value "target"/factory_zone, or whatever you named the command button you want to use). (Do not add a "wait" property). When you exit the area, have another trigger_multiple at the entrance of the new area target the command button for a new station.
- Here's an image of that for reference: [1] (One note: After some testing, I think that this trigger is not thick enough, as I described above, but maybe another 8 units thicker or so would do.)
- Be sure you cover every possible entry even those you aren't sure the player can cross, and match every entry-trigger with a mirroring exit-trigger for the adjacent area, and watch for possible busts like teleports (where you can just make sure the teleport process includes activating the command button for the ambient it's teleporting into).
- If you want no sound for an area (i.e., just turn the previous ambient off), have a trigger_multiple turn on the first command button, "station_0", which I recommended you name something like "silence".
- There's one special case. If you want the player to start in an area with an ambient playing, place a trigger right over the area where the player starts, targeting the command button for the sound you want to use (except for the first button, "silence", which would be redundant since the game starts already on "station_0" no sound).
The Script
Once you have all that set up, you need to create a script in something like notepad or wordpad. Remember to name it the name of your map "mapname.script" (and that you don't accidentally name it mapname.script.txt), and put it into the same folder as your map.
The following only creates three stations, plus the nosound "off" station, but it should be enough so that you know how to create more by changing the code accordingly. You can just copy it right into your script and it should work.
Currently there are two versions, this (simpler) one that has abrupt transitions between ambients, and a more complex one that fades the previous ambient out and the new ambient in at transitions, which is posted under the section "Adding Ambient Fades" later in this tutorial if you want to use that one instead. Since there are some times you may want ambients to fade in/out and some times you may want an abrupt start and end, I'll leave both versions for now, so you can choose which one is right for you (or mix and match if you like, but I need to think about that).
// ZONED AMBIENT SYSTEM (demagogue)
//
// Description: This script allows a method in which a trigger turns on an omni ambient when you enter a zone, then turns it off when you leave, possibly starting a new ambient for the new zone.
// The set up is described at http://modetwo.net/darkmod/wiki/index.php?title=Ambient_Sounds%2C_a_zone_approach.
float station_state = 0;
// the number corresponds to the "current radio station playing"
void main()
{
}
void station_0(entity button, boolean bOpen, boolean bLocked, boolean bInterrupted) //Reserved for turning ambients off
{
if(bOpen)
{
button.Close();
if (station_state != 0) // If any station other than "off" (0) is playing
{
station_state = 0;
$ambient_player.stopSound( SND_CHANNEL_BODY, false ); // stops any currently playing sound
sys.print("The radio is now turned off.");
}
else
{
sys.print("The radio is already off.");
}
}
}
void station_1(entity button, boolean bOpen, boolean bLocked, boolean bInterrupted)
{
if(bOpen)
{
button.Close();
if (station_state != 1) // If any station other than #1 is playing
{
station_state = 1;
$ambient_player.startSound( "snd_station1", SND_CHANNEL_BODY, false ); // starts station 1 looping
sys.print("Radio station 1 is now playing.");
}
else
{
sys.print("Radio station 1 is already playing.");
}
}
}
void station_2(entity button, boolean bOpen, boolean bLocked, boolean bInterrupted)
{
if(bOpen)
{
button.Close();
if (station_state != 2)
{
station_state = 2;
$ambient_player.startSound( "snd_station2", SND_CHANNEL_BODY, false ); // starts station 2 looping
sys.print("Radio station 2 is now playing.");
}
else
{
sys.print("Radio station 2 is already playing.");
}
}
}
void station_3(entity button, boolean bOpen, boolean bLocked, boolean bInterrupted)
{
if(bOpen)
{
button.Close();
if (station_state != 3)
{
station_state = 3;
$ambient_player.startSound( "snd_station3", SND_CHANNEL_BODY, false ); // starts station 3 looping
sys.print("Radio station 3 is now playing.");
}
else
{
sys.print("Radio station 3 is already playing.");
}
}
}
In a future edition I hope to explain all the parts of this script in more detail here.
For now I should make the probably obvious point that if you want to add more stations, you can use the three stations I set up here as a template, to add more "void" entries in the script, e.g., changing things like "station_3", "station_state != 3", "station_state = 3" "snd_station3", etc, and replacing all the 3's with, say 4's if it's for the 4th ambient you want covered. You can even just copy and paste an entire "void" entry and just change the numbers.
A note on the "sys.print" comments. When you have the script up and running, you can go into the console in-game and follow their operation through these comments when you touch a trigger. You won't see the message only if the script isn't running (then you know it's probably a problem either with the trigger misspelling its command button's name in its "target" property, or the command button's state_change_callback property misspelling its value and not starting the script at all.) I find them very useful, especially at the beginning, so I know exactly when an ambient is starting. They can also be useful if they tell you "Station X is now playing", but you don't hear it. Then the problem is probably that you mispelled the soundshader so it doesn't have a sound to play, or you used a soundshader that doesn't have a property like "global", so it's playing but you can't hear it.
Once you have everything working how you like, however, then these messages may get in your way, especially when they flood the console with "Station X is already playing" when you are standing in a trigger. You can add "//" in front of the sys.print line (as you can see in other examples) so the script ignores them. Note that for the sys.print function in the "else" section, you will have to get the script to ignore the entire "else" clause, not just the sys.print line. To do that you can add "/*" in front of the word "else", and "*/" after the "}" following the sys.print line. Alternatively you could just delete the entire "else" clause, as well as the standalone sys.print lines, but the nice thing about the "//" is that you can restore the lines if a problem comes up later you want to debug.
This is how the script would look with those lines commented out:
void station_3(entity button, boolean bOpen, boolean bLocked, boolean bInterrupted)
{
if(bOpen)
{
button.Close();
if (station_state != 3)
{
station_state = 3;
$ambient_player.startSound( "snd_station3", SND_CHANNEL_BODY, false ); // starts station 3 looping
// sys.print("Radio station 3 is now playing.");
}
/* else
{
sys.print("Radio station 3 is already playing.");
} */
}
}
On reflection, I am debating getting rid of the "else" clause altogether and just leaving the "Radio Station X is now playing" comments, since they are the most useful for debugging and tracking the script. But I'll leave it there for now since it sometimes comes in handy, and an author can decide to comment it out later.
Tips and Issues
General Tips
- It probably goes without saying, but be sure to test your set up by running back and forth across two zones over and over, to make sure the ambients turn on and off when they're supposed to and it doesn't break or the player gets through without it triggering.
- If there is a problem, you can put the StartingPoint in the blueroom to push command buttons and make sure the sounds turn on, and replace each other as they should, and that the first button stops all sound.
- Some typical problems might be, if the buttons work but the triggers don't, make sure the trigger spells the name of the button correctly in its "target" property. Also make sure you spell the buttons' "state_change_callback" value correctly.
- The lines sys.print("Radio station X is now/already playing."); are there so you can go into the console and see exactly when and how the triggers are working. If you don't see that line in the console when you touch a trigger, it means the script isn't even being called on, so check things like the trigger's target spells the command button's name correctly, and the command button spells the state_change_callback value properly. Once you have it set up and working, you can add "//" in front of the sys.print lines to turn them off.
Soundshaders and Modifying or Custom Sounds
- As I mentioned above, I've made an triggered-ambient soundshader for general use with this method you can download from the link above. But you may want to change some of its instructions or add custom ambients, so I'll describe a little about soundshaders here. You can browse some soundshaders in the Darkmod/sound folder to get an idea of how they look.
- A typical entry looks like this:
alien01_loop_trig
{
description "Made by Muze"
global
omnidirectional
looping
sound/ambient/ambience/alien01_loop.ogg
}
- For reference, the functions in the soundshader are:
- The first line is the name of the shader that you will use as your snd_station* values, as you can tell by looking at the example property/value I gave above.
- "global" means it will be heard everywhere in the map.
- "omnidirectional" means it will be heard from all directions (i.e., not from the direction of the speaker itself).
- "looping" means when the ambient is finished, it will start over (not always appropriate for every ambient).
- And the final line is the address of the actual sound file played. As you can see, you can listen to all the Darkmod ambients in the folder "sound/ambient/ambience" to help you choose one (if you have a soundplayer that can play .ogg's).
- If you want to modify some of these properties for your own purposes (e.g., deleting the looping, or adjusting the volume), or add a custom ambient sound, you can easily make a new soundshader by making a new text file with the file extension .sndshd and add an entry using the above example as a template, with your fan mission's name (or a simplified version) in the title, such as patent_ambient.sndshd (what I use; make sure you don't accidentally save it something like YourMapName_ambient.sndshd.txt). Be sure that if you do create your own ambient entry in a soundshader that you use a different name than one already used, e.g., if I modified the alien01_loop_trig entry and put the new entry in my own custom soundshader, I would name it alien01_loop_patent so it doesn't overlap.
- DON'T just modify the soundshader I made, or any tdm_* soundshader, and re-save it because you need to be in the habit of having all your custom sound-set ups in your own soundshader unique to your own fan mission. There are lots of tdm_* soundshaders that you don't want to alter, but you might use their sounds in your own way by making new entries in your own soundshader with the same sounds.
- A typical modification would be the volume. To change the volume, add a new line "volume" with a number (positive or negative), such as "volume 10" or "volume -10". You can test the different volumes by changing your custom soundshader, then starting in the blueroom pushing command buttons to listen to it in-game.
- If you wanted to use a custom ambient, and note it has to be in either .ogg or .wav format (see the wiki entry on sound formats for more info), just make a custom soundshader with an entry for it using the template above, with its own unique name, and the address pointing to where your custom soundfile will be when packaged for release (I need to learn about this myself, and I'll add that information sometime; but the information on that is hopefully somewhere on the wiki or forums.)
- Once you modify or have a custom sound in your own soundshader, dropped into the Darkmod/sound folder, make sure you use its name for the value in the ambient_player entity to use it (or any other speaker with the property s_shader, for that matter).
(Note to self: I should also explain how to package the soundshader and custom sounds with the PK4 file when you get your fan mission ready for release).
Ambient Fades and Blends
Adding Ambient Fades
- To add ambient fades and blends, you can use a script that uses the function fadeSound to do the fading for you (but doesn't yet blend the fade-in and fade-out), or you can back the triggers up from an entrance and add a normal speaker to handle the fade (which has quirks like possible ambient overlap, but you can also blend.)
// ZONED AMBIENT SYSTEM (demagogue)
//
// Description: This script allows a method in which a trigger turns on an omni ambient when you enter a zone, then turns it off when you leave, possibly starting a new ambient for the new zone.
// The set up is described at http://modetwo.net/darkmod/wiki/index.php?title=Ambient_Sounds%2C_a_zone_approach.
float station_state = 0;
// the number corresponds to the "current radio station playing"
void main()
{
}
void station_0(entity button, boolean bOpen, boolean bLocked, boolean bInterrupted) //Reserved for turning ambients off
{
if(bOpen)
{
button.Close();
if (station_state != 0) // If any station other than "off" (0) is playing
{
station_state = 0;
$ambient_player.fadeSound( SND_CHANNEL_BODY, -60, 2 ); // fades the current sound playing to -60 db ("off") in 2 seconds
// $ambient_player.stopSound( SND_CHANNEL_BODY, false ); // abruptly stops any currently playing sound, if desired
sys.print("The radio is now turned off.");
}
else
{
sys.print("The radio is already off.");
}
}
}
void station_1(entity button, boolean bOpen, boolean bLocked, boolean bInterrupted)
{
if(bOpen)
{
button.Close();
if (station_state != 1) // If any station other than #1 (this one) is playing
{
if (station_state != 0) // If it's another sound playing (i.e., not "off", where the fade-in is already primed)
{
$ambient_player.fadeSound( SND_CHANNEL_BODY, -60, 2 ); // fades the previous sound out, -60 db in 2 seconds
sys.wait( 2.1 ); // gives time for the fade out
}
$ambient_player.startSound( "snd_station1", SND_CHANNEL_BODY, false ); // starts station 1 looping
$ambient_player.fadeSound( SND_CHANNEL_BODY, 60, 2 ); // fade the new sound in
sys.print("Radio station 1 is now playing.");
station_state = 1;
}
else
{
sys.print("Radio station 1 is already playing.");
}
}
}
void station_2(entity button, boolean bOpen, boolean bLocked, boolean bInterrupted)
{
if(bOpen)
{
button.Close();
if (station_state != 2)
{
if (station_state != 0)
{
$ambient_player.fadeSound( SND_CHANNEL_BODY, -60, 2 ); // fades the previous sound out
sys.wait( 2.1 ); // gives time for the fade out
}
$ambient_player.startSound( "snd_station2", SND_CHANNEL_BODY, false ); // starts station 2 looping
$ambient_player.fadeSound( SND_CHANNEL_BODY, 60, 2 ); // fade the new sound in
sys.print("Radio station 2 is now playing.");
station_state = 2;
}
else
{
sys.print("Radio station 2 is already playing.");
}
}
}
- Update. A little more experimentation later, and here is a more complex version of the script that uses fadeSound when you cross a boundary to fade the old ambient out and the new ambient in entirely by script (so just set up your trigger walls as normal, no box thing, and the fading is taken care for you).
- You can adjust the rate at which the fade in/out occurs with the third term of the fadeSound functions, which is the time in seconds it takes for the fade to occur.
- There is one problem with this code in that the first time an ambient triggers, it's not primed by a previous fade out to fade in. It seems like that would be an easy problem to solve, but I haven't cracked it just yet. But here is the code if people want to start using it right away (and maybe update it with the fix for the first ambient later).
Fading and Blending with Speakers
- Using speakers in combination with the triggers was the old method I thought of for doing fades and blends. It may be obsoleted now, but there might be a situation you want to use it. So below is more detail on that for the record.
- If you want the ambient to fade in, as with a speaker, but still use this method, instead of shaping the trigger_multiples like 2 walls on each side of the entrance, you can shape them like a box (three walls around the entrance, with an inside layer, for nosound, and an outside layer, for your ambient, so 6 brushes altogether), or even easier, and probably just as effective in many cases, just back the trigger-wall up a ways from the entrance, and in either setup place a normal speaker inside the middle area ("waitfortrigger"/0, "global"/0, "looping"/1, "omni"/1, "s_mindistance" & "s_maxdistance" set appropriately, remember there is a button in Dark Radiant to see the speaker radius).
- Here's an image of that for reference: [2]
- It just uses 2 walls (four brushes), with the third being a brick wall.
- The speaker will fade the ambient in, the trigger will turn on the ambient, and as the speaker fades out the ambient continues. One issue, the ambient sound will duplicate in the overlap between speaker and trigger-on. Depending on the ambient that may sound strange. You may adjust the speaker radius and trigger locations to minimize that, though (better than I did for my screenshot above). But for other ambients you won't notice and it works great.
- In testing this, I found it good to have the ambient trigger be the outside layer and a "nosound" trigger (i.e., station0) the inside, the speaker radius does the fade in the nosound area in the middle. Then if you wanted the area on the other side of that door to have its own ambient, you'd create another 3 walls on the other side of that doorway with the ambient trigger an outside layer and a "nosound" trigger an inside layer, and its own speaker fading in its middle (basically just mirroring the screenshot I took for the other side of the doorway). Then the two speakers could even overlap in the middle so you get a nice blending gradient between the two ambients.
- (Also when testing this, I had some problems creating three walls as one trigger_multiple, which is possible but it didn't trigger, at least not in my testing, and had better luck creating three separate trigger_multiples, each as a single wall-brush. I don't know if that's a real problem or just a quirk I ran into and actually you can use multiple brushes as one trigger_multiple. It's just something to keep in mind).