Difference between revisions of "User:SuzannaLinn Resident/ScriptingClasses/Basics 2"

From Second Life Wiki
Jump to navigation Jump to search
 
(5 intermediate revisions by the same user not shown)
Line 353: Line 353:
Do we always have to use return when we  exit the function  ?
Do we always have to use return when we  exit the function  ?
* Yes, if we have defined the function to return a value.
* Yes, if we have defined the function to return a value.
^ If it is, for instance, a string function, we need to use "return" to provide a string before the function ends.
* If it is, for instance, a string function, we need to use "return" to provide a string before the function ends.
 
 
 
== Wednesday class ==
 
=== Practice review ===
We are starting with a review practice. We have already done things like this ones.
 
This is the practice:
* rezz two objects, give them different names.
* add a script to each of them that says in a channel number of your choice (not the public chat) a message with the name of the toucher.
* rezz a third object.
* add a script to this object that listens to the same channel and says a message in public chat with the name of the object and the name of the toucher.
 
Let's go!
 
You have the solution in the class materials folder.
 
The 3 objects are: "the green listener", "the yellow sphere" and "the orange sphere".
 
It's one of the possible solutions, there are always different ways to script the same thing.
 
 
=== While ===
-> Object 4 While (a multicolor box)
-> Script 4 while
 
In line 0, we define a variable of type "integer", with the name totalFaces.
 
This variable is global to all the script, we can use it everywhere.
 
We define it as global because we are going to use it in two events.
 
Let's look at how the colors are stored in LSL.
 
A color is defined with 3 values with decimals, between 0 and 1, for the Red, Green and Blue components of the color. To keep the three RGB values we use the type "vector".
 
Vectors are often used for the position of the object, for the values of the x, y and z coordinates. Also for the size of the object, the height, width and depth coordinates.
 
And since we also use 3 values to define  colors , the scripting language uses the same type vector for the colors (instead of creating a type "color").
 
The values, between 0 and 1 with decimals, are of the type "float".
 
LSL has the type "integer" for numbers without decimals, and the type "float" for numbers with decimals.
 
Out of SL, usually we uses numbers from 0 to 255 as the RGB values. In this script we are saying both LSL and RGB values.
 
To convert to RGB we have written the function lsl2rbg, lines 2-4.
 
We define this function to return a "vector" (with the "vector" before the function name) and to take a parameter of tpye "vector" (the "vector" between the parenteses). We give to this parameter the name "color", to refer to it inside the function.
 
Multiplying a vector by a number, we get all the values in the vector (x, y and z) multiplied.
 
We could also use / to divide the values in the vector by a number.
 
And we return the result of the multiplication, which is a vector.
 
In state_entry, lines 8-11, we get the number of faces of the object and assign it to the global variable totalFaces, that we have defined in line 0.
 
We use the LL function llGetNumberOfSides(), that returns the number of sides or faces of the object where the script is in.
 
The script is in a box, and we know that a box has 6 faces. We could just write  totalFaces = 6.
 
But it's important to write scripts that can be reused as much as possible.
 
By getting the number of sides, instead of using a fixed number, we can use this script, without any modification, in any shape of object.
 
Let's go to touch_start, in lines 13·32.
 
We start defining two local variables that we are using later in this event, in lines 15-16.
 
We say how many faces has the object in line 18.
 
In lnes 20-31, there is something new, that uses the "while" keyword in line 22.
 
We want to say the color of each of the face, 6 faces in this example.
 
We could write 6 lines with llOwnerSay() to say the color of each face, and it would work.
 
But in the future we will have to do things hundreds or thousands of times, so it wouldn't be a good solution.
 
We have a much better way to do it, using the loop "while".
 
The loop is done with the lines 20, 22 and 30.
 
What we do in each loop is in lines 24-28.
 
Let's start with the loop, lines 20, 22 and 30.
 
We have 6 faces in this case. The faces are numbered starting with 0. So we want to say the color of the faces 0, 1, 2, 3, 4 and 5.
 
In line 20 we set faceCount to 0. The face 0 is the first face that we want to say the color. The variable faceCount will have all the numbers of the faces, from 0 to 5.
 
In line 22 we compare faceCount (which now is 0) with totalFaces (that is 6 all the time).
 
The comparison  facesCount < totalFaces  is true, so the block of instructions enclosed between { and } in lines 23 and 31 is executed.
 
Inside this block, after saying the color of face 0, in line 31, we add 1 to faceCount, changing it to 1.
 
After finishing the block, the script jumps back to line 22, to the "while" keyword again.
 
Now faceCount is 1, It repeats the process for face 1, changes faceCount to 2 at the end of the block, and jumps to "while" again.
 
After saying the color of face 5, facesCount is changed to 6. And jumps back to "while".
 
Now    faceCount (6) < totalFaces (6)  is false and the "while" jumps from line 22 to line 32, after the block "while", finishing the loop.
 
Inside the loop, in line 24, we get the color of the face.
 
llGetColor() takes an integer as parameter, the number of face, and returns a vector, the color of the face.
 
In lines 26-28 we say the color.
 
This is only one function. We could write it in one line. We are writing it in 3 lines for clarity.
 
We can add white space and newlines as we like, for the script until the ; is only one line.
 
 
=== While in touch start ===
-> Object 5 While in touch start(a pink torus)
-> Script 5 while in touch start
 
Now that we know how to do a loop, we can use that parameter total_number in the event touch_start.
 
total_number has the total of touches received at the same time, or nearly at the same time.
 
Nearly always is 1, only one touch. But if two people touch the object at the same time it could 2, or more.
 
We use a loop while in lines 34-44 to look at all the touchers.
 
And we use touchCount in llDetectedKey() to get the UUID of each toucher.
 
 
=== Practice ===
Now a practice a bit more difficult.
 
This is the practice:
*start with the Script 3 if else, from Monday.
*change the script to say, not only the previous toucher, but the two previous touchers.
* when a toucher is the owner, instead of the name, say "the owner" or any other message. Use the function llGetOwner(), without parameters, that returns a key with the UUID of the owner.
* any other idea that comes to your mind. Creativity is great!
 
Let's go!
 
 
===  Answers to Questions ===
Could we have assigned totalFaces when we defined it, instead of in state_entry?
* No, we can only assign fixed values to global variables when we define them, not results from functions or operacions.
* This is a limitation of the scripting language, for example, we could write    integer totalFaces = 6;,  but we can't set it equal to a function.
 
Could we use the script with other prims?
* Yes, this script can be used with any prim, regardless of the number of faces.
 
What is the advantage of assigning totalFaces in state_entry compared to elsewhere?
* There are two ways to do it, both are good.
** We are only using the variable in touch_start, so we can define it in touch_start, alongside faceColor and facesCount, like:
*** integer totalFaces = llGetNumberOfSides();
** Using a local variable inside an event or a function, unlike with global variables, allows us to assign it the value of a function or operation.
** The problem with this is that we are calling llGetNumberOfSides each time the object is touched, and calling ll functions is much slower than accessing variable.
* So we use a faster option in this example
** We define totalFaces as global at the start of the script, and since we can't call the function at that moment, we assign the value in state_entry, that it's only called after a script reset.
** The main advantage is that state_entry only runs when the script is reset or a state change occurs.  So assigning constant values is best done there so you don't redo calculations unnecessarily.
** But the problem here is that totalFaces is using script memory all the time,
* So none of both solutions is perfect.
* Neither option is perfect, so we choose based on whether we want to prioritize speed or memory conservation.
 
Does the script store that value the whole time the object is rezzed, and lose it when the object is taken, or does it continue to store it in inventory if the object is taken?
* It stores the value all the time and it is not lost when the object is taken into inventory.
** The value remains and will still be there when the object is rezzed again.
** The value its only lost if the script is reset, or modified and saved.
** or, like in this example, if we reset the script when the object is  rezzed, using the event on_rez at the end.
** After reset, SL calls the event state_entry immediately and we set the value again.
* Scripts in objects taken into inventory and rezzed again resume where they left off after being rezzed.  The only difference is that after being rezzed, the on_rez event will be triggered.
 
 
 
== Saturday class ==
 
 
=== Practice solution ===
-> Object 6 Say two previous touchers (a blue cylinder) -> Script Object 6 say two previous touchers
 
Let's look at the solution of the practice of Wednesday, a script that, when touched says the current toucher and the two previous ones.
 
Please, rezz the Object 6 Say two previous touchers.
 
We have added several lines to the scripts.
 
We need a variable to store the id of the second previous toucher. We add it in line 1 with the name lastToucherId2.
 
In the event touch_start, lines 36-40, we check is there is a toucher in lastToucherId2 to add the name to the message to say.
 
There is a change in line 45, now we are adding the name of the first previous toucher to the variable message, instead of setting the variable to the name, because it could be a message in the variable already.
 
And we have added line 52, copying the id of the first previous toucher to the variable for the second previous toucher, before copying the current toucher to the first previous toucher.
 
 
=== For ===
-> Object 7 For (a multicolor box) -> Script Object 7 for
 
On Wednesday we studied the loop while. Today we are going to look to another loop, the loop for.
 
Please, rezz the Object 7 For.
 
The loop for exists in most of the programming languages, and in each language it works in a different way.
 
In LSL the "for" is a kind of "packed while", where we can place 3 lines of code in only 1.
 
Let's look at the "Script 7 for". It's the same than the "Script 4 while" that we studied on Wednesday, but now it has both loops, while and for, doing the same.
 
The "for" condition, between parentheses, in line 38, has 3 parts, separated by semicolons.
 
* 1) The initializer. It runs only once, at the start. The same than in line 20 for the "while".
 
* 2) The condition. It runs in each loop, before running the code. If the condition is false, the code is not executed and it jumps to the next line after the closing "}". The same condition than in the "while" in line 22.
 
* 3) The increment (or decrement). It runs at the end of each loop. The same than in line 30 for the "while".
 
We can use any of both loops, the one that we like, it's a matter of preference.
 
In general, if we are counting from a starting value to another value that we know in advance, the loop for is more often used.
 
If our loop is not in that style "from 0 to 9", the "while" is the one more used.
 
Unlike other languages, in LSL we can't declare a variable inside the "for", we can't do this:
* // WRONG!      for ( integer facesCount = 0;  ...    // WRONG!
 
We need to declare the variable previously.
 
 
=== For in touch start ===
-> Object 8 For in touch start (a purple torus) -> Script Object 8 for in touch start
 
We have also added the loop for to the "Script while in touch_start".
 
Please, rezz the Object 8 For in touch_start.
 
The same than in the other script, we have joined the lines 34, 36 and 43 into the line 48.
 
 
=== Organizing inventory ===
 
And now a non-scripting topic, but that can help to write scripts better.
 
How to organize the class materials and notes, and your scripting projects, in your inventory?
 
Let's begin with the classes.
 
All class folders start with "SUZ0" and a number "01", "02",... for each week. The "0" in "SUZ0" is the series of classes.
 
So you can search your inventory for "SUZ0" and all the classes of this series of scripting will appear, and probably not much more.
 
The problem, perhaps, is that you have several folders that start with "SUZ001".
 
The contents of the classes in the week all have different names. The idea is that you move all contents to the first "SUZ001" folder and delete the other, now empty, folders.
 
And that you add a subfolder "practices" inside "SUZ001" to keep your practices related to the class.
 
Also you can add a folder "SUZ0 - scripting classes", and move all the classes inside it.
 
Now ideas on how to organize your projects.
 
Open a folder "My projects" to place all your projects inside it.
 
Add a subfolder for each project, with a descriptive name. Use it for one and only one project.
 
If you are using several textures, or animations, etc., open a subfolder in your project to store them.
 
An important thing is to control the versions.
 
Open a subfolder in your project every time that you start with a new versions, or you want to make a copy before doing important changes.
 
Use the date in the name of the version subfolder. This way, if you remember that "I was doing it about 6 months ago", will be easier to find.
 
Also in the name use the status of the version, like "finished", "in progress", "wrong way"...
 
And copy everything into the folder, not only the object that you are working on, but also all the other objects, notecards, textures, etc.
 
Sometimes it happens that we are improving a project, but at some moment we discover that how we are doing it is not the best way, and we decide to start again from the previous copy.
 
Don't delete it. Make a copy, adding "wrong way" to the subfolder. Then delete it from the main folder and recover the previous copy.
 
When we re-think on how to improve the project, we could find out that a part of the previous work is useful, or can be used with small changes.
 
When a project is finished, copy the scripts in the objects to the folder (the same that in the class materials). This way, when you want to copy-paste code for another project, it will be faster.
 
In your "My projects" folder, and a subfolder "My library".
 
There, all in the same folder, without subfolders, add the parts of code that you are going to re-use.
 
For instance, a script that reads a notecard, and does nothing else.
 
When you start a project that reads notecards, you can start with that script.
 
Good scripters are lazy, we are not going to write the same code twice :)
 
 
=== Answers to questions ===
 
Can we delete a function from the script after we have used it?
* No, no way to edit or delete from the script using LSL code, only manually.
* The only way would be to put the function in another script and then delete that script after using the function.
 
Is facesCount=facesCount+1; the same as facesCount++ ?
* Yes, the operator ++ increments the value by one, it's the same than adding one.
* There is also the operator -- that decreases the value by one-
 
Does facesCount+=1 works too?
* Yes, the operator  +=  adds the value on the right side of the = to the variable, and  -=  subtract the value on the right side.
* The ++ and -- operators are more efficient  than the = +1 operation, both memory use and execution time.
 
Is ++var correct?
*  Yes, there are both pre and post incrrement/decrement operation in LSL.  ++Var increments Var and then uses in the expression, while Var++ uses the original value in the expression and then increments it for later use.
* Before the variable, it increases the value of the variable before using this value
** with  a = 5;  b = ++a;      b will be 6, ,  b gets the value from a after a is incremented
** b= ++a    is the same than  a = a + 1;  b = a;
* After the variable, the value of the variable is used, and after it is incremented
** with  a = 5;    b = a++;    b will be 5,  b gets the value from a before a is incremented
** b= a++    is the same than  b = a;  a = a + 1;
* In the case of using it to increment the ocunter in a for loop, it doesn't matter which you use because the variable isn't used in a expression, only incremented.
 
What is the function lsl2rgb in the script?
* The function lsl2rgb in lines 2-4 is converting a vector with color values as used in LSL, values from 0 to 1, to the usual RGB values from 0 to 255.
* We use the function in the loops to say the values in both formats.
* It's a user function, written by us.
 
Does it round the values ? Do we have float values in the vector?
* No, the function doesn't round the values.
* Yes, the values in a vector are floats, numbers with decimals.
 
Are they values between 0.0 and 1.0 ?
* Yes, the values of the colors are between 0.0 and 1.0, in LSL.
 
Why is totalFaces initialized in state_entry while it is only used in touch_start?
* state_entry is only called once, so we only call the function once.
* In touch_startt we would be calling the function for each touch.
* Using a function is much slower than using a variable
 
Can we write a script that says which face has been touched, and tell the color of the touched face?
* Yes, there is a function that says the face touched:
** llDetectedTouchFace()
* For instance, like this in the event touch_start:
** integer faceTouched = llDetectedTouchFace( 0 );
** vector faceColor = llGetColor( faceTouched );
** llSay( 0, "Face " + (string)faceTouched + " has been touched. Its color is: "+ (string)faceColor );
 
Is it possible to write the two lines  integer facesCount;    and  facesCount=0;  in one line instead:  integer facesCount=0; ? Can we can set values to variables when declaring them?
* Yes, we can use  integer facesCount=0;
* We can set a value when declaring a variable, with no restrictions when the variable is inside a function or event.
* When the variable is global, we can only set it to a number or a string, not to a function, and not to the result of an operation.
 
Can we call a function in the condition of a loop for?
* Yes, we can do this:  for (facesCount=0; facesCount < llGetNumberofSides(); facesCount = facesCount+1).
* But calling the function in each loop is very slow.
* It's much better to use a variable to store the value of the function and use the variable in the loop for.
 
Can we use  for (i=0; i<totalFaces; i=i+1i  without declaring i before?
* No, all variables must be declared, we need to add a line  integer i;  before the "for"
 
Can we use two conditions in the loop for?
* Yes, we can use  condition1 && condition2    in the condition part of the "for".
 
Can a for loop count down ?
* Yes, instead of  toucherCount = toucherCount - 1  we can write  countDown = countDown -1.
* Counting down in a "for" loop is much less often used than counting up, but it's not uncommon.

Latest revision as of 01:05, 20 October 2024

Basics 2

Monday class

Function review

-> Object 1 Say Previous Toucher (a blue cylinder) -> Script 1 say previous toucher

Let's start with a review of the format that we use when creating a new user function.

In last class we created this function:

  • string getFullName(key personId)

First we have the type of the value returned by the function. In the case of functions that don't return a value, they don't have a type and we start with the name of the function.

Then it comes the name of the function, the name that we have chosen for it.

And, after the name, the parameters, between parentheses and separated by commas. In this case the function has one parameter, of type key.

So we have a function called getFullName, that needs one parameter of type key, and returns a string.

And what is personId?

We are going to use this parameter in the code of our function. We need to give a name to it. The name that we choose.

This name is only internal to the function. We are using personId in the function to get the display name and username. If we try to use personId anywhere else in the script we will get an error when saving.

When we call the function, the name that we use as a parameter changes depending on where we have the key, like:

  • getFullName( id )
  • getFullName( llDetectedKey( 0 ) )
  • getFullName( "0f16c0e1-384e-4b5f-b7ce-886dda3bce41" ), using a literal UUID (keys have to be enclosed between quotes like the strings).

Please rezz the object "Object 1 Say Previous Toucher" from your class materials folder. It's a blue cylinder.

We are improving on the the scripts of last week.

Currently our script says who touches it. We want it also to say the name of the previous toucher.

Please touch the cylinders of the students around you and also your own one, to see how it looks.

The first time it says "() touched me and now... etc" because there isn't a previous toucher. Later, we will make this look better.

Let's see how we can do it in the script.

We get the current toucher with llDetectedKey(0). But, how do we get the previous toucher?

There isn't any LSL function that returns the previous toucher. No way to get it; the information about the previous toucher is lost.

But we are clever scripters, and we have a plan:

  • we know the current toucher.
  • next time that someone touches, we will need this current toucher, to say who was the previous toucher.
  • we need to keep the current toucher now, for future uses.

And how do we keep the current toucher?

We can create our own named storage locations to hold the data.

They are called variables. This name means that we can change its value as often as we need in our scripts.

Each variable has its type. It can be of type integer, string, key and other types that we will see in future classes.

And each variables has its name, a name of our choice.

Same as functions, the name has to start with a letter, and after it, letters and numbers. Letters can be lowercase or uppercase. When we use our variable we will use exactly the same name, and the same lowercases and uppercases.

Variables look very similar to the parameters in the functions and events that we have been using.

A variable can only hold values of its type. Other programming languages have variables that can take any kind of value, but not LSL.

It helps to make the code easier to read and easier to check and optimize when it is saved.

As we said with functions, It's very important to choose a name that tells what data is the variable storing. This way, we will be able to read our scripts more easily.

Let's look again at the several formats of naming; all of these are good and used by scripters:

  • lastToucherId (our example)
  • kLastToucherId (the "k" shows that this is a variable of type "key")
  • LastToucherId (similar to our example, but the first letter in caps too)
  • last_toucher_id ("_" instead of caps to differenciate words, so caps are not needed)
  • k_last_toucher_id (with "_", and "k" at the start)

Please open the "Script 1 say previous toucher" that is in your class materials folder.

Let's look at what we have added to our script.

Our new variable is created in line 0.

It has the type "key", because we will use it to keep the UUID of the toucher.

I have chosen the name "lastToucherId". You can choose the name that you prefer.

And you can use names as long as you like. Names don't use script memory when the script is saved.

Variables, like functions, are placed before the keyword default {}.

We could place the variables first, or the functions first, or all of them mixed.

For clarity, it's better to place all the variables first. This way we will see all of them together at the top of the script.

Now that we have created a place to store the toucher, let's go to touch_start, lines 19-23, to see how we use it.

The say function, line 21, is saying two names, using our function getFullName() twice.

The second getFullName() is called with llDetectedKey(0), as before.

We call the first getFullName() with our variable lastToucherId.

But we haven't stored any value in lastToucherId yet. What happens?

The variables are created with an empty value. This value is "" for types string and key, and 0 for type integer.

We are sending the value "" to getFullName(). Our function uses "" to call llGetDisplayName() and llGetUsername() and both functions return "".

So getFullName() is returning "" + "(" + "" + ")". Which is that message starting with () that we get the first time that we touch the object.

After saying the touchers, it's time to save the current toucher for future uses. We will assign the current toucher, that is in llDetectedKey(0), to our variable lastToucherId.

To assign a value to a variable we use the equal sign: =.

The = is always assigning something to a variable. It's never comparing. To compare values we use another sign; we will see it later.

In line 22 we assign the current toucher to lastToucherId:

  • lastToucherId = llDetectedKey(0);

Next time that touch_start is triggered, lastToucherId will be ready with the previous toucher to be said.


Variables

-> Object 2 Variables (a yellow box) -> Script 2 variables

Please rezz the object "Object 2 Variables" from your class materials folder. It's a yellow box.

Now that we know how to use variables, we can make some improvements in the script to see more variables in action.

It also helps make the script easier to read. Instead of calling functions with functions as parameters, we use variables to write it step by step.

We have seen that we can add variables at the start of the script. We can add variables inside the functions too.

The variables that we create in a function can only be used inside the function itself. The variables created at the start of the script can be used anywhere in the script.

The variables in a function are created each time that the function is called, and destroyed each time that the function returns.

It might seem that we are losing much scripting running time doing it. We can call a function hundreds of times, and its variables are created and destroyed the same hundreds of times.

But this process is optimized when the script is saved, in a way that it doesnt take any time.

And we save on scripting memory. Only some variables exist at the same time. They take much less memory that all the variables existing at the same time.

It also helps the script readability. We have the variables only where we are using them.

Please open the "Script 2 variables".

There are changes adding variables to most of the functions.

Let's start with getFullName() in lines 2-14.

We have added 3 variables (lines 4-6): displayName, username and fullName. All are of type string.

We use displayName and username to store the names returned by the LSL functions (lines 8-9).

And we use fullName to store the names (line 11) to return (line 13).

Now to the event listen, lines 23-30.

There is the variable fullName (line 25), that we use to store the name (line 27) to say (line 29).

And finally the event touch_start, lines 32-45.

There are 3 variables (lines 34-36). A key for the current toucher and two strings for the names of the current toucher and the last toucher.

We assign their values in lines 38-40.

Now we can write a cleaner say function in line 42.

And assign toucherId to lastToucherId in line 44, instead of using llDetectedKey(0).

Now, in this function we are calling llDetectedKey(0) only once instead of twice.

Using variables we avoid repeated calls to LSL functions. To call an LSL function is much slower that getting a value from a variable, so we save scripting time.

And, why haven't we moved the variable lastToucherId into the event touch_start?

It would not work.

The variables in the event are destroyed each time that the code in the event finishes to execute.

If key lastToucherId is inside the event touch_start, then:

  • we assign toucherID to lastToucherId (line 44).
  • the event code finishes; the variables are destroyed.
  • on the next touch, the event is triggered again; the variables are created.
  • our variable lastToucherId, recently created, has a value of ""; we can't say the previous toucher name.


If

-> Object 3 If else (a green prism) -> Script 3 if else

Please rezz the object "Object 3 If Else" from your class materials folder. It's a green prism.

Let's go solve the problem with that empty previous toucher in the first time we touch.

Touch your prism and the prims around you to see that the first time it only says the current toucher.

The previous toucher name is only said if there is a previous toucher.

We can use the language keyword "if" to write code that will be only executed in some conditions.

The format is:

    if ( condition )
    {
        // code to execute if the condition is true
    }

In our script we want to add the previous toucher name when the variable lastToucherId is not empty.

The empty value is "", so we check if lastToucherId is different, or not equal, to "".

The LSL operator to do it is  !=

And the if that we use is:

    if ( lastToucherId != "" )
    {
        // code to execute is the condition is true
    }

Please open the "Script 3 if else".

The event touch_start is in lines 39-60.

We have added a string variable, messageToSay in line 44. We use this variable to build our message in different steps.

In lines 49-53 there is our if condition.

Lines 51 and 52 are only executed when lastToucherId is not equal to "". Otherwise the variable messageToSay stays empty.

In line 55 we always add the current toucher to messageToSay.


If else

Another improvement in the script is that when the display name and the username of a toucher are the same, we don't want it to repeat it twice.

For instance, in case that I had changed my display name to SuzannaLinn, saying "SuzannaLinn" instead of "SuzannaLinn (suzannalinn)".

Two strings are equal only when they have the same text in the same case. So "SuzannaLinn" is not equal to "suzannalinn".

The username is always in lowercase. We will change the display name to lower case before comparing tthem.

There is the LSL function llToLower() to do it. It expects a string and returns a string.

We are doing it in the function getFullName(), lines 2-21.

We add another variable, of type string, to store the display name in lowercase: displayNameLowerCase, in line 5.

We get its value in line 10:

  • displayNameLowerCase = llToLower(displayName);

The conditional part is in lines 13-18.

In the condition of the if we use the operator == to check if username is equal to displayNameLowerCase:

  • if ( username == displayNameLowerCase )

The double == is the operator to compare for equality.

Important!!!

  • Double == compares.
  • Single = assigns to a variable, never compares.

If we use = instead of == in the condition of an if, often we will not get an error when saving the script, but the script will behave very strangely in some cases.

It's a very common error, especially if you are used to other programming languages that haven't got the ==.

And usually it's an error difficult to find. If your script does unexpected things, remember to check your ==.

In line 15, if the username is equal to the display name, we assign only the display name to the variable fullName.

If they are not equal, we want to do as before, returning both names.

We could add another if, checking for not equal, like:

  • if ( username != displayNameLowerCase )

But there is another LSL keyword that does exactly this, the keyword "else".

The format is:

   if ( username == displayNameLowerCase )
   {
           // code if the condition is true
   }
   else
   {
           // code if the condition is false
   }

Both keywords, if and else, have their own block of code, enclosed in { and }.

One or the other of the blocks will be executed.

We have the else in line 16, and in line 17 we assign the names to the variable fullName, when the names are not equal.


Answers to questions

Is the previous toucher identified in the message from the current toucher?

  • No, we have to get the previous toucher in the previous message, and store the UUID of the previous toucher.

Is there an advantage to using one style of naming variables to another?

  • There is no advantage to using one style over another, choose the one that you feel more comfortable with, but always use the same style.

If we only use the "k" as a prefix for key values, what do we use for other types?

  • We use "s" for strings, "i" for integers, etc.

Could we define the key lastToucherId = " no one touched me"; ?

  • No, because it's a variable of type key, and it can only contain a key, a UUID.

Do we always need am event state_entry?

  • No, if we don't need to do anything in the state_entry, we don't have to write it as empty.
  • No event is mandatory, but we need to write at least one event, any event.

If we didn't have the state entry in this script, where would the llListen function go?

  • We can place the function llListen in any other event, except within the event listen itself
  • For instance, we could say a message: "touch me to start listening" and place the function llListen in the event touch_start.
  • Never place it in the event listen, if we don't start listening to a channel, the event listen will never be triggered.

Why can't be use variables that are inside another function or another event?

  • All the variables defined inside an event or a function, and also their parameters, can only be accessed from within that event or the function.
  • If we want to access a variable from multiple places, we need to define it as a global variable outside of any function or event.
  • If you use it only within an event or function and don't need to store the value between calls to the event or function then make it local, only use globals to store values that need to be kept between calls, among calls to different functions or events.

When an event finishes executing, does the variables that are created at that level count against memory?

  • No, when the event or function finishes, the variables disappear and stop using memory.
  • It's good to define as many variables as possible inside events or functions to save memory.
  • Local variables are stored on the stack when possible, while globals go in the heap. Stack references are always faster than heap references and are much easier for the memory manager to delete when they go out of scope and are no longer needed. This is true of nearly all languages, not just LSL.

In case of gothic names, they are shown with display name?

  • Yes, any special character is shown in the display name, gothic, chinese, arabic, symbols...

When an "if" doesn't have an "else", ff the condition specified isn't true, it just does nothing?

  • Yes, the "else" is optional, if the condition is false it jumps to the code after the closing brace of the "if".

Using the if/else if/else tree is the "LSL best practice?

  • In general using a chain of if...else if... else statemetns to check for different cases is good practice.

Do we always have to use return when we exit the function  ?

  • Yes, if we have defined the function to return a value.
  • If it is, for instance, a string function, we need to use "return" to provide a string before the function ends.


Wednesday class

Practice review

We are starting with a review practice. We have already done things like this ones.

This is the practice:

  • rezz two objects, give them different names.
  • add a script to each of them that says in a channel number of your choice (not the public chat) a message with the name of the toucher.
  • rezz a third object.
  • add a script to this object that listens to the same channel and says a message in public chat with the name of the object and the name of the toucher.

Let's go!

You have the solution in the class materials folder.

The 3 objects are: "the green listener", "the yellow sphere" and "the orange sphere".

It's one of the possible solutions, there are always different ways to script the same thing.


While

-> Object 4 While (a multicolor box) -> Script 4 while

In line 0, we define a variable of type "integer", with the name totalFaces.

This variable is global to all the script, we can use it everywhere.

We define it as global because we are going to use it in two events.

Let's look at how the colors are stored in LSL.

A color is defined with 3 values with decimals, between 0 and 1, for the Red, Green and Blue components of the color. To keep the three RGB values we use the type "vector".

Vectors are often used for the position of the object, for the values of the x, y and z coordinates. Also for the size of the object, the height, width and depth coordinates.

And since we also use 3 values to define colors , the scripting language uses the same type vector for the colors (instead of creating a type "color").

The values, between 0 and 1 with decimals, are of the type "float".

LSL has the type "integer" for numbers without decimals, and the type "float" for numbers with decimals.

Out of SL, usually we uses numbers from 0 to 255 as the RGB values. In this script we are saying both LSL and RGB values.

To convert to RGB we have written the function lsl2rbg, lines 2-4.

We define this function to return a "vector" (with the "vector" before the function name) and to take a parameter of tpye "vector" (the "vector" between the parenteses). We give to this parameter the name "color", to refer to it inside the function.

Multiplying a vector by a number, we get all the values in the vector (x, y and z) multiplied.

We could also use / to divide the values in the vector by a number.

And we return the result of the multiplication, which is a vector.

In state_entry, lines 8-11, we get the number of faces of the object and assign it to the global variable totalFaces, that we have defined in line 0.

We use the LL function llGetNumberOfSides(), that returns the number of sides or faces of the object where the script is in.

The script is in a box, and we know that a box has 6 faces. We could just write totalFaces = 6.

But it's important to write scripts that can be reused as much as possible.

By getting the number of sides, instead of using a fixed number, we can use this script, without any modification, in any shape of object.

Let's go to touch_start, in lines 13·32.

We start defining two local variables that we are using later in this event, in lines 15-16.

We say how many faces has the object in line 18.

In lnes 20-31, there is something new, that uses the "while" keyword in line 22.

We want to say the color of each of the face, 6 faces in this example.

We could write 6 lines with llOwnerSay() to say the color of each face, and it would work.

But in the future we will have to do things hundreds or thousands of times, so it wouldn't be a good solution.

We have a much better way to do it, using the loop "while".

The loop is done with the lines 20, 22 and 30.

What we do in each loop is in lines 24-28.

Let's start with the loop, lines 20, 22 and 30.

We have 6 faces in this case. The faces are numbered starting with 0. So we want to say the color of the faces 0, 1, 2, 3, 4 and 5.

In line 20 we set faceCount to 0. The face 0 is the first face that we want to say the color. The variable faceCount will have all the numbers of the faces, from 0 to 5.

In line 22 we compare faceCount (which now is 0) with totalFaces (that is 6 all the time).

The comparison facesCount < totalFaces is true, so the block of instructions enclosed between { and } in lines 23 and 31 is executed.

Inside this block, after saying the color of face 0, in line 31, we add 1 to faceCount, changing it to 1.

After finishing the block, the script jumps back to line 22, to the "while" keyword again.

Now faceCount is 1, It repeats the process for face 1, changes faceCount to 2 at the end of the block, and jumps to "while" again.

After saying the color of face 5, facesCount is changed to 6. And jumps back to "while".

Now faceCount (6) < totalFaces (6) is false and the "while" jumps from line 22 to line 32, after the block "while", finishing the loop.

Inside the loop, in line 24, we get the color of the face.

llGetColor() takes an integer as parameter, the number of face, and returns a vector, the color of the face.

In lines 26-28 we say the color.

This is only one function. We could write it in one line. We are writing it in 3 lines for clarity.

We can add white space and newlines as we like, for the script until the ; is only one line.


While in touch start

-> Object 5 While in touch start(a pink torus) -> Script 5 while in touch start

Now that we know how to do a loop, we can use that parameter total_number in the event touch_start.

total_number has the total of touches received at the same time, or nearly at the same time.

Nearly always is 1, only one touch. But if two people touch the object at the same time it could 2, or more.

We use a loop while in lines 34-44 to look at all the touchers.

And we use touchCount in llDetectedKey() to get the UUID of each toucher.


Practice

Now a practice a bit more difficult.

This is the practice:

  • start with the Script 3 if else, from Monday.
  • change the script to say, not only the previous toucher, but the two previous touchers.
  • when a toucher is the owner, instead of the name, say "the owner" or any other message. Use the function llGetOwner(), without parameters, that returns a key with the UUID of the owner.
  • any other idea that comes to your mind. Creativity is great!

Let's go!


Answers to Questions

Could we have assigned totalFaces when we defined it, instead of in state_entry?

  • No, we can only assign fixed values to global variables when we define them, not results from functions or operacions.
  • This is a limitation of the scripting language, for example, we could write integer totalFaces = 6;, but we can't set it equal to a function.

Could we use the script with other prims?

  • Yes, this script can be used with any prim, regardless of the number of faces.

What is the advantage of assigning totalFaces in state_entry compared to elsewhere?

  • There are two ways to do it, both are good.
    • We are only using the variable in touch_start, so we can define it in touch_start, alongside faceColor and facesCount, like:
      • integer totalFaces = llGetNumberOfSides();
    • Using a local variable inside an event or a function, unlike with global variables, allows us to assign it the value of a function or operation.
    • The problem with this is that we are calling llGetNumberOfSides each time the object is touched, and calling ll functions is much slower than accessing variable.
  • So we use a faster option in this example
    • We define totalFaces as global at the start of the script, and since we can't call the function at that moment, we assign the value in state_entry, that it's only called after a script reset.
    • The main advantage is that state_entry only runs when the script is reset or a state change occurs. So assigning constant values is best done there so you don't redo calculations unnecessarily.
    • But the problem here is that totalFaces is using script memory all the time,
  • So none of both solutions is perfect.
  • Neither option is perfect, so we choose based on whether we want to prioritize speed or memory conservation.

Does the script store that value the whole time the object is rezzed, and lose it when the object is taken, or does it continue to store it in inventory if the object is taken?

  • It stores the value all the time and it is not lost when the object is taken into inventory.
    • The value remains and will still be there when the object is rezzed again.
    • The value its only lost if the script is reset, or modified and saved.
    • or, like in this example, if we reset the script when the object is rezzed, using the event on_rez at the end.
    • After reset, SL calls the event state_entry immediately and we set the value again.
  • Scripts in objects taken into inventory and rezzed again resume where they left off after being rezzed. The only difference is that after being rezzed, the on_rez event will be triggered.


Saturday class

Practice solution

-> Object 6 Say two previous touchers (a blue cylinder) -> Script Object 6 say two previous touchers

Let's look at the solution of the practice of Wednesday, a script that, when touched says the current toucher and the two previous ones.

Please, rezz the Object 6 Say two previous touchers.

We have added several lines to the scripts.

We need a variable to store the id of the second previous toucher. We add it in line 1 with the name lastToucherId2.

In the event touch_start, lines 36-40, we check is there is a toucher in lastToucherId2 to add the name to the message to say.

There is a change in line 45, now we are adding the name of the first previous toucher to the variable message, instead of setting the variable to the name, because it could be a message in the variable already.

And we have added line 52, copying the id of the first previous toucher to the variable for the second previous toucher, before copying the current toucher to the first previous toucher.


For

-> Object 7 For (a multicolor box) -> Script Object 7 for

On Wednesday we studied the loop while. Today we are going to look to another loop, the loop for.

Please, rezz the Object 7 For.

The loop for exists in most of the programming languages, and in each language it works in a different way.

In LSL the "for" is a kind of "packed while", where we can place 3 lines of code in only 1.

Let's look at the "Script 7 for". It's the same than the "Script 4 while" that we studied on Wednesday, but now it has both loops, while and for, doing the same.

The "for" condition, between parentheses, in line 38, has 3 parts, separated by semicolons.

  • 1) The initializer. It runs only once, at the start. The same than in line 20 for the "while".
  • 2) The condition. It runs in each loop, before running the code. If the condition is false, the code is not executed and it jumps to the next line after the closing "}". The same condition than in the "while" in line 22.
  • 3) The increment (or decrement). It runs at the end of each loop. The same than in line 30 for the "while".

We can use any of both loops, the one that we like, it's a matter of preference.

In general, if we are counting from a starting value to another value that we know in advance, the loop for is more often used.

If our loop is not in that style "from 0 to 9", the "while" is the one more used.

Unlike other languages, in LSL we can't declare a variable inside the "for", we can't do this:

  • // WRONG! for ( integer facesCount = 0; ... // WRONG!

We need to declare the variable previously.


For in touch start

-> Object 8 For in touch start (a purple torus) -> Script Object 8 for in touch start

We have also added the loop for to the "Script while in touch_start".

Please, rezz the Object 8 For in touch_start.

The same than in the other script, we have joined the lines 34, 36 and 43 into the line 48.


Organizing inventory

And now a non-scripting topic, but that can help to write scripts better.

How to organize the class materials and notes, and your scripting projects, in your inventory?

Let's begin with the classes.

All class folders start with "SUZ0" and a number "01", "02",... for each week. The "0" in "SUZ0" is the series of classes.

So you can search your inventory for "SUZ0" and all the classes of this series of scripting will appear, and probably not much more.

The problem, perhaps, is that you have several folders that start with "SUZ001".

The contents of the classes in the week all have different names. The idea is that you move all contents to the first "SUZ001" folder and delete the other, now empty, folders.

And that you add a subfolder "practices" inside "SUZ001" to keep your practices related to the class.

Also you can add a folder "SUZ0 - scripting classes", and move all the classes inside it.

Now ideas on how to organize your projects.

Open a folder "My projects" to place all your projects inside it.

Add a subfolder for each project, with a descriptive name. Use it for one and only one project.

If you are using several textures, or animations, etc., open a subfolder in your project to store them.

An important thing is to control the versions.

Open a subfolder in your project every time that you start with a new versions, or you want to make a copy before doing important changes.

Use the date in the name of the version subfolder. This way, if you remember that "I was doing it about 6 months ago", will be easier to find.

Also in the name use the status of the version, like "finished", "in progress", "wrong way"...

And copy everything into the folder, not only the object that you are working on, but also all the other objects, notecards, textures, etc.

Sometimes it happens that we are improving a project, but at some moment we discover that how we are doing it is not the best way, and we decide to start again from the previous copy.

Don't delete it. Make a copy, adding "wrong way" to the subfolder. Then delete it from the main folder and recover the previous copy.

When we re-think on how to improve the project, we could find out that a part of the previous work is useful, or can be used with small changes.

When a project is finished, copy the scripts in the objects to the folder (the same that in the class materials). This way, when you want to copy-paste code for another project, it will be faster.

In your "My projects" folder, and a subfolder "My library".

There, all in the same folder, without subfolders, add the parts of code that you are going to re-use.

For instance, a script that reads a notecard, and does nothing else.

When you start a project that reads notecards, you can start with that script.

Good scripters are lazy, we are not going to write the same code twice :)


Answers to questions

Can we delete a function from the script after we have used it?

  • No, no way to edit or delete from the script using LSL code, only manually.
  • The only way would be to put the function in another script and then delete that script after using the function.

Is facesCount=facesCount+1; the same as facesCount++ ?

  • Yes, the operator ++ increments the value by one, it's the same than adding one.
  • There is also the operator -- that decreases the value by one-

Does facesCount+=1 works too?

  • Yes, the operator += adds the value on the right side of the = to the variable, and -= subtract the value on the right side.
  • The ++ and -- operators are more efficient than the = +1 operation, both memory use and execution time.

Is ++var correct?

  • Yes, there are both pre and post incrrement/decrement operation in LSL. ++Var increments Var and then uses in the expression, while Var++ uses the original value in the expression and then increments it for later use.
  • Before the variable, it increases the value of the variable before using this value
    • with a = 5; b = ++a; b will be 6, , b gets the value from a after a is incremented
    • b= ++a is the same than a = a + 1; b = a;
  • After the variable, the value of the variable is used, and after it is incremented
    • with a = 5; b = a++; b will be 5, b gets the value from a before a is incremented
    • b= a++ is the same than b = a; a = a + 1;
  • In the case of using it to increment the ocunter in a for loop, it doesn't matter which you use because the variable isn't used in a expression, only incremented.

What is the function lsl2rgb in the script?

  • The function lsl2rgb in lines 2-4 is converting a vector with color values as used in LSL, values from 0 to 1, to the usual RGB values from 0 to 255.
  • We use the function in the loops to say the values in both formats.
  • It's a user function, written by us.

Does it round the values ? Do we have float values in the vector?

  • No, the function doesn't round the values.
  • Yes, the values in a vector are floats, numbers with decimals.

Are they values between 0.0 and 1.0 ?

  • Yes, the values of the colors are between 0.0 and 1.0, in LSL.

Why is totalFaces initialized in state_entry while it is only used in touch_start?

  • state_entry is only called once, so we only call the function once.
  • In touch_startt we would be calling the function for each touch.
  • Using a function is much slower than using a variable

Can we write a script that says which face has been touched, and tell the color of the touched face?

  • Yes, there is a function that says the face touched:
    • llDetectedTouchFace()
  • For instance, like this in the event touch_start:
    • integer faceTouched = llDetectedTouchFace( 0 );
    • vector faceColor = llGetColor( faceTouched );
    • llSay( 0, "Face " + (string)faceTouched + " has been touched. Its color is: "+ (string)faceColor );

Is it possible to write the two lines integer facesCount; and facesCount=0; in one line instead: integer facesCount=0; ? Can we can set values to variables when declaring them?

  • Yes, we can use integer facesCount=0;
  • We can set a value when declaring a variable, with no restrictions when the variable is inside a function or event.
  • When the variable is global, we can only set it to a number or a string, not to a function, and not to the result of an operation.

Can we call a function in the condition of a loop for?

  • Yes, we can do this: for (facesCount=0; facesCount < llGetNumberofSides(); facesCount = facesCount+1).
  • But calling the function in each loop is very slow.
  • It's much better to use a variable to store the value of the function and use the variable in the loop for.

Can we use for (i=0; i<totalFaces; i=i+1i without declaring i before?

  • No, all variables must be declared, we need to add a line integer i; before the "for"

Can we use two conditions in the loop for?

  • Yes, we can use condition1 && condition2 in the condition part of the "for".

Can a for loop count down ?

  • Yes, instead of toucherCount = toucherCount - 1 we can write countDown = countDown -1.
  • Counting down in a "for" loop is much less often used than counting up, but it's not uncommon.