As I'm sure many of you know, horror games benefit a lot from using scares that is not necessarily caused by or meant for the player. A book randomly falling from a shelf somewhere in the map that the player has no control over is an example of this. The falling book might not be in the same room as the player, it might not be noticed and chances are that it did nothing at all to affect the player. However, when it does work, it works incredibly well.
This technique puts another dimension to the game and makes the player really feel like they're inside it. It's no longer "Damn, that was scary!", but more like "Why did that happen...?". Questioning events that happens around you and feeling like you're not the one triggering them removes the distance between the player sitting in the chair by his computer and the scares in the game.
I'm sure I could have explained this better. However, now we get to the good part.
I have created a script that I call "Custom Ambient Map Scares". This is basically something that I can copy into my maps, add some filenames to, and by doing that I can effectivaly add an automatic scare triggerer.
What it does is, it triggers random events, or functions, at random times during the time the player spends in the map. The time between the event is easily manageable, and so is the actual function calls. I use simple and easy techniques to simulate percantage-chances and different levels of "sizes" of the functions, as well as how much it affects the player.
This took a long time to create, and a lot of thinking about how I wanted it to be had to be done. Now I have a first and simple product, and I thought, why not save my fellow programmers on the Frictional Forum from the hours of thinking and scripting to create the same effect?
Below I will paste a copy of my script, and you're all free to use it in your mods. However, I would like you to put my name in the credits. I cannot force everyone to do it, but I am asking you to show me the same respect as I'm showing you right now.
If you like this this, and think it's a good idea, please let me know. If the feedback is good, I will happily continue to develop this script into something perfect that I'll share with you all, and might even submit to the wiki. Tips and help is much appreciated too!
///////////////////////////////////////////////////////// ///// - CUSTOM AMBIENT MAP SCARES - ///// ///// - CREATED BY WAPEZ - ///// ////////// - STARTS HERE - //////////
void RandomWindSound(string &in asTimer) { //-----------------------------------||-----------------------------------// // Plays a random WIND sound on one of the specified areas. // Timer is originally set between float values 5.0 and 20.0 seconds. // Timer: AmbientWindSoundsTimer //-----------------------------------||-----------------------------------//
if(GetLocalVarString("CAMS_State") == "CustomAmbientMapScares_1"){ //FINDS OUT WHICH PART OF THE MAP THE PLAYER IS LOCATED IN SetLocalVarInt("WS_AreaNumber", RandInt( 1, 7 )); //DEPENDING ON WHERE THE PLAYER IS, PLAY SOUNDS ON DIFFERENT AREAS AddDebugMessage("Playing sounds in CAMS_1", false); //ADD DEBUG MESSAGE FOR TESTING PURPOSES } if(GetLocalVarString("CAMS_State") == "CustomAmbientMapScares_2"){ //FINDS OUT WHICH PART OF THE MAP THE PLAYER IS LOCATED IN SetLocalVarInt("WS_AreaNumber", RandInt( 8, 13 )); //DEPENDING ON WHERE THE PLAYER IS, PLAY SOUNDS ON DIFFERENT AREAS AddDebugMessage("Playing sounds in CAMS_2", false); //ADD DEBUG MESSAGE FOR TESTING PURPOSES } if(GetLocalVarString("CAMS_State") == "CustomAmbientMapScares_3"){ //FINDS OUT WHICH PART OF THE MAP THE PLAYER IS LOCATED IN SetLocalVarInt("WS_AreaNumber", RandInt( 14, 19 )); //DEPENDING ON WHERE THE PLAYER IS, PLAY SOUNDS ON DIFFERENT AREAS AddDebugMessage("Playing sounds in CAMS_3", false); //ADD DEBUG MESSAGE FOR TESTING PURPOSES } if(GetLocalVarString("CAMS_State") == "CustomAmbientMapScares_4"){ //FINDS OUT WHICH PART OF THE MAP THE PLAYER IS LOCATED IN SetLocalVarInt("WS_AreaNumber", RandInt( 20, 27 )); //DEPENDING ON WHERE THE PLAYER IS, PLAY SOUNDS ON DIFFERENT AREAS AddDebugMessage("Playing sounds in CAMS_4", false); //ADD DEBUG MESSAGE FOR TESTING PURPOSES }
AddDebugMessage("Played sound at WindSoundArea_" + GetLocalVarInt("WS_AreaNumber"), false);
float AmbientWindSoundsTimerNumber = RandFloat( 10.0f, 25.0f ); //CALCULATES NEW TIMER VALUE
AddTimer("aw_soundstimer", AmbientWindSoundsTimerNumber, "RandomWindSound"); //ACTIVATES NEW TIMER }
void RandomStepsSound(string &in asTimer) { //-----------------------------------||-----------------------------------// // Plays a random STEPS sound on one of the specified areas. // Timer is originally set between float values 10.0 and 25.0 seconds. // Timer: AmbientStepsSoundsTimer //-----------------------------------||-----------------------------------//
void RandomScrapeSound(string &in asTimer) { //-----------------------------------||-----------------------------------// // Plays a random SCRAPE sound on one of the specified areas. // Timer is originally set between float values 3.0 and 15.0 seconds. // Timer: AmbientStepsSoundsTimer //-----------------------------------||-----------------------------------//
void RandomVoiceSound(string &in asTimer) { //-----------------------------------||-----------------------------------// // Plays a random VOICE sound on one of the specified areas. // Timer is originally set between float values 30.0 and 120.0 seconds. // Timer: AmbientVoiceSoundsTimer //-----------------------------------||-----------------------------------//
/* ACTIVATE FOR PLAYING SOUNDS AT RANDOM AREAS INSTEAD OF AT THE PLAYER if(GetLocalVarString("CAMS_State") == "CustomAmbientMapScares_1"){ SetLocalVarInt("VS_AreaNumber", RandInt( 1, 7 )); AddDebugMessage("Playing sounds in CAMS_1", false); } if(GetLocalVarString("CAMS_State") == "CustomAmbientMapScares_2"){ SetLocalVarInt("VS_AreaNumber", RandInt( 8, 13 )); AddDebugMessage("Playing sounds in CAMS_2", false); } if(GetLocalVarString("CAMS_State") == "CustomAmbientMapScares_3"){ SetLocalVarInt("VS_AreaNumber", RandInt( 14, 19 )); } if(GetLocalVarString("CAMS_State") == "CustomAmbientMapScares_4"){ SetLocalVarInt("VS_AreaNumber", RandInt( 20, 27 )); } */
AddDebugMessage("Played voice sound at Player", false);
float AmbientVoiceSoundsTimerNumber = RandFloat( 30.0f, 120.0f ); //CALCULATE NEW TIMER VALUE
AddTimer("av_soundstimer", AmbientVoiceSoundsTimerNumber, "RandomVoiceSound"); //ACTIVATE NEW TIMER }
void RandomObjectPushFunction(string &in asTimer) { //-----------------------------------||-----------------------------------// // Adds a pushing force to one of the specified entities by a random amount. // Uses categories to select which kind of entity to be pushed. // The chance of every category to trigger is different. // Strength of push is randomized within the assigned values. // Timer is originally set between float values 30.0 and 60.0 seconds. // Timer: AmbientObjectFallFunctionTimer //-----------------------------------||-----------------------------------//
int OP_CategoryNumber = RandInt( 1, 100 ); // GENERATES A RANDOM NUMBER BETWEEN 1 AND 100.
if( OP_CategoryNumber <= 40 ) // THE NUMBER REPRESENTS THE PERCENTAGE CHANCE OF THIS CATEGORY BEING TRIGGERED. ONLY USES ONE VALUE SINCE IT'S THE FIRST CATEGORY. { //-----------------------------------||-----------------------------------// // Book category. // Mainly used for pushing down books from shelves. // Default chance on trigger: 40% // Default pushing values: 75 - 150 //-----------------------------------||-----------------------------------//
int Book_CategoryNumber = RandInt( 1, 4 ); // DECIDES WHICH DIRECTION-CATEGORY TO CHOOSE
int PushAmount = RandInt( 75, 150 ); // DECIDES THE STRENGTH OF THE PUSH
///// - PUSH X DIRECTION - ///// if( Book_CategoryNumber == 1){ int Book_Number = RandInt( 1, 3 ); // FIRST VALUE ALWAYS 1, SECOND VALUE IS AMOUNT OF BOOKS AddPropForce("push_book_x_" + Book_Number, PushAmount, 0, 0, "world"); // PUSHES THE BOOK }
///// - PUSH Z DIRECTION - ///// if( Book_CategoryNumber == 2){ int Book_Number = RandInt( 1, 3 ); // FIRST VALUE ALWAYS 1, SECOND VALUE IS AMOUNT OF BOOKS AddPropForce("push_book_z_" + Book_Number, 0, 0, -PushAmount, "world"); // PUSHES THE BOOK } ///// - PUSH NEGATIVE X DIRECTION - ///// if( Book_CategoryNumber == 3){ int Book_Number = RandInt( 1, 4 ); // FIRST VALUE ALWAYS 1, SECOND VALUE IS AMOUNT OF BOOKS AddPropForce("push_book_-x_" + Book_Number, -PushAmount, 0, 0, "world"); // PUSHES THE BOOK }
///// - PUSH NEGATIVE Z DIRECTION - ///// if( Book_CategoryNumber == 4){ int Book_Number = RandInt( 1, 3 ); // FIRST VALUE ALWAYS 1, SECOND VALUE IS AMOUNT OF BOOKS AddPropForce("push_book_-z_" + Book_Number, 0, 0, PushAmount, "world"); // PUSHES THE BOOK } }
if( OP_CategoryNumber > 40 && OP_CategoryNumber <= 60 ) // DECIDES TRIGGER CHANCE. (SECOND VALUE) - (FIRST VALUE) = PERCENTAGE CHANCE OF CATEGORY BEING TRIGGERED. { //-----------------------------------||-----------------------------------// // Container category. // Mainly used to push containers over ledges. // Default chance on trigger: 20% // Default pushing values: 400 - 500 //-----------------------------------||-----------------------------------//
int Container_CategoryNumber = RandInt( 1, 4 );
int PushAmount = RandInt( 400, 500 );
///// - PUSH X DIRECTION - ///// if( Container_CategoryNumber == 1){ int Container_Number = RandInt( 1, 1 ); //ONLY USE ONE CONTAINER AddPropForce("push_container_x_" + Container_Number, PushAmount, 0, 0, "world"); }
///// - PUSH Z DIRECTION - ///// if( Container_CategoryNumber == 2){ int Container_Number = RandInt( 1, 3 ); AddPropForce("push_container_z_" + Container_Number, 0, 0, PushAmount, "world"); }
///// - PUSH NEGATIVE X DIRECTION - ///// if( Container_CategoryNumber == 3){ int Container_Number = RandInt( 1, 3 ); AddPropForce("push_container_-x_" + Container_Number, -PushAmount, 0, 0, "world"); }
///// - PUSH NEGATIVE Z DIRECTION - ///// if( Container_CategoryNumber == 4){ int Container_Number = RandInt( 1, 3 ); AddPropForce("push_container_-z_" + Container_Number, 0, 0, -PushAmount, "world"); } }
if( OP_CategoryNumber > 60 && OP_CategoryNumber <= 80 ) { //-----------------------------------||-----------------------------------// // Bottle category. // Mainly used for pushing bottles, glasses and goblets off of tables. // Default chance on trigger: 20% // Default pushing values: 75 - 150 //-----------------------------------||-----------------------------------//
int Bottle_CategoryNumber = RandInt( 1, 4 );
int PushAmount = RandInt( 75, 150 );
///// - PUSH X DIRECTION - ///// if( Bottle_CategoryNumber == 1){ int Bottle_Number = RandInt( 1, 5 ); AddPropForce("push_bottle_x_" + Bottle_Number, PushAmount, 0, 0, "world"); }
///// - PUSH Z DIRECTION - ///// if( Bottle_CategoryNumber == 2){ int Bottle_Number = RandInt( 1, 3 ); AddPropForce("push_bottle_z_" + Bottle_Number, 0, 0, PushAmount, "world"); }
///// - PUSH NEGATIVE X DIRECTION - ///// if( Bottle_CategoryNumber == 3){ int Bottle_Number = RandInt( 1, 3 ); AddPropForce("push_bottle_-x_" + Bottle_Number, -PushAmount, 0, 0, "world"); }
///// - PUSH NEGATIVE Z DIRECTION - ///// if( Bottle_CategoryNumber == 4){ int Bottle_Number = RandInt( 1, 3 ); AddPropForce("push_bottle_-z_" + Bottle_Number, 0, 0, -PushAmount, "world"); }
}
if( OP_CategoryNumber > 80 && OP_CategoryNumber <= 90 ) { //-----------------------------------||-----------------------------------// // Lamp category. // Mainly used to give chandeliers a light push. // Chance on trigger: 10% // Default pushing values: (-500) - 500 //-----------------------------------||-----------------------------------//
void RandomPlayerScareFunction(string &in asTimer) { //-----------------------------------||-----------------------------------// // Starts an event that is based on and affects the player. // Timer is originally set between float values 60.0 and 300.0 seconds. // Timer: AmbientPlayerScareFunctionTimer //-----------------------------------||-----------------------------------//
}
void ChangeCAMSState(string &in asParent, string &in asChild, int alState) { SetLocalVarString("CAMS_State", asChild); AddDebugMessage("Changed CAMS_State to " + asChild, false); }
RandomWindSound()
- Plays a random WIND sound on one of the specified areas.
- Timer is originally set between float values 10.0 and 25.0 seconds.
- Timer: AmbientWindSoundsTimer
RandomStepsSound()
- Plays a random STEPS sound on one of the specified areas.
- Timer is originally set between float values 10.0 and 25.0 seconds.
- Timer: AmbientStepsSoundsTimer
RandomScrapeSound()
- Plays a random SCRAPE sound on one of the specified areas.
- Timer is originally set between float values 3.0 and 15.0 seconds.
- Timer: AmbientScrapeSoundsTimer
RandomVoiceSound()
- Plays a random VOICE sound on one of the specified areas.
- Timer is originally set between float values 30.0 and 120.0 seconds.
- Timer: AmbientVoiceSoundsTimer
RandomObjectPushFunction()
- Adds an impulse to one of the specified entities by a random amount.
- Uses categories to select which kind of entity to be pushed.
- The chance of every category to trigger is different.
- Uses three different pushing values: Weak, Medium and Strong.
- Timer is originally set between float values 30.0 and 180.0 seconds.
- Timer: AmbientObjectFallFunctionTimer
RandomPlayerScareFunction()
- Starts an event that is based on and affects the player.
- Timer is originally set between float values 60.0 and 300.0 seconds.
- Timer: AmbientPlayerScareFunctionTimer
ChangeCAMSState()
- Changes which areas are getting randomized when player enters a new part of the map.
- ScriptAreas for executing this when player collides with them is called "CustomAmbientMapScares_[number here]".
- Useful for large maps.
StartCustomAmbientMapScares() (used in OnEnter(); function)
- Generates a random value for each and every timer.
- Starts the timers with the randomly generated values.
I know all that code is a pain in the ass to read, so I will upload the actual file here too, for you to download. It's currently a text file (.txt), so it cannot be used as the script file for a map. Copy and paste is needed.
If you find any problems, please let me know. Also, as mentioned above, let me know what you think of this and if you want me to update it and continue to develop it, or if you want to be a part of it.
Thank you for reading this post, and thank you for sharing your thoughts. Have a great day.
________________________________________________________________________________________________________________________________________________________________
UPDATE - CHANGELOG!
Spoiler below!
After spending a few hours of testing and tweaking, I have now completed the first working product of the "Custom Ambient Map Scares". You can access it either through the php window above, or by downloading the attached .txt file.
What I added:
- There is now 5 categories of entities within the RandomObjectPushFunction(). "Books", "containers", "bottles", "lamps" and "other".
- Instead of three different strengths of the pushes, the force will now be any number between to assigned values.
- Example sounds where added to the sound functions.
- StartCustomAmbientMapScares(); is now placed in the OnEnter function instead of the OnStart one.
- All errors were removed. It now runs perfectly!
If you wanna try this, simply put this block of code into your map, and add this line to your OnEnter() function:
StartCustomAmbientMapScares();
After that you add the entities that needs to be added, and name them:
push_CATEGORY NAME HERE_AXIS TO PUSH ENTITY HERE (x or z)_ENTITY'S NUMBER HERE (1, 2, 3 etc.)
For example:
push_book_x_1
Then you add the areas that the sounds will be coming from, and name them:
SOUND CATEGORY HERESoundArea_AREA'S NUMBER HERE
For example:
WindSoundArea_1
Please leave feedback and suggestions in the comments. Thank you!
UPDATE - VIDEO!
Spoiler below!
Here is a recorded video of me testing the script. Timers are set between 5 - 15 seconds for testing purposes, and will remain their original length in the release version (see above).
All the current functions except RandomPlayerScareFunction() is used in this video. All the sounds is used and is coming from three different directions, and all the five entity categories of the RandomObjectPushFunction is used. For more info, see above, or take a look at the downloadable text file.
Please leave feedback and suggestions in the comments. Thank you!
UPDATE - NEW FEATURE!
Spoiler below!
- Objects can now be pushed two more directions, from now on including negative values too.
BIG UPDATE - NEW FEATURES AND IMPROVEMENTS!
Spoiler below!
- Scrape sounds will now be played at random areas in your map.
- Using FG's LocalVarInts instead of normal integers for simplified and less troubling implementation.
- Added better descriptions to the functions.
- Added debug messages at some functions for testing purposes.
- Can now change the array of areas that can be randomly picked depending on where the player is located, using areas called "CustomAmbientMapScares_[number here]". For more info, see script and/or functions section.
- Script is now in a working condition, designed and programmed for my personal map. Change to your own preference! (Some functions are turned off as a result to my personal preference).
Founder & Legally Accountable Publisher of Red Line Games.
Environment & Gameplay Designer and Scripter. http://moddb.com/mods/in-lucys-eyes
(This post was last modified: 12-11-2013, 07:36 PM by Wapez.)
Updated a typo error. As mentioned, I have not had the time to test this.
The response of this is amazing. I will continue developing this into something big. What I can ask from all of you now is not much, but it's incredibly useful. I gladly accept
- Tips and tricks
- Suggestions on new functions and/or how to execute them
- TESTERS!
My own complete testing of this will start tomorrow, monday. Let me know if you want to help out.
Thank you.
Founder & Legally Accountable Publisher of Red Line Games.
Environment & Gameplay Designer and Scripter. http://moddb.com/mods/in-lucys-eyes
Yes, maybe make one thread with all your scripts etc... and hide details with spoiler tags for example to not make them huge. For example there is no point in having one thread for the actual assets/scripts and another to explain what each does. Keep them all together, nice and organized.
(10-21-2013, 11:02 PM)plutomaniac Wrote: Yes, maybe make one thread with all your scripts etc... and hide details with spoiler tags for example to not make them huge. For example there is no point in having one thread for the actual assets/scripts and another to explain what each does. Keep them all together, nice and organized.
Btw, poll removed at your request.
I'll bake them together when I get some time. Thanks. UPDATED!
Added a video showing the working product. Check it out!
Founder & Legally Accountable Publisher of Red Line Games.
Environment & Gameplay Designer and Scripter. http://moddb.com/mods/in-lucys-eyes
(This post was last modified: 10-22-2013, 09:41 AM by Wapez.)