Second Life Scripting – The Elevator Script

7 minute read

One of the most important aspects of Second Life is the fact that every prim (building block object) can contain executable code — script — which can control the appearance and behavior of the prim, communicate with avatars and with other prim, respond to events, and so forth. The scripting language is called LSL (Linden Scripting Language) and is documented in the LSL Wiki.

In this post I am going to document a script that I call the “Elevator Script.” I built this script as part of an ongoing series of experiments that I’m conducting into presentation styles within Second Life. I assume that you have some familiarity with a modern scripting language like Perl or PHP. I won’t be spending any time discussing language syntax or fundamentals.

I used GraphicsEx to take a tall picture of the Amazon Web Services Blog. I used The Gimp to scale the down so that it was exactly 512 pixels wide. Next I created a series of square images, each 512 by 512 pixels in size. I uploaded the images to Second Life, and created a tall stack of square prims. I then textured each prim with the appropriate blog image; the result was a tall image of the entire blog, as you can see at the right.

The script runs inside of a chair prim — a simple grey square which you can see at the base of the blog to the right. When an avatar sits on the chair, the script is activated. The chair rises, step by step, until it reaches the top of the blog, then it reverses course. When it gets to the bottom, it instructs the avatar to stand up, and it remains at rest until another avatar sits down.

LSL includes a unique state-machine model which can simplify and clarify otherwise complex scripts. The Elevator Script has four states:

  • default – Every LSL script starts out in the default state.
  • idle – The chair is in this state when it is waiting for an avatar to sit on it.
  • going_up – The chair is in this state when it is ascending.
  • going_down – The chair is in this state when it is descending.

Here’s an outline of the script. LSL makes it easy to model states, by enclosing the code for each state in a distinct block like this: <div class="lsl"> default { }</p> <p> state idle { } </p>

state going_up { }

state going_down { } </div>

We’ll start with a couple of definitions:

integer MIN_HEIGHT = 2; integer MAX_HEIGHT = 15;

These values simply set the minimum and maximum height (in meters) of the chair with respect to the ground. Before I go any further I should note that you need to be extremely careful when writing scripts that have the ability to move prims. If you write a script that moves the prim up by one meter every second, it will happily move up, up and away. If you can’t catch it and stop the script, you have effectively lost the object! Second Life is a take no prisoners, real time world — there’s no undo, and once that object is out of sight you are going to have a really hard time finding it again. Trust me on this one!

Next up is a simple function to set the prim’s text label. The given string floats above the prim. In the code below, the value “<0, 1, 0>” is an RGB color vector. This value says that I want no red, full green, and no blue. The third parameter, 1.0, specifies that the text should not have any transparency.

Text(string Message) { llSetText(Message, <0, 1, 0>, 1.0); }

The implementation of the default state is pretty simple:

default { state_entry() { llSitTarget(<0.4,0.0,.35>, ZERO_ROTATION); llSetTimerEvent(2); state idle; } }

The state_entry is like a special form of function declaration. It says that the code inside is to be run each time the state is entered. llSitTarget fine-tunes the seating properties of the chair so that the avatar appears to be resting immediately on top of the chair rather than “in” the chair. llSetTimerEvent arranges for the timer handler in the current state (whatever it happens to be) to be run every 2 seconds. Finally, the state is changed to the idle state.

Here’s the code for the idle state:

state idle { state_entry() { Text(“Sit here to read the AWS Blog”);</p>
    vector pos = llGetPos();
    float GroundHeight = llGround(<0, 0, 0>);

    pos.z = GroundHeight + MIN_HEIGHT;

changed(integer Change)
    if (Change & CHANGED_LINK)
        if (llAvatarOnSitTarget() != NULL_KEY)
            Text("Welcome aboard!");
            state going_up;

} </div>

The state_entry code is run when the state is set to idle. The first call is to the Text function, to display an instructional message above the chair. llGetPos is used to get the chair’s current position, and llGround is used to get the height of the ground immediately beneath the chair. MIN_HEIGHT is added to this value, and the chair’s height (z) is set. In effect this code leaves the chair’s x and y position unchanged and sets the chair’s height to be 2 meters above the ground.

The changed code is run when an avatar sits on the chair. The call to llAvatarOnSitTarget ensures that there’s really an avatar sitting on the chair. Assuming that this is true, the message is changed to “Welcome aboard!” and the script pauses for 3 seconds. The state is then set to going_up.

Oh yeah, one last thing. Remember that timer we set in the idle state? It is going off every 2 seconds, but since there’s no timer function in the idle state, nothing happens. A more sophisticated script could (and probably should) use the timer only while the elevator is actually moving.

Here’s the code for the going_up state:

state going_up { state_entry() { Text(“Going up…”); } timer() { vector pos = llGetPos(); vector motion = <0, 0, 2>; pos += motion; float GroundHeight = llGround(<0, 0, 0>); if (pos.z < = (GroundHeight + MAX_HEIGHT)) { llSetPos(pos); } else { state going_down; } } changed(integer Change) { if (Change & CHANGED_LINK) { if (llAvatarOnSitTarget() == NULL_KEY) { Text("Thanks for watching!"); llSleep(3); state idle; } } } }

Entering the state is really simple — change the message and that’s it.

Now, back to that timer. The chair wants to move now, so each time the timer event comes in, the event handler gets the old position, adds the vector value <0, 0, 2> (move up 2 meters) to the position, and then checks to see if the maximum height has been reached. If the height has not been reached, the new position is set using llSetPos. If the maximum height has been reached, the state is set to going_down, and on the next tick of the timer the event handler in the going_down state will be activated.

The handler for the changed event detects the departure of the avatar. If the avatar stands up while the chair is in motion, the text label is changed, and after a short pause the chair returns to the idle state. </div>

The code for the going_down state is similar:

state going_down { state_entry() { Text(“Going down…”); } timer() { vector pos = llGetPos(); vector motion = <0, 0, -2>; pos += motion; float GroundHeight = llGround(<0, 0, 0>); if (pos.z >= (GroundHeight + MIN_HEIGHT)) { llSetPos(pos); } else { llUnSit(llAvatarOnSitTarget()); state idle; } llSetPos(pos); } changed(integer Change) { if (Change & CHANGED_LINK) { if (llAvatarOnSitTarget() == NULL_KEY) { Text(“Thanks for watching!”); llSleep(3); state idle; } } } }

There’s one important difference — when the chair reaches the lower extreme of its motion, it unceremoniously boots the avatar off of the chair (they will return to the standing position) using llUnsit and enters the idle state.

Ok, does this make any sense? I hope that you can see how the states in the script effectively mirror the actual states of the elevator. LSL is really pretty easy to use, and because it it has the ability to control and to move prims, the scripts can generate interesting effects with just a modicum of code.

One more thing — if you want to see the elevator in action, hop on over to Athabasca. Walk through the building and go up to the third floor, and you’ll see the elevator. Let me know what you think!