What follows is not an attempt to cover every aspect of scripting using LSL (Linden Scripting Language), but is intended to offer one approach to creating efficient, working scripts. The example scripts are deliberately simple (and pointless!) so that they are easy to follow. Several references will be made to the LSL Portal website which is an invaluable resource for scripting.
- a section where global variables are declared. Variables are used to store values which can be changed while the script is running. Global variables can be used anywhere in a script.
- a section for user-defined functions. These are sections of code defined by the scripter to carry out specific tasks. They are particularly helpful where the same actions are carried out several times. They are also very useful for helping to make your script easier to understand.
- a section called a state. The foundation of any LSL script is a ‘state’. At any given time a script is in a state which enables it to respond in a number of ways. At the very least there must be one state which is called ‘default’, but a script may contain two or more states. For the purposes of this article I will only refer to the default state.
- Touch – when an avatar clicks on any object
- On_rez – when any object is rezzed
- Sensor – senses whether there are any avatars within range of the script
Events are managed by sections of code called event handlers which must ALWAYS be contained with the default or another state.
One event that is particularly useful is the timer() event as this event can be initiated from within your own script at any time you choose and is very useful for a variety of purposes:
- you can use a timer to divert your script to any user function at any time
- you can repeat any section of code any number of times with a specified delay
- you can use a timer() event to switch off listens and other laggy behaviour after a specific time. For example timers are often used to switch off a listen which is associated with a menu
Global variables are declared at the very start of the script. These variables will be available in any part of the script. Local variables are defined within a function or an event and are only available within that function. Local variables can be passed from one area of a script to another as shown in the example opposite.
It is worth giving meaningful names to your variables (and user functions) as this will make your scripts much easier to understand. If you were to write a += b then the script would work, but to write TotalDonations += NewDonation makes it much more intelligible!
- INVENTORY_TEXTURE – used to specify or find textures in a prim’s inventory
- CHANGED_OWNER – used to check if the owner of the object has changed
- CLICK_ACTION_SIT – used to change the click action of any prim to the sit action
One function worth mentioning specifically is llSetLinkPrimitiveParamsFast(). This function can be used to change a great many characteristics of any prim or prims in a linkset. It can be used instead of many functions like llSetTexture() which may be simpler to use but this command has several advantages:
- it’s very fast and takes no script time to execute
- it can be used on any single prim, on groups of prims or all prims in a linkset
- a variety of parameters can be set with one instance of this function
Although it is intended to be used with prims in a linkset it can also be used with single prim objects. In this case you should refer to the prim using the constant LINK_THIS which specifies only the prim containing your script- eg: llSetLinkPrimitiveParamsFast(LINK_THIS, list rules)
- If, else if, else – this construct is used to test whether a condition or a set of conditions is true or false. The script can then take different actions according to the result of this test. This is a very powerful control and allows your scripts to take decisions based upon what is happening.
- For – this allows you to repeat a section of code a specific number of times
- Do, While, Do While – this allows you to repeat a section of code until a specified condition is met
- Jump – this instructs the script to divert to a specified section of code
- Return – this instructs the script to stop executing the code within any section and return to the previous section
Look for opportunities to write general purpose user functions which can be used in different scripts. If you find yourself writing similar code several times then it may be that a user function will be a much better way to approach this. A function for writing different textures to different faces of any prim is a good example of such a user function.
There are several ways to improve script efficiency which will often improve the speed of execution as well as reducing the impact on sim performance. It is good practice to keep the number of scripts in any object to an absolute minimum as each script adds to script time usage. Use the listen() event as little as possible and especially when listening to channel 0. Switch off a listen when you can.
A script should be written with the minimum amount of code to make it work properly. This will result in a script running more quickly, it will be easier to understand and debug and there is less chance of running out of script memory. An LSL mono script has a maximum of 64 kb of memory. This includes the code itself but you must also bear in mind that you will need memory to store the values held in variables.
My inspiration for the Freefall was the well-known floaty, dream-dance animation which I’d used for SL photography. At first my idea was simply to create a poseball with the animation inside but after some daydreaming it grew into something much bigger and better. I recalled a couple of scenes from ‘2001: A Space Odyssey’ that I thought I could incorporate in some way and before I knew it Star Wars and Star Trek had crept in there too! Add in some particle and lighting effects, music and sound effects and a simple storyline and I was good to go!
- how will the script start up when the object is rezzed?
- what are all the things the script will need to do to fulfil its purpose?
- if users need to interact with it, how will this happen? The possibilities include clicking prims or ‘buttons’ or using a menu system. (Saying stuff in nearby chat is NOT good!)
- how do you cater for different user behaviour? For example, if poseballs are involved, how does the script deal with someone TPing without jumping off
- will users get any help and, if so, how will this be given?
- Does the script keep running or will it close down at some point. If so, how do you do this so it’s ready to start again?
With the Freefall the following considerations were uppermost in my mind:
- how will the ride be started once avatars have jumped on the poseballs?
- the ride is made up of 16 ‘episodes’ each with its own set of actions. How will the script know when to move on to the next episode?
- it is accompanied by a ‘soundtrack. How can the music and action fit together?
- how will the different scripts and objects communicate with each other?
- how will the ride finish and reset itself, ready to start again? What if all the avatars jump off? How will the ride recognise this and reset itself?
I keep code within event handlers as minimal as possible and very often there is little more than calls to user functions. This makes it much easier to trace the program flow and to understand exactly what is happening when the script runs.
With the Freefall this stage was surprisingly easy in spite of its complexity. I used the sim music track as the basis for breaking up the storyline into 15 timed episodes, using natural break points in the music to determine their length. This gave me a list of 15 times in seconds and this list was placed inside a ‘master’ script which was used for the main program flow. A simple timer() loaded each episode one after the other. The number of each episode was used as an index to trigger all of the actions associated with each episode. In most cases the code for these actions was in other scripts so it was a relatively simple matter to pass the episode number to these other scripts.
Scripts need to be written very precisely according to a set of rules called syntax . Miss out a bracket, a semi-colon, a comma or use a word the script doesn’t understand and the script will fail to compile and an error message will be displayed. Helpfully this message will point you at the line containing the error!
Use comments to make your scripts more readable. It may seem extra work but it is well worth it as it makes your scripts easier to understand. This is especially true if you work on the script again at some future time.
You may wish to check out alternative script editors where you can work offline. I find it much easier to concentrate on scripting offline as I’m not tempted to test the code every few minutes and I find it a much more productive way to work. These are several other advantages such as auto-complete, automatic formatting and debugging help too.
Finding the error
You may know exactly where to look or you may have to trace the program flow up to the point of the error. It’s often a good idea to start by looking at the code inside event handlers. In particular check that conditions (using if statements) are coded accurately and that user functions are called correctly.
It’s often the case that variable values are incorrectly assigned and this will cause the script to fail. One way to make it easier to check these is to insert lines to ‘say’ the value of variables in nearby chat. A simple little user function makes this much easier. The example on the right illustrates how this works.
Correcting your code
This is more difficult to describe as there are an infinite number of ways you might do this. However, as a general priciple look for ways to keep your code as concise as possible as this makes it easier to fix! For example I remember feeling very smug when I wrote a do while loop to check whether an avatar’s name was already in a list of names. It would run through up to 700 names one at a time until it either found a match or got to the end of the list. My smugness evaporated when I discovered this could be done with a single command:
if (llListFindList(AvNames, [AvieName]) == -1) // -1 means no match was found!
Having found your error and fixed it, don’t be surprised if that creates one or two new issues. Such is the way of scripting!
Once you think your script is working perfectly it’s a really good idea to have someone else test it. Only give them the information people will have when they use your finished object. This will often throw up one or two new issues but best to find that out before you go public!