Advanced Level Features: Difference between revisions
Created page with "If you have not. You should check Creating a Level first. Let us begin with level features that require no scripting: == Soundspace ==" |
|||
| (7 intermediate revisions by the same user not shown) | |||
| Line 1: | Line 1: | ||
If you have not. You should check [[Creating a Level]] first. | If you have not. You should check [[Creating a Level]] first. | ||
Let us begin with level features that require no scripting | Let us begin with level features that require no scripting. The following features are added by editing prim descriptions. | ||
== Soundspace == | The XOBJ syntax for descriptions are task0$task0arg0$task0arg1...$$task1$task1arg0$task1arg1... | ||
Separate tasks from arguments with a single $ | |||
Use multiple tasks in a description by separating them with $$ | |||
These three features are all set by scanning the description of the prim that a player is standing on. You do not have to set the soundspace or environment on every prim, because it will only updated when the player moves to another prim that has a soundspace or environment description. Footsteps require all prims that you walk on to have the proper sound in the description. | |||
=== Soundspace === | |||
A soundspace is a way to set an ambient loop to play in the background. There are built in soundspaces, but you can also set your own via UUID. | |||
The soundspace tasks looks like: "SS$sound$volume", so "SS$ru$0.2" will add a rumble ambient loop. | |||
You can replace the ru with a UUID to use a custom sound, or use one of the preset sounds from the soundspace header file: https://github.com/JasXSL/SL-XOBJ/blob/master/xobj_core/classes/jas%20Soundspace.lsl | |||
=== Footsteps === | |||
Footsteps use the same system. Footsteps should be set on all of the prims that avatars walk on. Many of the JasX library meshes come with footsteps already setup. | |||
The footstep task looks like: "tFS$type", if you wanted sand you would use "tFS$SAND". You can find a full list of supported sounds in the header file: https://github.com/JasXSL/SL-XOBJ/blob/master/xobj_toonie/classes/ton%20Footsteps.lsl | |||
You cannot use UUIDs for footsteps. But you can make pull requests on githup for additional sounds. | |||
=== Environment Settings === | |||
Environment settings allow you to change the sky setting (formerly windlight). It works like Soundspace in that it is only needed on points where you want it to change, such as the prim players start their level on. | |||
The environment task looks like: "WL$sky_uuid", such as "WL$96b70f39-ce1e-8e92-257d-d54d34bd3853" | |||
You can only use UUIDs for this setting. | |||
=== Doing all at once === | |||
Let us assume that we want to set footsteps, windlight, and a soundspace on the prim that players start the level on. | |||
The soundspace looks like "SS$ru$0.2", the footsteps like "tFS$SAND", and windlight like "WL$96b70f39-ce1e-8e92-257d-d54d34bd3853". | |||
We combine these with $$ to get the description: SS$ru$0.2$$tFS$SAND$$WL$96b70f39-ce1e-8e92-257d-d54d34bd3853 | |||
We edit the floor prim that player start on and set its description to that. The change should be immediately visible! | |||
=== Water === | |||
# Create a prim. The prim must be a box, but it can have any shape as a box. | |||
# Set the prim name to WATER (all upper case) | |||
# Set the prim to phantom. | |||
# The prim is now treated as water. Walk into it to test. | |||
# Drag a got Portal script from your devtools into the water prim. | |||
# Take a copy of your water prim and put it inside of your level's inventory. | |||
# Say add to create a spawn entry for your water. | |||
# Optionally you can use the WATER prim that comes with the devtools. | |||
===== Adding multiple waters ===== | |||
# Rez the WATER prim from devtools. | |||
# Rename it to WATER_2 | |||
# Position it and take a copy. | |||
# Put the copy into your level inventory. | |||
# Say add to add the level. | |||
# When spawned live, the WATER_2 prim will rename itself to WATER. | |||
== Making your level more interactive with scripts == | |||
=== Level scripts === | |||
Before you can start. You must install the GoThongs and LSL libraries: [[Installing Script Libraries]] | |||
The level _MAIN script is a unique script to each level. The other scripts are loaded from the HUD and overwritten when you rez the level. All the custom game logic should go in the _MAIN script. | |||
There are two main ways to communicate: | |||
* The built in scripts such as got Level, got LevelData etc raise events that you can do things with in the onEvt function. You can find events raised by built in scripts in their header files. Such as LevelEvt$ constants in https://github.com/JasXSL/GoThongs/blob/master/classes/got%20Level.lsl#L47 See the script header files for events that you can utilize! | |||
* To communicate between custom scripts you use standard LSL listeners. Many levels use channel 69 or 80085 but you can pick your own channels. | |||
The _MAIN script comes with a few of the most common events pre-defined. We will go through some of them: | |||
==== LevelEvt$load ==== | |||
Raised when a spawn group (see below) has been loaded. An empty spawn group of "" means that the level start has loaded, and is a good place to reset your custom code such as level progress. | |||
==== Triggers & Spawn Groups ==== | |||
Triggers are events tied to players walking through a trigger prim. They are often used to track player progress and spawn monsters as you go. Spawning only a few monsters at start and then more monsters as player proceed makes the level much faster to load and reduces prim cost. It is recommended. | |||
# You can spawn a trigger the same way you do monsters. The recommended way is to say "spawn Trigger" in chat. | |||
# Place and resize the trigger. | |||
# Say "trig SPB" in chat. This will update the description for you which consists of an JSON array with 3 values: 1. A vector that should be the size of the trigger, so if your trigger is 6x2x4, the first value should be "<6,2,4>". 2. An integer of flags. Currently only 1 flag is supported: 0x1 and causes the trigger to despawn when triggered. 3. A name that is included in the trigger event. | |||
# Save it like any other asset by saying "add". | |||
# Edit your _MAIN script and find the LevelEvt$trigger handler and edit the if(d == "SPB") if statement to: if(d == "SPB"){ qd("Trigger named SPB triggered!"); } | |||
# Say listAssets or click ASSETS on your dev HUD and spawn the trigger live. The trigger will flash quickly and go invisible. | |||
# Walking into the trigger will make your _MAIN script output a debug message saying that you triggered it. | |||
Let us create a monster spawn group and have it spawn when players walk into the trigger. | |||
# Spawn a monster or a few like you did in the first guide. | |||
# You are familiar with the "add" command. If you put some text after typing add, it creates spawns with a spawn group for that text. For now, type "add SPB" to create spawn entries with the spawn group "SPB". Remember to clean up after adding them. | |||
# You will get a message like this: [14:58] Test Level: Inserted 5 >>> [0,"Cock Goblin 2","<3,6,11.16>","<0,0,-0.707,.707>","","SPB"] (Remove) [14:58] Test Level: Inserted 6 >>> [0,"Cock Goblin 2","<4.5,3,11.16>","","","SPB"] (Remove) | |||
# The final value in the array (index #5) is the spawn group. You can test spawning the group live by saying "load live SPB" | |||
# Go back to your _MAIN script and edit the SPB if statement again: if(d == "SPB"){ Level$intLoadSharp("SPB"); } | |||
# Spawn your trigger live again. This time when you walk through it, all the NPCs with the spawn group set to "SPB" will spawn. | |||
==== ID Events ==== | |||
When you give a monster an ID like we did with finalGoblin in the last tutorial, it will raise ID events that you can handle in the _MAIN script. In the last tutorial we used the ID "finalGoblin". This is handled in if(n == "finalGoblin"){} and sets a flag to allow exit. You can use this to track killing a certain amount of monsters etc. You can see the full range of events in the level header event: https://github.com/JasXSL/GoThongs/blob/master/classes/got%20Level.lsl | |||
==== Music ==== | |||
Music can be triggered by using the Bridge$audioMusic(playerUUID, url, volume, looping). You have to run this on every player that you want to play music on. See LevelEvt$load for an example. | |||
You can find the list of installed creative commons tracks here: https://jasx.org/lsl/got/media/audio/ | |||
To create a musical moment (short clip) that temporarily silences playing music you use Bridge$audioMoment(key playerstring filaName, float volume) | |||
To stop playing audio you use Bridge$audioStop(key uuid, string file, float fadeOutTime) | |||
Example: Bridge$audioMusic(llGetOwner(), "tense_loop.ogg", 0.3, FALSE); | |||
You are also allowed provide a full URL to use a song hosted on your own server. The music MUST be an OGG file. | |||
==== Objective Hints ==== | |||
* The usual way to track objectives is to set progress flags in the onObjectives function. All you need to do is to set the value of string txt to the hint. | |||
* For an example you can define a BFL bitflag like #define BFL_FIRST_TRIGGER 0x2 | |||
* Then we change the "SPB" LevelEvt$trigger event to: | |||
<pre> | |||
if(d == "SPB"){ | |||
Level$intLoadSharp("SPB"); | |||
BFL = BFL|BFL_FIRST_TRIGGER; | |||
} | |||
</pre> | |||
* And then we make our onObjectives to: | |||
<pre> | |||
onObjectives(key id){ | |||
string txt = "Go forward"; | |||
if( BFL & BFL) | |||
txt = "First trigger reached, take a right!"; | |||
if(txt){ | |||
list p = PLAYERS; | |||
if(id) | |||
p = [id]; | |||
list_shift_each(p, val, | |||
Alert$freetext(val, txt, FALSE, TRUE); | |||
) | |||
} | |||
} | |||
</pre> | |||
* Load the level again with "load live" and click the objective hint button. Then when you walk into the trigger and click it again, the text will have changed! | |||
==== Using the timer template for cutscenes ==== | |||
The easiest way to make a cut-scene is to use the custom timer event. It defines some variables that you can use to automate your scene: | |||
<pre> | |||
integer nr = (integer)data; | |||
float next; // When set to a positive value, it will loop the timer and increase the value of nr by 1 | |||
string text; string sound; // when text is set, it will appear on all players HUDs like if an NPC is speaking. Sound can be set to a UUID of a sound to play. | |||
vector cam = <-1,-1,-1>; rotation camRot; // when cam is set to ZERO_VECTOR it clears the camera params. When set to a non-zero and not <-1,-1,-1> it will lock the players' cameras to a position relative to the level root prim. | |||
</pre> | |||
The HUD comes with a command that logs the camera position and rotation relative to the level root prim. | |||
# Position your camera and say: cam | |||
# Look in chat. You will want to copy the vector and rotation such as: [15:19] GoT - DevHUD :: 2023-05: Put in level core: RLV$setCamera(TARG_KEY, (<4.156967, -9.853317, 12.115234>+llGetRootPosition()), (<-0.03346, 0.14670, 0.21984, 0.96386>)); | |||
# Lets build the following code and break it down: | |||
<pre> | |||
// Multi-timers if you need them | |||
timerEvent(string id, string data){ | |||
integer nr = (integer)data; | |||
float next; | |||
string text; string sound; | |||
vector cam = <-1,-1,-1>; rotation camRot; | |||
// Our custom code starts here | |||
// Timer handler for timer "CUTSCENE" | |||
if( id == "CUTSCENE" ){ | |||
// We start off with nr 0 | |||
if( nr == 0 ){ | |||
// Set the camera position and rotation from our "cam" command | |||
cam = <4.156967, -9.853317, 12.115234>; | |||
camRot = <-0.03346, 0.14670, 0.21984, 0.96386>; | |||
// Setting a text and sound outputs it on the HUDs | |||
text = "Goblin: I hear somebody!"; | |||
sound = "8c82492d-304b-ab35-ffac-dd67c5fc3d3b"; | |||
// Wait 4 seconds | |||
next = 4; | |||
} | |||
// This fires after 4 seconds because we set next to 4 above | |||
else if( nr == 1 ){ | |||
// Setting cam to ZERO_VECTOR frees the cameras again | |||
cam = ZERO_VECTOR; | |||
// We output a message | |||
text = "Goblin: We better get ready!"; | |||
sound = "4f07f8f3-2215-4e27-ad96-e88c6ab8874e"; | |||
} | |||
} | |||
runOnPlayers(targ, | |||
if(text) | |||
Language$common(targ, text, sound, 0.75); | |||
if(cam == ZERO_VECTOR) | |||
RLV$clearCamera(targ); | |||
else if(camRot != ZERO_ROTATION) | |||
RLV$setCamera(targ, (cam+llGetRootPosition()), camRot); | |||
) | |||
if(next) | |||
multiTimer([id, nr+1, next, FALSE]); | |||
} | |||
</pre> | |||
We still need to trigger our cut-scene. A good place to do it is with a trigger. So let us rewrite the trigger that we made before: | |||
<pre> | |||
if(d == "SPB"){ | |||
Level$intLoadSharp("SPB"); | |||
// Set a timer named "CUTSCENE" with data 0 and fire after 0.1 seconds | |||
multiTimer(["CUTSCENE", 0, 0.1, FALSE]); | |||
} | |||
</pre> | |||
== Next steps == | |||
The next step is customizing NPC behavior with [[Monster Scripts]] such as making friendly monsters or having monsters walk along a path. | |||
[[Category:Tutorials]] | |||
Latest revision as of 15:38, 14 April 2024
If you have not. You should check Creating a Level first.
Let us begin with level features that require no scripting. The following features are added by editing prim descriptions.
The XOBJ syntax for descriptions are task0$task0arg0$task0arg1...$$task1$task1arg0$task1arg1...
Separate tasks from arguments with a single $
Use multiple tasks in a description by separating them with $$
These three features are all set by scanning the description of the prim that a player is standing on. You do not have to set the soundspace or environment on every prim, because it will only updated when the player moves to another prim that has a soundspace or environment description. Footsteps require all prims that you walk on to have the proper sound in the description.
Soundspace
A soundspace is a way to set an ambient loop to play in the background. There are built in soundspaces, but you can also set your own via UUID.
The soundspace tasks looks like: "SS$sound$volume", so "SS$ru$0.2" will add a rumble ambient loop.
You can replace the ru with a UUID to use a custom sound, or use one of the preset sounds from the soundspace header file: https://github.com/JasXSL/SL-XOBJ/blob/master/xobj_core/classes/jas%20Soundspace.lsl
Footsteps
Footsteps use the same system. Footsteps should be set on all of the prims that avatars walk on. Many of the JasX library meshes come with footsteps already setup.
The footstep task looks like: "tFS$type", if you wanted sand you would use "tFS$SAND". You can find a full list of supported sounds in the header file: https://github.com/JasXSL/SL-XOBJ/blob/master/xobj_toonie/classes/ton%20Footsteps.lsl
You cannot use UUIDs for footsteps. But you can make pull requests on githup for additional sounds.
Environment Settings
Environment settings allow you to change the sky setting (formerly windlight). It works like Soundspace in that it is only needed on points where you want it to change, such as the prim players start their level on.
The environment task looks like: "WL$sky_uuid", such as "WL$96b70f39-ce1e-8e92-257d-d54d34bd3853"
You can only use UUIDs for this setting.
Doing all at once
Let us assume that we want to set footsteps, windlight, and a soundspace on the prim that players start the level on.
The soundspace looks like "SS$ru$0.2", the footsteps like "tFS$SAND", and windlight like "WL$96b70f39-ce1e-8e92-257d-d54d34bd3853".
We combine these with $$ to get the description: SS$ru$0.2$$tFS$SAND$$WL$96b70f39-ce1e-8e92-257d-d54d34bd3853
We edit the floor prim that player start on and set its description to that. The change should be immediately visible!
Water
- Create a prim. The prim must be a box, but it can have any shape as a box.
- Set the prim name to WATER (all upper case)
- Set the prim to phantom.
- The prim is now treated as water. Walk into it to test.
- Drag a got Portal script from your devtools into the water prim.
- Take a copy of your water prim and put it inside of your level's inventory.
- Say add to create a spawn entry for your water.
- Optionally you can use the WATER prim that comes with the devtools.
Adding multiple waters
- Rez the WATER prim from devtools.
- Rename it to WATER_2
- Position it and take a copy.
- Put the copy into your level inventory.
- Say add to add the level.
- When spawned live, the WATER_2 prim will rename itself to WATER.
Making your level more interactive with scripts
Level scripts
Before you can start. You must install the GoThongs and LSL libraries: Installing Script Libraries
The level _MAIN script is a unique script to each level. The other scripts are loaded from the HUD and overwritten when you rez the level. All the custom game logic should go in the _MAIN script.
There are two main ways to communicate:
- The built in scripts such as got Level, got LevelData etc raise events that you can do things with in the onEvt function. You can find events raised by built in scripts in their header files. Such as LevelEvt$ constants in https://github.com/JasXSL/GoThongs/blob/master/classes/got%20Level.lsl#L47 See the script header files for events that you can utilize!
- To communicate between custom scripts you use standard LSL listeners. Many levels use channel 69 or 80085 but you can pick your own channels.
The _MAIN script comes with a few of the most common events pre-defined. We will go through some of them:
LevelEvt$load
Raised when a spawn group (see below) has been loaded. An empty spawn group of "" means that the level start has loaded, and is a good place to reset your custom code such as level progress.
Triggers & Spawn Groups
Triggers are events tied to players walking through a trigger prim. They are often used to track player progress and spawn monsters as you go. Spawning only a few monsters at start and then more monsters as player proceed makes the level much faster to load and reduces prim cost. It is recommended.
- You can spawn a trigger the same way you do monsters. The recommended way is to say "spawn Trigger" in chat.
- Place and resize the trigger.
- Say "trig SPB" in chat. This will update the description for you which consists of an JSON array with 3 values: 1. A vector that should be the size of the trigger, so if your trigger is 6x2x4, the first value should be "<6,2,4>". 2. An integer of flags. Currently only 1 flag is supported: 0x1 and causes the trigger to despawn when triggered. 3. A name that is included in the trigger event.
- Save it like any other asset by saying "add".
- Edit your _MAIN script and find the LevelEvt$trigger handler and edit the if(d == "SPB") if statement to: if(d == "SPB"){ qd("Trigger named SPB triggered!"); }
- Say listAssets or click ASSETS on your dev HUD and spawn the trigger live. The trigger will flash quickly and go invisible.
- Walking into the trigger will make your _MAIN script output a debug message saying that you triggered it.
Let us create a monster spawn group and have it spawn when players walk into the trigger.
- Spawn a monster or a few like you did in the first guide.
- You are familiar with the "add" command. If you put some text after typing add, it creates spawns with a spawn group for that text. For now, type "add SPB" to create spawn entries with the spawn group "SPB". Remember to clean up after adding them.
- You will get a message like this: [14:58] Test Level: Inserted 5 >>> [0,"Cock Goblin 2","<3,6,11.16>","<0,0,-0.707,.707>","","SPB"] (Remove) [14:58] Test Level: Inserted 6 >>> [0,"Cock Goblin 2","<4.5,3,11.16>","","","SPB"] (Remove)
- The final value in the array (index #5) is the spawn group. You can test spawning the group live by saying "load live SPB"
- Go back to your _MAIN script and edit the SPB if statement again: if(d == "SPB"){ Level$intLoadSharp("SPB"); }
- Spawn your trigger live again. This time when you walk through it, all the NPCs with the spawn group set to "SPB" will spawn.
ID Events
When you give a monster an ID like we did with finalGoblin in the last tutorial, it will raise ID events that you can handle in the _MAIN script. In the last tutorial we used the ID "finalGoblin". This is handled in if(n == "finalGoblin"){} and sets a flag to allow exit. You can use this to track killing a certain amount of monsters etc. You can see the full range of events in the level header event: https://github.com/JasXSL/GoThongs/blob/master/classes/got%20Level.lsl
Music
Music can be triggered by using the Bridge$audioMusic(playerUUID, url, volume, looping). You have to run this on every player that you want to play music on. See LevelEvt$load for an example.
You can find the list of installed creative commons tracks here: https://jasx.org/lsl/got/media/audio/
To create a musical moment (short clip) that temporarily silences playing music you use Bridge$audioMoment(key playerstring filaName, float volume)
To stop playing audio you use Bridge$audioStop(key uuid, string file, float fadeOutTime)
Example: Bridge$audioMusic(llGetOwner(), "tense_loop.ogg", 0.3, FALSE);
You are also allowed provide a full URL to use a song hosted on your own server. The music MUST be an OGG file.
Objective Hints
- The usual way to track objectives is to set progress flags in the onObjectives function. All you need to do is to set the value of string txt to the hint.
- For an example you can define a BFL bitflag like #define BFL_FIRST_TRIGGER 0x2
- Then we change the "SPB" LevelEvt$trigger event to:
if(d == "SPB"){
Level$intLoadSharp("SPB");
BFL = BFL|BFL_FIRST_TRIGGER;
}
- And then we make our onObjectives to:
onObjectives(key id){
string txt = "Go forward";
if( BFL & BFL)
txt = "First trigger reached, take a right!";
if(txt){
list p = PLAYERS;
if(id)
p = [id];
list_shift_each(p, val,
Alert$freetext(val, txt, FALSE, TRUE);
)
}
}
- Load the level again with "load live" and click the objective hint button. Then when you walk into the trigger and click it again, the text will have changed!
Using the timer template for cutscenes
The easiest way to make a cut-scene is to use the custom timer event. It defines some variables that you can use to automate your scene:
integer nr = (integer)data; float next; // When set to a positive value, it will loop the timer and increase the value of nr by 1 string text; string sound; // when text is set, it will appear on all players HUDs like if an NPC is speaking. Sound can be set to a UUID of a sound to play. vector cam = <-1,-1,-1>; rotation camRot; // when cam is set to ZERO_VECTOR it clears the camera params. When set to a non-zero and not <-1,-1,-1> it will lock the players' cameras to a position relative to the level root prim.
The HUD comes with a command that logs the camera position and rotation relative to the level root prim.
- Position your camera and say: cam
- Look in chat. You will want to copy the vector and rotation such as: [15:19] GoT - DevHUD :: 2023-05: Put in level core: RLV$setCamera(TARG_KEY, (<4.156967, -9.853317, 12.115234>+llGetRootPosition()), (<-0.03346, 0.14670, 0.21984, 0.96386>));
- Lets build the following code and break it down:
// Multi-timers if you need them
timerEvent(string id, string data){
integer nr = (integer)data;
float next;
string text; string sound;
vector cam = <-1,-1,-1>; rotation camRot;
// Our custom code starts here
// Timer handler for timer "CUTSCENE"
if( id == "CUTSCENE" ){
// We start off with nr 0
if( nr == 0 ){
// Set the camera position and rotation from our "cam" command
cam = <4.156967, -9.853317, 12.115234>;
camRot = <-0.03346, 0.14670, 0.21984, 0.96386>;
// Setting a text and sound outputs it on the HUDs
text = "Goblin: I hear somebody!";
sound = "8c82492d-304b-ab35-ffac-dd67c5fc3d3b";
// Wait 4 seconds
next = 4;
}
// This fires after 4 seconds because we set next to 4 above
else if( nr == 1 ){
// Setting cam to ZERO_VECTOR frees the cameras again
cam = ZERO_VECTOR;
// We output a message
text = "Goblin: We better get ready!";
sound = "4f07f8f3-2215-4e27-ad96-e88c6ab8874e";
}
}
runOnPlayers(targ,
if(text)
Language$common(targ, text, sound, 0.75);
if(cam == ZERO_VECTOR)
RLV$clearCamera(targ);
else if(camRot != ZERO_ROTATION)
RLV$setCamera(targ, (cam+llGetRootPosition()), camRot);
)
if(next)
multiTimer([id, nr+1, next, FALSE]);
}
We still need to trigger our cut-scene. A good place to do it is with a trigger. So let us rewrite the trigger that we made before:
if(d == "SPB"){
Level$intLoadSharp("SPB");
// Set a timer named "CUTSCENE" with data 0 and fire after 0.1 seconds
multiTimer(["CUTSCENE", 0, 0.1, FALSE]);
}
Next steps
The next step is customizing NPC behavior with Monster Scripts such as making friendly monsters or having monsters walk along a path.