User:SuzannaLinn Resident/ScriptingClasses/Basics 2

From Second Life Wiki
Jump to navigation Jump to search

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.