Your Complete Introduction to Programming in Virtual Worlds - Master the basics of LSL scripting from absolute zero to creating interactive objects.
Sorin Todys - LSL Master Developer
With over 20 years of experience teaching scripting in virtual worlds, Sorin has helped thousands of creators bring their ideas to life. No prior programming experience required! All classes held at Alife Virtual School region.
Welcome to the exciting world of LSL scripting! This course will take you from complete beginner to confident script creator.
By the end of this course, you will be able to:
LSL (Linden Scripting Language) is a programming language specifically designed for virtual worlds like Second Life and OpenSimulator (which powers Alife Virtual). It allows you to add behavior and interactivity to objects.
Scripts are text files that contain instructions written in LSL. When you place a script inside an object (a "prim"), the server reads and executes those instructions.
To create your first script:
When you create a new script, you'll see this default code:
default
{
state_entry()
{
llSay(0, "Hello, Avatar!");
}
touch_start(integer total_number)
{
llSay(0, "Touched.");
}
}
Let's understand each part:
default { }Every script has at least one state. Think of a state as a mode or condition. The default state is where every script begins. More complex scripts can have multiple states (like "on" and "off").
default // This is the state name
{
// Events go inside these curly braces
}
state_entry() and touch_start()Events are things that happen to the object. When an event occurs, the code inside it runs.
state_entry() - Runs when the script starts or enters a new statetouch_start(integer total_number) - Runs when someone touches the objectstate_entry() // Event name
{
// Code here runs when script starts
}
touch_start(integer total_number) // Event with parameter
{
// Code here runs when object is touched
}
llSay()Functions are pre-built commands that do specific things. LSL has hundreds of built-in functions. They always start with ll (Linden Lab).
llSay(0, "Hello, Avatar!");
// │ └─ The message to say
// └─ Channel number (0 = public chat)
Common functions:
llSay(channel, text) - Speak in chatllOwnerSay(text) - Message only to ownerllWhisper(channel, text) - Quiet message (10m range)llShout(channel, text) - Loud message (100m range)Let's modify the default script to make it more personal:
default
{
state_entry()
{
llSay(0, "Welcome to my interactive object!");
llOwnerSay("Script is running and ready.");
}
touch_start(integer total_number)
{
llSay(0, "Thanks for touching me!");
llOwnerSay("Someone touched your object.");
}
}
Variables are containers that store information. Think of them as labeled boxes where you can keep different types of data.
| Type | Description | Example |
|---|---|---|
integer |
Whole numbers | 42, -7, 1000 |
float |
Decimal numbers | 3.14, -0.5, 99.9 |
string |
Text (in quotes) | "Hello", "Alife Virtual" |
key |
Unique identifier (UUID) | "a1b2c3d4-..." |
vector |
3D coordinates (x,y,z) | <128.0, 128.0, 25.0> |
rotation |
Rotation (quaternion) | <0.0, 0.0, 0.0, 1.0> |
list |
Collection of items | [1, 2.5, "text", key] |
// Declaring variables (global scope - outside events)
integer counter = 0;
float temperature = 98.6;
string message = "Hello, World!";
vector position = <128.0, 128.0, 25.0>;
default
{
state_entry()
{
llSay(0, "Counter is: " + (string)counter);
llSay(0, "Temperature is: " + (string)temperature);
llSay(0, message);
}
touch_start(integer total_number)
{
// Increment the counter each touch
counter = counter + 1;
llSay(0, "Touched " + (string)counter + " times!");
}
}
Sometimes you need to convert one type to another:
integer num = 42;
float decimal = 3.14;
string text = "Hello";
// Convert to string for displaying
llSay(0, "Number: " + (string)num); // "Number: 42"
llSay(0, "Decimal: " + (string)decimal); // "Decimal: 3.14"
// Convert string to integer/float
integer converted = (integer)"123"; // 123
float convertedFloat = (float)"99.9"; // 99.9
// Simple touch counter
integer touchCount = 0;
string objectName = "Touch Counter";
default
{
state_entry()
{
llSetObjectName(objectName);
llSetText("Touches: 0", <1,1,1>, 1.0);
llOwnerSay("Counter initialized.");
}
touch_start(integer total_number)
{
// Increment counter
touchCount = touchCount + 1;
// Update floating text
llSetText("Touches: " + (string)touchCount, <0,1,0>, 1.0);
// Say the count
llSay(0, "This object has been touched " + (string)touchCount + " times!");
}
}
Control flow allows your script to make decisions and choose different actions based on conditions.
if (condition)
{
// Code runs if condition is TRUE
}
| Operator | Meaning | Example |
|---|---|---|
== |
Equal to | if (x == 5) |
!= |
Not equal to | if (x != 0) |
> |
Greater than | if (x > 10) |
< |
Less than | if (x < 100) |
>= |
Greater or equal | if (x >= 5) |
<= |
Less or equal | if (x <= 50) |
if (condition)
{
// Runs if condition is TRUE
}
else
{
// Runs if condition is FALSE
}
integer score = 85;
if (score >= 90)
{
llSay(0, "Grade: A - Excellent!");
}
else if (score >= 80)
{
llSay(0, "Grade: B - Good job!");
}
else if (score >= 70)
{
llSay(0, "Grade: C - Average");
}
else if (score >= 60)
{
llSay(0, "Grade: D - Needs improvement");
}
else
{
llSay(0, "Grade: F - Failed");
}
Combine multiple conditions:
&& - AND (both must be true)|| - OR (at least one must be true)! - NOT (inverts the condition)integer age = 25;
integer hasTicket = TRUE;
// Both conditions must be true
if (age >= 18 && hasTicket)
{
llSay(0, "Access granted!");
}
// At least one condition must be true
if (age < 18 || !hasTicket)
{
llSay(0, "Access denied!");
}
// Owner-only access system
default
{
touch_start(integer total_number)
{
// Get the key of who touched the object
key toucher = llDetectedKey(0);
// Get the owner's key
key owner = llGetOwner();
// Check if toucher is the owner
if (toucher == owner)
{
llSay(0, "Welcome, Owner! Access granted.");
llOwnerSay("You have full access to this system.");
}
else
{
llSay(0, "Access denied. Owner only.");
// Get toucher's name for logging
string toucherName = llDetectedName(0);
llOwnerSay("Access attempt by: " + toucherName);
}
}
}
key comparisons for security, never names! Avatar names can be changed, but keys (UUIDs) are permanent and unique.
States allow objects to have different behaviors depending on their current mode. Think of a light switch: it has "on" and "off" states, and touching it does different things in each state.
state state_name
{
// Events for this state
state_entry()
{
// Runs when entering this state
}
touch_start(integer num)
{
// Runs on touch while in this state
}
}
Use the state command to change states:
state new_state_name; // Switches to new_state_name
// Two-state light switch
default // This is the "OFF" state
{
state_entry()
{
// When entering OFF state, make object dark
llSetColor(<0.2, 0.2, 0.2>, ALL_SIDES);
llSetText("OFF", <1,0,0>, 1.0);
llOwnerSay("Light is OFF");
}
touch_start(integer total_number)
{
// When touched while OFF, turn ON
llSay(0, "Turning light ON...");
state on; // Switch to "on" state
}
}
state on // This is the "ON" state
{
state_entry()
{
// When entering ON state, make object bright
llSetColor(<1.0, 1.0, 0.0>, ALL_SIDES); // Yellow
llSetText("ON", <0,1,0>, 1.0);
// Make it glow and emit light
llSetPrimitiveParams([
PRIM_FULLBRIGHT, ALL_SIDES, TRUE,
PRIM_POINT_LIGHT, TRUE, <1,1,0>, 1.0, 10.0, 0.75
]);
llOwnerSay("Light is ON");
}
touch_start(integer total_number)
{
// When touched while ON, turn OFF
llSay(0, "Turning light OFF...");
// Remove the light
llSetPrimitiveParams([
PRIM_FULLBRIGHT, ALL_SIDES, FALSE,
PRIM_POINT_LIGHT, FALSE, <0,0,0>, 0, 0, 0
]);
state default; // Switch back to default (OFF) state
}
}
default state (OFF)state_entry() makes object darkon statestate_entry() in ON state makes it brighttouch_start() behavior!// Traffic light with 3 states
state green
{
state_entry()
{
llSetColor(<0, 1, 0>, ALL_SIDES); // Green
llSetText("GO", <1,1,1>, 1.0);
llSetTimerEvent(5.0); // Change after 5 seconds
}
timer()
{
llSay(0, "Changing to YELLOW");
state yellow;
}
}
state yellow
{
state_entry()
{
llSetColor(<1, 1, 0>, ALL_SIDES); // Yellow
llSetText("CAUTION", <0,0,0>, 1.0);
llSetTimerEvent(2.0); // Change after 2 seconds
}
timer()
{
llSay(0, "Changing to RED");
state red;
}
}
state red
{
state_entry()
{
llSetColor(<1, 0, 0>, ALL_SIDES); // Red
llSetText("STOP", <1,1,1>, 1.0);
llSetTimerEvent(5.0); // Change after 5 seconds
}
timer()
{
llSay(0, "Changing to GREEN");
state green;
}
}
| Event | When It Fires | Common Uses |
|---|---|---|
state_entry() |
Script starts or state changes | Initialization, setup |
touch_start() |
Avatar touches object | Buttons, doors, interactive objects |
touch_end() |
Avatar releases touch | Detecting release after hold |
timer() |
Timer interval expires | Repeating actions, animations |
collision_start() |
Object collides with something | Damage systems, triggers |
listen() |
Hears chat on channel | Chat commands, communication |
sensor() |
Detects nearby objects/avatars | Security, proximity detection |
changed() |
Object properties change | Inventory changes, linking |
// Clock that announces the time every 60 seconds
default
{
state_entry()
{
llSetTimerEvent(60.0); // Fire timer every 60 seconds
llSay(0, "Clock started!");
}
timer()
{
// Get current timestamp
string timestamp = llGetTimestamp();
llSay(0, "Current time: " + timestamp);
}
touch_start(integer num)
{
// Stop the timer
llSetTimerEvent(0.0);
llSay(0, "Clock stopped.");
}
}
// Collision detector
default
{
state_entry()
{
llSetStatus(STATUS_PHANTOM, FALSE); // Make sure collisions work
llSay(0, "Collision detector active");
}
collision_start(integer num_detected)
{
// Loop through all collisions
integer i;
for (i = 0; i < num_detected; i++)
{
string name = llDetectedName(i);
llSay(0, "Collided with: " + name);
}
}
}
// Simple chat command system
integer listenHandle;
default
{
state_entry()
{
// Listen on channel 0 (public chat)
listenHandle = llListen(0, "", NULL_KEY, "");
llSay(0, "I'm listening! Say 'hello' or 'help'");
}
listen(integer channel, string name, key id, string message)
{
// Convert message to lowercase for easier matching
message = llToLower(message);
if (message == "hello")
{
llSay(0, "Hello, " + name + "!");
}
else if (message == "help")
{
llSay(0, "Available commands: hello, help, time");
}
else if (message == "time")
{
llSay(0, "Current time: " + llGetTimestamp());
}
}
}
Task: Create a script that greets the toucher by name.
Requirements:
llDetectedName(0) to get the toucher's nameUse: string name = llDetectedName(0); and llSay(0, "Hello, " + name + "!");
Task: Create an object that changes color each time it's touched.
Requirements:
llSetColor() functionUse modulo operator: colorIndex = counter % 4; to cycle through 4 colors
Task: Create a countdown timer that counts from 10 to 0.
Requirements:
Task: Create a simple automatic door using states.
Requirements:
Use llSetStatus(STATUS_PHANTOM, TRUE); to make door passable
Task: Create a simple quiz that asks a question and checks the answer.
Requirements:
llListen() to hear chatTask: Create a vending machine that gives items.
Requirements:
| Function | Purpose | Example |
|---|---|---|
llSay() |
Public chat (20m) | llSay(0, "Hello!"); |
llOwnerSay() |
Message to owner only | llOwnerSay("Private msg"); |
llSetText() |
Floating text | llSetText("Hi", <1,1,1>, 1.0); |
llSetColor() |
Change color | llSetColor(<1,0,0>, ALL_SIDES); |
llGetOwner() |
Get owner's key | key owner = llGetOwner(); |
llDetectedName() |
Get toucher's name | string n = llDetectedName(0); |
llDetectedKey() |
Get toucher's key | key k = llDetectedKey(0); |
llSetTimerEvent() |
Start/stop timer | llSetTimerEvent(5.0); |
llListen() |
Listen to chat | llListen(0, "", NULL_KEY, ""); |
// Global variables here (optional)
integer myVariable = 0;
string myText = "Hello";
// Default state (required)
default
{
state_entry()
{
// Initialization code
llSay(0, "Script started!");
}
touch_start(integer total_number)
{
// Code when touched
llSay(0, "Touched!");
}
timer()
{
// Code when timer fires
}
}
// Additional states (optional)
state my_other_state
{
state_entry()
{
// Code when entering this state
}
touch_start(integer total_number)
{
// Different behavior in this state
state default; // Return to default state
}
}
; at the end{ needs a matching }llSay(0, myInteger); - must cast: llSay(0, (string)myInteger);= vs. comparison ==You've completed the LSL Scripting Fundamentals course!
Continue your learning journey with these recommended courses: