This work was originally done for the Kan-ed network within the Kansas Board of Regents, but is now being maintained by the author.
In addition, the LSL Wiki homepage links to many LSL features, including a list of LSL functions by category that will probably be helpful as you read this tutorial, since almost every section introduces at least one new LSL function.
Caveat: This page covers neither vehicles nor collisions, and is still under development.
Left-click again on the ground. You will then hear a
mysterious rumble and an object will appear. It will be some
3-dimensional geometric figure, such as a cube, cone or
sphere. The colored lines emanating from that object are used
for making certain positional changes in the object.
Return to the object manipulation menu, and if you see a
"More>>" button, press it once, until the extended
version of the menu opens.
Within the extended menu, left-click on the "Object" tab,
and you will see options for changing the overall shape and
dimensions of the currently selected object.
Left-click on the "Content" tab and you will see a list of
scripts associated with the object. The "Contents"
directory displayed within the "Content" tab will most likely
be empty.
Left-click on the "New Script" button, and a "Script"
editing window will open. The editing window has a "script
editing area" where you can type or paste in a new script, and
buttons for saving the script, undoing changes made, etc. It
will look something like this:
The script editing area should contain the following
script:
Left-click on the "Save" button. You should see the message
"Compile successful, saving..." appear in the box below the
script editing area. After a short pause you should see the
message "Save complete." appear in that box as well.
Once the script is saved, you will see the message "Hello,
Avatar!" appear in the lower-left-hand corner of your screen.
This message indicates that the script has started and waiting
for touch events.
To test the object's responsiveness to "touch," close both
the script editing and object manipulation windows, and
right-click on the object. The circular object menu will
appear from which you can select "Touch." You should then see
"Object: Touched." appear in the lower-left-hand corner as
well.
Note that your boxes will have different colors and sizes,
and that this program also counts the number of times the
object has been touched, and writes a message in the
lower-left-hand corner of the screen giving that number.
To insert this script into the object just created, right
click on the object, reopen the Script editing window, paste
this script into the Script editing area, and click "Save".
When the script has been saved, test it by right-clicking on
the object and choosing "Touch," as before.
You may have to do some debugging to correct errors, and
when you finally get things running correctly, you may want to
right-click on the object and choose "Take" to move the object
into your Inventory. Objects in your inventory will remain
there even after you log off, and will be available when you
log back in. Later, you may move copies of these
objects back into SecondLife, and reuse them to avoid
recreating them.
When a touch_start event occurs, the program starts a timer
that "runs out" every 2 seconds, causing a "timer event."
Whenever a timer event occurs, the script segment within the
brackets below the string "timer()" is executed. The
timer() section counts the number of times it is activated,
and resets the script after 20 or so activations.
If multiple avatars touch the object at the same time,
strange behavior may occur. Such an event may be monitored and
controlled by using the total_number variable passed
to touch_start.
The new positions are chosen by incrementing the starting
position using a vector constructed during each iteration from
3 random values between 0.0 and 10.0 meters.
To help you keep track of the object, it is repositioned to
the starting position before the script reinitializes.
The motion generated by random repositioning is, of course,
quite eratic. (One might even call it "random.") To cause an
object to move more "naturally" one must choose subsequent
positions more carefully. New positions must lie along a
movement track, and be sufficiently close to one another that
jerkiness is either not apparent or minimized. This is
sometimes difficult in SecondLife due to network delays,
server congestion, and/or poor client CPU or graphics card
performance.
A 3 GHz CPU paired with a high-end 3D graphics card, such
as the nVidia 6800 Ultra, will usually deliver very good
performance operating over a network connection with a 50ms
roundtrip ping time to a SL server. A 3 GHz CPU hosting an
nVidia Quadro NVS (designed primarily for 2D graphics) and
operating with a 200ms ping time provides only moderately
satisfactory results.
To see an example attempting to portray smooth object
movement under script control, look at this script
to move an object in a contained volume.
To see this in action, you can modify the script in
previous section to emit particles while the object is moving.
To do that insert a call to llParticleSystem() in
touch_event() and another call to turn it off when the object
has been returned to its starting position and the script is
about to be reset.
The first call should like this one from the Wiki:
The unusual layout shown in the llParticle System() call
above is meant to emphasize how parameters in the call to
llParticleSystem() are sent as a collection of element pairs
within an LSL list. Each line in the parameter list contains
two elements separated by a single comma. The first element is
the parameter name and the second element is the value to be
associated with that parameter. For example, the particle
color is controlled by the parameter
PSYS_PART_START_COLOR, and the value sent is <
1, 0, 0 >, which specifies the color "red".
Some parameters can take multiple settings at the same
time. For example, the parameter-value pair The Wiki
page for llParticleSystem() lists all the accessible
parameters. There are many parameters and some have difficult
names, but it is fairly easy to experiment with them to find
out how they work, since they usually have directly observable
effects.
Note that particles are generated locally on each client
within range. That means they do not put much load on the
server and can be quite realistic.
Quaternions are implemented within LSL via a built-in
variable type, called a "rotation," which is composed of 4
components, x, y, z, and s, all of type float. The first 3
components may be thought of as defining an axis around which
rotation occurs, while the 4th describes the amount of
rotation.
A rotation can be defined using an Euler representation and
then converted to quaternion representation via a call to
llEuler2Rot(). For example, < 0, 10, 0 > defines a
rotation of 10 degrees around the Y axis, and can be converted
to quaternion form using the call When the rotation describing the "current orientation" of
an object is known, a new orientation can be derived simply by
multiplying one rotation by another.
Here is an example script that illustrates the use of a for
loop to simulate continuous rotation of an object in one
place. It uses llGetRot() to acquire the rotation describing
the current orientation, modifies the returned value and then
uses llSetRot() to reorient the object. If you right-click on the object while it is rotating, the
object axes will appear, and you can verify it is rotating
around its Y-axis.
Note that operand order is significant when multiplying by
variables of type rotation. The statement:
Here is an example that rotates an object around both the Y
and Z axes (as specified using < 0, 1, 1 >) at a rate of
.2 * Pi radians per second:
This script makes use of the effect of multiplying a
position vector by a rotation. The Wiki
page describing this operation says:
The orbiting motion is simulated via a for loop that
repositions the object by 15 degrees during each iteration.
Note that llSetPos() is used in place of llSetRot() during
this process. Here is a snapshot of the author riding a large orange cone
rotating under control of a hybrid version of the script above
using code from both of the rotation examples just
presented. The cone rotates around the Y-axis while
simultaneously orbiting the distant center point.
And here is what the experience looks like from the
author's point of view just as the snapshot above was taken:
Note that it is also possible to combine this orbit motion
with local rotation produced by using llSetTargetOmega(), as
shown in
this
script. This is accomplished by starting local rotation
along with a timer that periodically executes instructions to
move the rotating object in an orbit. This approach can be
used to simulate lunar movement around an orbiting planet (if
the orbiting object is two linked spheres). Note, however,
that the planet will rotate at the same rate as the moon,
which is not realistic.
To see "physics" in action, create an object, open the
Object tab, check the "Physics" box, change the Z-axis height
of your object to make it several meters above the ground, and
then close the Object manipulation window. Your object should
then drop to the ground, and if the ground is not level and
your object is sufficiently spherical, it may even roll
downhill. (In fact, you may have to chase it to regain control
of it.)
The following script sets physics "on" using the function
call Since the weight of an object is equal to the mass of the
object times the force of gravity, or 9.8 meters per second
per second, a force greater than 9.8 * objMass should overcome
the force of gravity and raise the object. In this script,
Z_force is set to "10.2 * objMass", which should
raise the object slowly. You can edit the script to change
this parameter and observe different object behaviors.
Note that it can be difficult to control objects in motion
in the Second Life environment. For example, avatars
apparently lose control of objects more than 10 meters away,
and objects set in motion may continue across many lands
before they eventually leave the simulated space. (When they
do leave, they are usually returned to the "lost and found"
area of your Inventory.)
The script above has two features to help maintain control.
First, it uses a timer event, as illustrated in a previous
program, to monitor the position of the object, and to take
corrective action. In particular, if the object moves more
than 30 meters from its starting position, the timer event
script eliminates applied force by using the function call
llSetForce( < 0, 0, 0 >, FALSE ).
Second, the script stops the simulation and returns the
object to its starting position after a fixed number of timer
events.
Note that this script deliberately manipulates object
susceptibility to physical forces. In particular, physics is
turned "off" while the object is repositioned to its starting
location using script functions that won't work when physics
is enabled.
llListen() is used to set up an event trigger, so the
"listen" event will be invoked whenever a chat message is
received from the owner. The listen event receives the content
of the message in its "message" argument, and
"parses" that message into its blank-delimited parts. The
first part is taken as a "command" and the second part as a
"distance", as described above.
Note that this technique can also be used by objects to
communicate with one another, if they are close enough to one
another.
Whenever the object is touched it will ask the user to move
it up or down.
Note that a dialog box can include an option to request
"Additional options" which will appear on a cascaded Dialog
box.
Note that this script takes over both the up- and
down-arrow keys, as well as the forward and backward control
buttons provided by "View/Movement controls".
To work with this feature, create an object, give it the
name "Object1", and allow it to be copied by selecting "Allow
anyone to copy" and "Next owner can copy" within the Object
manipulation window.
Then right-click on the object and "Take" it into your
avatar's inventory.
Now create a second object with the name "Object2", and
open its Object manipulation window "Content" tab. Then open
your avatar's inventory by clicking "Inventory" at the bottom
of your SecondLife window, open the "Objects" folder,
left-click on the "Object1" entry, and drag it to the Object2
"Contents" folder area.
Now edit the following script into Object2:
Now when you touch Object2, llRezObject() will instantiate
Object1, and you should see a copy of Object1 appear 2 meters
above Object2.
The last parameter in the llRezObject() call ("42" in this
example) is passed to the new object. It is presented as the
single parameter to the on_rez event in the new object. An
"intelligent" version of Object1 could use such a parameter to
make immediate changes (such as color or size) under its
control.
llRezObject() triggers the object_rez event and passes the
new object's key along for subsequent use within Object2.
Commands within object_rez can then link the newly created
object and/or send messages to the new object, etc. to affect
its appearance or behavior.
Here is a version of the script above that asks for
permission to link other objects into a linked set and creates
two child objects only if it receives permission to link them
once they have been created.
Once linkage is established, the script then uses
llSetLinkColor() to make the newly created objects red. The
newly created and linked objects are identified using link
numbers "2" and "3" during message exchanges within the
linkset, but they may also be addressed via LSL constants like
"LINK_ALL_CHILDREN" or "LINK_SET".
The parent object is identified as link number "1" and may
be addressed as "LINK_ROOT" (although primitive objects
without linked children are identified as link number "0").
The object_rez event runs only when a rez succeeds, at
which point the new object is linked in and the script checks
to see if all objects have been successfully created. If so,
the child objects are ordered to change their color, and one
is made semi-transparent.
It is here that additional commands could be added to
direct subsequent behavior of the linkset. For example, the
entire set could be made to rotate by inserting portions of
the rotation script presented earlier. Alternatively, this
script could change control to a different script state (by
using the "state" command) that would continue to direct
linkset behavior.
Members of a linkset can send messages to other members of
the linkset by using the llMessageLinked() function. To
receive messages, member objects must have a
link_message_event event handler defined within their scripts.
Note that objects can only be created within 10 meters
distance of the object containing the creating script.
Also, while objects can be created from within loops
(although there is a short delay with each instantiation), you
should be very careful about interative or recursive
object creation to avoid system catastrophes.
The following script will produce a similar flame when
applied to an appropriate object: During the upload process you will see that the texture
file is composed of 16 slightly different flame images laid
out in a 4 by 4 grid, as shown below:
The script calls llSetStatus() to make the object a
"phantom" and then specifies the texture for
llSetTextureAnimation() to use with a call to llSetTexture().
Finally, llSetTextureAnimation() is called to loop through
each of the 16 flame images. The third and fourth parameters
in the parameter list (namely, "4, 4") specify the 4 by 4
grid. The fifth and sixth parameters (namely, "0, 0") specify
the starting frame and the number of frames to include in the
loop, where "0" in parameter 5 specifies the first frame, and
where "0" in parameter 6 specifies "all frames." The last
parameter specifies the number of frames to display per
second.
Note that this image will be almost 2-dimensional. To
create a more convincing illusion, animate the sides of a
composite object made from two flat panels intersecting
orthogonally at their vertical midlines. To do that, create
the two panels, position them to intersect, and link them
together by using the "Link" option under the "Tools" menu
option (or simply Control-l). (It would also be possible to
rez and link the second panel for this purpose using the
technique presented earlier.)
Note that llSetTextureAnim() executes locally, so that it
will appear slightly different on different SecondLife
clients. Note also that vertical yellow lines will sometimes
be seen next to the flame. These are actually the very narrow
sides of the cube glowing with the flame image. (Perhaps
careful specification of object sides to be used during
animation could prevent these artifacts.)
Sensors allow objects to scan their environment to
establish the presence of other objects or agents in their
vicinity. Sensors are invoked by using the llSensor() or
llSensorRepeat() functions to scan selected portions of the
environment. llSensor() makes a single scan, while
llSensorRepeat() can be set up to scan the environment at
regular intervals. Note that an object may have only one
active sensor at a time.
When a sensor conducts a scan, it induces the "sensor"
event which can process the results of the scan. Within the
script to process the sensor event, a set of detection
functions can be used to acquire information about sensed
objects.
Here is a script that uses a repeating sensor scan to
identify the location of its owner, and rotates its object
around its owner.
Now create an object suitable for rezzing as a "bullet".
Once again, a default sphere will do, but you may choose a
more (or less) realistic object. Put the bullet script into
the newly created bullet object, "Save" it, and then "Take" it
object into your Inventory.
When the bullet object is in your inventory, you can move a
copy to the gun object's Contents collection, to "load" your
gun. "Attach" the gun to your Right Hand, enter
"Mouselook" and every time you left-click, you will "fire" a
bullet.
Note that both gun and bullet have "physics". Each shot
will cause a recoil, and bullets will fall under the influence
of gravity. Using a slow muzzle velocity allows you to observe
these effects more easily.
To use the floater script, move it to an object and "Save"
it as usual. When the Save is complete, right-click the
object, and select "Get in". You will see the "Stand up"
button appear at the bottom of your screen. When you are
safely seated, right-click and choose "Touch". The script will
take over your keyboard and you will see the "Release Keys"
and "Mouselook" buttons appear at the bottom of your screen,
as well as a list of control commands in the Chat area. You
may also type "menu" to ask the script to repeat the menu of
control commands at any time. (Note that "Touch" will toggle
script control over your keyboard.)
The floater and cross-leg-sit scripts can be used together
to allow your avatar to sit cross-legged on the object being
driven. To use these scripts together just put both of them
separately into an object's Content collection. Aside from the
fact that your avatar will sit in a more aestheically pleasing
position, the floater script will work the same.
Please send suggestions for improvement and report problems
and successes encountered using this document to Michael Grobe
at grobe@ku.edu.
Bon voyage!
First version: August 5, 2005
Running LSL scripts
SecondLife includes a facility for
defining scripts within existing objects. To create an object,
start SecondLife, log in, right-click on virtual ground or
water and left-click "Create" on the circular menu that
appears. When you do this, an "object manipulation menu" will
open on the left side of your screen and your cursor will
change to a "magic wand" when you hover over the ground again.
default
{
state_entry()
{
llSay( 0, "Hello, Avatar!");
}
touch_start(integer total_number)
{
llSay( 0, "Touched.");
}
}
Entering and running a simple script
Here is a very
simple program that changes the color and size of the object
every time the object is touched.
integer counter;
default
{
state_entry()
{
llSay( 0, "Hello, Avatar! Touch to change color and size.");
counter = 0;
}
touch_start(integer total_number)
{ // do these instructions when the object is touched.
counter = counter + 1;
// choose three random RGB color components between 0. and 1.0.
float redness = llFrand( 1.0 );
float greenness = llFrand( 1.0 );
float blueness = llFrand( 1.0 );
// combine color components into a vector and use that vector
// to set object color.
vector prim_color = < redness, greenness, blueness >;
llSetColor( prim_color, ALL_SIDES ); // set object color to new color.
// choose a random number between 0. and 10. for use as a scale factor.
float new_scale = llFrand(10.0) + 1.0;
llSetScale(< new_scale, new_scale, new_scale > ); // set object scale.
llSay( 0, "Touched by angel number " + (string)counter);
}
}
Here is a snapshot showing the author next to one of
the boxes created by this program.
Changing an object's appearance over time
Here is a
script that changes the color and size of an object every two
seconds for about 40 seconds beginning when the object is
touched.
integer counter;
integer second;
default
{
state_entry()
{
llSay( 0, "Hello, Avatar! Touch to change color and size.");
counter = 0;
}
touch_start(integer total_number)
{
counter = counter + 1;
llSay( 0, "Touched by angel number " + (string)counter);
llSetTimerEvent( 2 ); // create a "timer event" every 2 seconds.
}
timer() // do these instructions every time the timer event occurs.
{
second++;
// choose three random RGB color components between 0. and 1.0.
float red = llFrand( 1.0 );
float green = llFrand( 1.0 );
float blue = llFrand( 1.0 );
// combine color components into a vector and use that vector
// to set object color.
vector prim_color = < red, green, blue >;
llSetColor( prim_color, ALL_SIDES ); // set object color to new color.
// a choose random number between 0. and 10 for use as a scale factor.
float new_scale = llFrand( 10.0 );
llSetScale(< new_scale, new_scale, new_scale > ); // set object scale.
if ( second > 19 ) // then time to wrap this up.
{
// turn object black, print "resting" message, and reset object....
llSetColor( < 0, 0, 0 >, ALL_SIDES );
llSay( 0, "Object now resting and resetting script." );
llResetScript(); // return object to ready state.
}
}
}
Changing an object's position over time
A script may
change the position of its object by using the llSetPos()
function. Here is a script that repositions an object using
llSetPos() about once a second for 20 or so seconds.
integer counter;
integer second;
vector startPosition;
default
{
state_entry()
{
llSay( 0, "Hello, Avatar! Touch to change position.");
counter = 0;
startPosition = llGetPos();
}
touch_start(integer total_number)
{
counter = counter + 1;
llSay( 0, "Touched by angel number " + (string)counter);
llSetTimerEvent( 1 ); // arrange for a "timer event" every second.
}
timer() // do these instructions every time the timer event occurs.
{
second++;
// choose three random distances between 0. and 10.0.
float X_distance = llFrand( 10.0 );
float Y_distance = llFrand( 10.0 );
float Z_distance = llFrand( 10.0 );
// combine these distance components into a vector and use it
// to increment the starting position and reposition the object.
vector increment = < X_distance, Y_distance, Z_distance >;
vector newPosition = startPosition + increment;
llSetPos( newPosition ); // reposition object.
if ( second > 19 ) // then time to wrap this up.
{
// move object back to starting position...
while ( llVecDist( llGetPos(), startPosition ) > 0.001)
{
llSetPos( startPosition );
}
llSay( 0, "Object now resting and resetting script." );
llResetScript(); // return object to ready state.
}
}
}
A simple particle system
It is possible to arrange for
an object to generate and emit "particles" under script
control. For example, an object can be set up to emit bubbles,
sparks, fire, etc.
llParticleSystem(
[
PSYS_PART_FLAGS, PSYS_PART_WIND_MASK | PSYS_PART_EMISSIVE_MASK,
PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_EXPLODE,
PSYS_PART_START_COLOR, <1,0,0>
] );
and the second call should look like: llParticleSystem( [] );
Here is a snapshot of the author standing next to a
a moving object emitting glowing red balls as specified in the
call above:
PSYS_PART_FLAGS, PSYS_PART_WIND_MASK | PSYS_PART_EMISSIVE_MASK
sets both the PSYS_PART_WIND_MASK and
PSYS_PART_EMISSIVE_MASK flags within the
PSYS_PART_FLAGS parameter at the same time. (You can
set as many flags as necessary by separating them with logical
OR characters (|) as shown above.)
Rotating an object
There are several ways to represent
rotational movement in LSL. The Euler representation uses a
(3-part) vector where each component specifies movement in one
of 3 dimensions. For example, the components of a vector, V,
holding an Euler rotation, is composed of 3 angular components
describing: Component Movement in plane Around axis Known as
--------- ----------------- ------------ --------
V.x Y-Z X roll
V.y X-Z Y pitch
V.z X-Y Z yaw
While the Euler representation is fairly intuitive and
is supported within LSL, rotational movments may also be
represented as "quaternions."
llEuler2Rot( < 0, 10 * DEG_TO_RAD, 0 >)
yielding a value of < 0.00000, 0.08716, 0.00000,
0.99619 >.
default
{
state_entry()
{
llSay( 0, "Hello, Avatar!");
vector startPoint = llGetPos();
}
touch_start(integer total_number)
{
llSay( 0, "Touched." );
// Define a rotation of 10 degrees around the Y-axis.
rotation Y_10 = llEuler2Rot( < 0, 10 * DEG_TO_RAD, 0 > );
// now rotate the object 10 degrees in the X-Z plane during
// each loop iteration. note that each call to llSetRot
// causes a .2 second delay.
integer i;
for( i = 1; i < 100; i++ )
{
// rotate object in the X-Z plane around its own Y-axis.
rotation newRotation = llGetRot() * Y_10;
llSetRot( newRotation );
}
llSay( 0, "Rotation stopped" );
}
}
Each pass through the for loop calculates a new
orientation relative to the current orientation by multiplying
the object's current rotational orientation by the 10 degree
rotation defined during the state_entry event.
rotation newRot = llGetRot() * Y_15;
will rotate the object with respect to global
coordinates, while the statement: rotation newRot = Y_15 * llGetRot();
will rotate with respect to the object's local axes.
Rotation local to each client
Rotation may also be
simulated by using the llTargetOmega() function within a
script. Movement produced by this function is produced
independently by each SecondLife client that can observe the
moving object. As a result, movement is much smoother, but
behavior will differ slightly on each client.
default
{
state_entry()
{
llTargetOmega( < 0, 1, 1 >, .2 * PI, 1.0 );
}
}
Note that this approach is less flexible than
rotational movement produced using llSetRot().
Rotating an object in an orbit
Here is a script that
makes an object orbit in the X-Y plane around a point < 3,
3, 3 > from its starting position.
"An object can be rotated around an arbitrary
point by multiplying a vector by a rotation.... The vector
should be the difference between the object's current
position and the desired "center-point" of rotation. Take
the result of the multiplication and add it to the object's
current position. This vector will be the "new location" the
object should be moved to."
vector rotationCenter; // point to rotate around.
default
{
state_entry()
{
llSay( 0, "Hello, Avatar!");
vector startPoint = llGetPos();
rotationCenter = startPoint + < 3, 3, 3 >;
// distance to the point of rotation should probably be a
// function of the max dimension of the object.
}
touch_start(integer total_number)
{
llSay( 0, "Touched." );
// Define a "rotation" of 10 degrees around the z-axis.
rotation Z_15 = llEuler2Rot( < 0, 0, 15 * DEG_TO_RAD > );
integer i;
for( i = 1; i < 100; i++ ) // limit simulation time in case of
{ // unexpected behavior.
vector currentPosition = llGetPos();
vector currentOffset = currentPosition - rotationCenter;
// rotate the offset vector in the X-Y plane around the
// distant point of rotation.
vector rotatedOffset = currentOffset * Z_15;
vector newPosition = rotationCenter + rotatedOffset;
llSetPos( newPosition );
}
llSay( 0, "Orbiting stopped" );
}
}
Two simple examples using LSL "physics"
LSL includes
the ability to simulate some of the physical laws governing
energy, gravity, torque, etc. as they affect objects in-world.
This ability derives from a set of built-in functions that
implements the effects of these laws.
llSetStatus( 1, TRUE )
and then, when the object is touched, uses the
command llApplyImpulse( < 0.0, 0.0, z_force >, FALSE );
to apply an instantaneous vertical impulse: default
{
state_entry()
{
llSay( 0, "Hello, Avatar! Touch to launch me straight up.");
llSetStatus( 1, TRUE ); // turn on physics.
}
touch_start(integer total_number)
{
vector start_color = llGetColor( ALL_SIDES ); // save current color.
llSetColor( < 1.0, 0.0, 0.0 > , ALL_SIDES ); // set color to red.
float objMass = llGetMass();
float Z_force = 20.0 * objMass;
llApplyImpulse( < 0.0, 0.0, Z_force >, FALSE );
llSay( 0, "Impulse of " + (string)Z_force + " applied." );
llSetColor( start_color , ALL_SIDES ); // set color to green.
}
}
The next example applies a continuous upward
vertical force using the command llSetForce( < 0.0, 0.0, Z_force >, FALSE )
on the object. This function call sets up a force of
0.0 Newtons along the positive X-axis, 0.0 Newtons along the
positive Y-axis, and Z_force Newtons along the positive
Z-axis.
vector startPos;
vector curPos;
vector curForce;
integer second;
default
{
state_entry()
{
llSay( 0, "Hello, Avatar! Touch to launch me straight up.");
llSetStatus( 1, TRUE );
startPos = < 0, 0, 0 >;
}
touch_start(integer total_number)
{
startPos = llGetPos();
curPos = startPos;
curForce = < 0, 0, 0 >;
second = 0;
llSetColor( < 1.0, 0.0, 0.0 > , ALL_SIDES ); // set color to red.
float objMass = llGetMass();
float Z_force = 10.2 * objMass;
llSetForce( < 0.0, 0.0, Z_force >, FALSE );
llSay( 0, "Force of " + (string)Z_force + " being applied." );
llSetTimerEvent(1);
}
timer()
{
second++;
curPos = llGetPos();
float curDisplacement = llVecMag( curPos - startPos );
if( ( curDisplacement > 30. ) && // then object is too far away, and
( llGetForce() != < 0.0, 0.0, 0.0 > ) ) // force not already zero,
{ // then let gravity take over, and change color to green.
llSetForce( < 0.0, 0.0, 0.0 >, FALSE );
llSetColor( < 0, 1.0, 0 >, ALL_SIDES );
llSay( 0, "Force removed; object in free flight." );
}
if ( second > 19 ) // then time to wrap this up.
{
// turn object blue and zero force to be safe....
llSetColor( < 0, 0, 1.0 >, ALL_SIDES ); // change color to blue.
llSetForce( < 0, 0, 0 >, FALSE );
// ...move object back to starting position...
// ...after saving current status of Physics attribute.
integer savedStatus = llGetStatus( 1 );
llSetStatus( 1, FALSE ); // turn physics off.
while ( llVecDist( llGetPos(), startPos ) > 0.001)
{
llSetPos( startPos );
}
llSetStatus( 1, savedStatus ); // restore Physics status.
//...and then turn color to black and Reset the script.
llSetColor( < 1, 1, 1 >, ALL_SIDES );
llSetTimerEvent( 0 ); // turn off timer events.
llSay( 0, "Done and resetting script." );
llResetScript(); // return object to ready state.
}
}
}
Controlling an object through commands embedded in chat
text
A script can be set up to "listen" to ongoing chat
dialog and respond to commands issued via chat. Here is a
script that listens for chat messages coming from the owner of
the object in which the script resides. If a chat line starts
with the strings "up" or "down" (separated from the other text
on the line by a blank), the script will assume the owner is
giving a command to move the object up or down. It will then
look for a second blank-delimited string, convert it to a
number, and use that number to decide how far up or down to
move the object.
vector startPosition;
float groundLevel;
default
{
state_entry()
{
llListen( 0, "", llGetOwner(), "");
startPosition = llGetPos();
groundLevel = llGround( startPosition );
llSay( 0, "Control this object with chat commands like:" );
llSay( 0, "'up' or 'down' followed by a distance." );
}
listen( integer channel, string name, key id, string message )
{
// separate the input into blank-delmited tokens.
list parsed = llParseString2List( message, [ " " ], [] );
// get the first part--the "command".
string command = llList2String( parsed, 0 );
// get the second part--the "distance".
string distance_string = llList2String( parsed, 1 );
float distance = ( float )distance_string;
vector position = llGetPos();
if( command == "up" )
{
if( ( position.z + distance ) < (startPosition.z + 10.0 ) )
{
llSetPos( llGetPos() + < 0, 0, distance > ); // move up
llSetText( "Went up " + (string)distance, < 1, 0, 0 >, 1 );
}
else
{
llSetText( "Can't go so high.", < 1, 0, 0 >, 1 );
}
}
else if( command == "down" )
{
if( ( position.z - distance ) > groundLevel )
{
llSetPos( llGetPos() + < 0, 0, -distance > ); // move down
llSetText( "Went down " + (string)distance, < 1, 0, 0 >, 1 );
}
else
{
llSetText( "Can't go so low.", < 1, 0, 0 >, 1 );
}
}
}
}
Controlling an object through a Dialog box
It is
possible for an object to present a set of options to an
avatar that can be used to control its behavior. Here is a
program that moves an object up or down along its Z axis under
dialog control:
integer dialog_channel= 427; // set a dialog channel
list menu = [ "Go up", "Go down" ];
vector startPosition;
float groundLevel;
default
{
state_entry()
{
// arrange to listen for dialog answers (from multiple users)
llListen( dialog_channel, "", NULL_KEY, "");
startPosition = llGetPos();
groundLevel = llGround( startPosition );
}
touch_start(integer total_number)
{
llDialog( llDetectedKey( 0 ), "What do you want to do?", menu,
dialog_channel );
}
listen(integer channel, string name, key id, string choice )
{
vector position = llGetPos();
// if a valid choice was made, implement that choice if possible.
// (llListFindList returns -1 if choice is not in the menu list.)
if ( llListFindList( menu, [ choice ]) != -1 )
{
if ( choice == "Go up" )
{
if( position.z < ( startPosition.z + 10.0 ) )
{
llSetPos( llGetPos() + < 0, 0, 1.0 > ); // move up
}
}
else if( choice == "Go down" )
{
if( position.z > ( groundLevel + 1.0 ) )
{
llSetPos( llGetPos() + < 0, 0, -1.0 > ); // move down
}
}
}
else
{
llSay( 0, "Invalid choice: " + choice );
}
}
}
Controlling an object through avatar movement control
keys
It is also possible to allow an object to respond to
keyboard keys that normally control your avatar. For example,
the next script will ask for permission to reassign your
avatar forward and backward movement keys to move the object
up and down.
vector startPosition;
float groundLevel;
default
{
state_entry()
{
// get permission to take over the avatar's control inputs.
llRequestPermissions( llGetOwner(), PERMISSION_TAKE_CONTROLS );
startPosition = llGetPos();
groundLevel = llGround( startPosition );
}
run_time_permissions( integer perm ) // event for processing
// permission dialog.
{
if ( perm & PERMISSION_TAKE_CONTROLS ) // permission has been given.
{
// go ahead and take over the forward and backward controls.
llTakeControls( CONTROL_FWD | CONTROL_BACK, TRUE, FALSE );
}
}
control( key id, integer held, integer change ) // event for processing
// key press.
{
vector position = llGetPos();
if ( change & held & CONTROL_FWD )
{ // the "move forward" control has been activated.
if( position.z < (startPosition.z + 10.0) )
{
llSetPos( llGetPos() + < 0, 0, 1.0 >); // move up
}
}
else if ( change & held & CONTROL_BACK )
{ // the "move backward" key has been activated.
if( position.z > groundLevel + 1.0 )
{
llSetPos( llGetPos() + < 0, 0, -1.0 >); // move down
}
}
}
}
Rezzing objects from within scripts
"Rezzing" is the
process of creating an object. Avatars can create objects as
described earlier. However, it is not possible for a
script to create an object in this fashion. Scripts can only
"instantiate" (create instances of) objects already
created and residing in the inventory of the object in which
the script is located.
default
{
state_entry()
{
llSay( 0, "Hello, Avatar!");
}
touch_start(integer total_number)
{
llSay( 0, "Touched.");
llRezObject("Object1", llGetPos() + < 0, 0, 2 >, ZERO_VECTOR,
ZERO_ROTATION, 42);
}
}
Creating linked sets of objects
LSL provides a set of
functions for linking and managing sets of primitive objects,
sometimes called "link_sets." Primitive objects that are
linked together will behave like a single object in some ways.
For example, they will move as a single object if repositioned
or made physical, and editing commands will apply to the
collection. They can also be set up to communicate with one
another and pass certain event information among themselves.
integer createdObjectCounter;
integer linkedObjectCounter;
default
{
state_entry()
{
llSay( 0, "Hello, Avatar!");
linkedObjectCounter = 0; // zero the linked object counter.
}
touch_start(integer total_number)
{
if( createdObjectCounter <= 0 ) // nothing has yet been linked,
{ // begin object creation sequence...
// ask for permissions now, since it will be too late later.
llRequestPermissions( llGetOwner(), PERMISSION_CHANGE_LINKS );
}
else // just do whatever should be done upon touch without
{ // creating new objects to link.
// insert commands here to respond to a touch.
}
}
run_time_permissions( integer permissions_granted )
{
if( permissions_granted == PERMISSION_CHANGE_LINKS )
{ // create 2 objects.
llRezObject("Object1", llGetPos() + < 1, 0, 2 >,
ZERO_VECTOR, ZERO_ROTATION, 42);
createdObjectCounter = createdObjectCounter + 1;
llRezObject("Object1", llGetPos() + < -1, 0, 2 >,
ZERO_VECTOR, ZERO_ROTATION, 42);
createdObjectCounter = createdObjectCounter + 1;
}
else
{
llOwnerSay( "Didn't get permission to change links." );
return;
}
}
object_rez( key child_id )
{
llOwnerSay( "rez happened and produced object with key " +
(string)child_id );
// link as parent to the just created child.
llCreateLink( child_id, TRUE );
// if all child objects have been created then the script can
// continue to work as a linked set of objects.
linkedObjectCounter++;
if( linkedObjectCounter >= 2 )
{
// Change all child objects in the set to red (including parent).
llSetLinkColor( LINK_ALL_CHILDREN, < 1, 0, 0 >, ALL_SIDES );
// Make child object "2" half-tranparent.
llSetLinkAlpha( 2, .5, ALL_SIDES );
// Insert commands here to manage subsequent activity of the
// linkset, like this command to rotate the result:
// llTargetOmega( < 0, 1, 1 >, .2 * PI, 1.0 );
}
}
}
Activities within this script are coordinated via a
cascade of events. The run_time_permissions event runs only
after the owner answers the prompt to allow object links to be
established. It checks to see if permission has been granted
and either rezzes the new objects or says it can't.
A simple texture animation
It is possible to create
texture files that hold multiple individual images that may be
used as animation frames. When a series of such frames is
displayed upon the side of an object many different effects
are possible. For example, an object may be made to appear as
if it is rotating even though it is not. Here is a snapshot of
the author next to a flame simulated using texture animation:
default
{
state_entry()
{
llSetStatus(STATUS_PHANTOM,TRUE);
llSetTexture("lit_texture", ALL_SIDES);
llSetTextureAnim (ANIM_ON | LOOP, ALL_SIDES, 4, 4, 0, 0, 15.0);
}
}
This script works on a flattened cube with
dimensions of approximately 8.0 by 8.0 by 0.0 meters, and
requires a texture like this flame_texture.
To get this texture into an object you must first get it into
your SecondLife inventory and then move it into the object. To
get it into your inventory:
Using a sensor to define a (moving) center of
rotation
An earlier section showed how to cause an object
to rotate around a fixed center. This section introduces
sensors and shows how they can be used to identify the
location of an avatar so that an object may be made to rotate
around it.
// This is a script designed to orbit its owner.
vector startPos;
vector curPos;
vector offset; // offset from Agent
integer iteration;
float rotationRate; // degrees of rotation per iteration
float sensorInterval; // seconds between sensor scan.
default
{
state_entry()
{
llOwnerSay( "Hello, Avatar! Touch to start orbiting." );
llSetStatus( 1, FALSE ); // turn Physics off.
offset = < 2, 2, 1 >;
iteration = 0;
rotationRate = .5;
sensorInterval = .3;
}
touch_start(integer total_number)
{
startPos = llGetPos();
curPos = startPos;
llSleep( .1 );
key id = llGetOwner();
llSensorRepeat( "", id, AGENT, 96, PI, sensorInterval );
}
sensor(integer total_number)
{
iteration++;
if( iteration > 300 )
{
llResetScript();
}
if( llDetectedOwner( 0 ) == llGetOwner() )
{ // the detected Agent is my owner.
vector position = llDetectedPos(0); // find Owner position.
// calculate next object position relative both to the Owner's
// position and the current time interval counter. That is,
// use the iteration counter to define a rotation, multiply
// the rotation by the constant offset to get a rotated offset
// vector, and add that rotated offset to the current position
// to defne the new position.
float degreeRotation = llRound( rotationRate * iteration ) % 360;
rotation Rotation =
llEuler2Rot( < 0, 0, degreeRotation * DEG_TO_RAD > );
vector rotatedOffset = offset * Rotation;
position += rotatedOffset;
// change the location of the object and save the current (rotated)
// offset for use during the next iteration.
llSetPos( position );
offset = rotatedOffset;
}
}
}
Sensor scans in this script are set to search for the
owner agent within 96 meters of the object and within PI
radians to either side of the object's forward vector (which
is to say all around the object).
Investigating some example scripts
This tutorial has
presented a variety of annotated example scripts to help
readers learn to use LSL. By using the basic structures
presented so far, it should be possible to begin to understand
some more detailed examples. Of particular interest are:
.
To use the example gun script, create an object to
be used as a gun. A default cube will do, but you may prefer
something more realistic. Copy the gun script into the newly
created object, lower the bullet velocity by replacing the
line float gBulletSpeed = 75.0;
with the line float gBulletSpeed = 7.5;
and "Save" the script. This change will make it
possible to see the bullet as it emerges from the gun and
moves through space.
Summary
This tutorial has presented a collection of
examples illustrating many of the basic facilities available
to LSL scripters. By using these basic structures, it should
be possible to construct scripts for a wide variety of
purposes. For example, you should be able to script simple
games, dynamic gadgets and kinetic art. It should even be
possible to investigate and/or illustrate some physical
phenomena in a more or less realistic fashion by using the
"physics" available in-world.
Second version: Sept 19, 2005
Current version: April 7, 2006