Thinking in LSL: The Mental Model

Welcome to VirtuaPrim's curriculum. If you have tried learning LSL before and given up, do not sweat it. Most guides treat LSL like a math textbook. We treat it like an interactive machine.

Unlike programming languages built for web apps or phones, LSL (Linden Scripting Language) is wired directly into a 3D physical world. To get good at it, stop thinking about lines of code running top-to-bottom. Start thinking about States, Events, and Actions.

The Core Loop: Think Like a Light Switch

Imagine a normal light switch. It lives in one of two States: "On" or "Off".

The switch sits completely quietly in the "Off" state until an Event happens. You click it. That trigger fires off an Action (electricity moves, bulb glows) and flips the machine into the "On" state. It stays there until the next event comes along.

In web coding, a script runs its lines and stops. In LSL, your script never stops running. It is an immortal little robot brain sitting inside a 3D prim, waiting for the universe to bump, touch, or shout at it.

Your Very First Script

Let us skip the boring history lesson and look at actual code. If you right-click any object in Second Life, go to Edit, jump to the Content tab, and click "New Script", you will get this exact boilerplate code.

default
{
    state_entry()
    {
        llOwnerSay("Hello, Avatar!");
    }

    touch_start(integer total_number)
    {
        llSay(0, "Touched.");
    }
}

How to Read This Without Panicking

See that word default at the top? That is the mandatory home state for every script. Think of it as the "Wake Up" mode. Curly braces { } act like wrapper boxes. Everything inside them belongs to that specific state.

Inside those walls, we have two triggers:

  • state_entry(): This event fires the exact millisecond the script boots up or resets.
  • touch_start(): This event fires whenever someone left-clicks the object inworld.

Inside those triggers, we run commands like llSay(). Notice the semicolon ; at the end of those lines? That is just telling LSL: "Alright, I am done with this instruction, move to the next one." Leave it out, and the script compiler will throw a tantrum.

States & Flow Control

Now that you know scripts start in a default state, let us look at how we actually travel between different states. This is how you make an object behave completely differently depending on the situation.

Interactive State Simulator

Click the physical prim below to test out the security script. Watch how the console output changes based on the object's current state.

DEFAULT
[System] Script running...
[state_entry] Default state active. Waiting for touch.

To move to another state in code, you use the state keyword. Here is the exact code running in the simulator above:

default
{
    touch_start(integer num)
    {
        llSay(0, "System armed. Entering secure mode...");
        state secure; // This teleports the script to the next block
    }
}

state secure
{
    state_entry()
    {
        llSetColor(<1.0, 0.0, 0.0>, ALL_SIDES); // Turn the prim red
    }

    touch_start(integer num)
    {
        llSay(0, "ACCESS DENIED. System is locked.");
    }
}

Events: The Triggers

Events are environmental radar sensors. They do not ask how something happened. They just scream out that something happened right now, forcing the script to react.

You cannot create your own custom events in LSL. You have to use the ones built into Second Life. Here are the big hitters you will use daily:

Essential Event Library

  • touch_start: Someone clicked the object.
  • collision_start: An avatar or physical object physically bumped into the prim.
  • sensor: Found an avatar or object nearby using a radar sweep.
  • listen: Heard someone say something in chat or received a message from a HUD.
  • timer: A built-in alarm clock went off.
  • changed: Someone sat on the object, or linked a new piece to it.

Functions: The Actions

If events are your triggers, Functions are your muscle power. They make things happen inworld. You use them to play sounds, change textures, or deal out items to players.

LSL provides hundreds of functions, and they all start with a lower-case double 'l' standing for Linden Lab: llDoSomething().

Arguments: Passing Info to Your Code

Most functions require extra instructions inside their parentheses. These are called arguments. For example, llSay() needs to know two things: what radio channel to talk on, and what text to say.

llSay(0, "I need data inside these parentheses to work!");

The 0 tells it to use the public chat channel, and the text in quotes is the message. If you pass the wrong type of info, your script will refuse to compile.

Variables & Data Types

A variable is just a storage box with a name on it. Instead of typing the number `42` ten times in your script, you put it in a box named myNumber. To create a variable in LSL, you have to declare exactly what Data Type is allowed inside that box.

The 7 Core Data Types of LSL

  • integer: Whole numbers only (e.g., 1, -50). Great for counting or tracking states.
  • float: Numbers with decimals (e.g., 1.5, 0.001). Used for physics mass, timers, and exact coordinates.
  • string: Text. It must always be wrapped in double quotes (e.g., "Hello Avatar!").
  • vector: A set of three floats wrapped in angle brackets (e.g., <1.0, 2.5, 0.0>). Used exclusively for X,Y,Z positions and RGB colors.
  • rotation: A set of four floats representing a quaternion angle. Do not try to do math on these manually.
  • key: A string that holds a unique 36-character UUID (e.g., "66864f3c-e095..."). Every avatar, texture, and object has one.
  • list: An array that can hold a mix of other data types wrapped in square brackets (e.g., ["Apple", 5, <1,0,0>]).

Example: Declaring Variables

// These are global variables placed at the top of the script
integer myChannel = -500;
string myMessage = "Access Granted.";
vector myColor = <0.0, 1.0, 0.0>; // Green

default
{
    state_entry()
    {
        llSetColor(myColor, ALL_SIDES);
        llSay(myChannel, myMessage);
    }
}

Loops & Conditional Logic

Logic is how you make your script actually think for itself. Instead of doing the exact same thing every time, conditional logic lets your script look at a situation and make a choice.

If / Else Statements

The if statement checks if a condition is true. If it is, it runs the code in the block. If it is false, it skips it or falls back to an else block.

integer powerLevel = 50;

if (powerLevel > 90)
{
    llOwnerSay("Power is optimal.");
}
else if (powerLevel > 20)
{
    llOwnerSay("Power is acceptable.");
}
else
{
    llOwnerSay("WARNING: Power critical!");
}

Loops (Doing things multiple times)

Sometimes you need to process a lot of data at once, like finding specific items in a list. A for loop lets you repeat a block of code an exact number of times.

// This loop will count from 1 to 5 and print it to local chat.
integer i;
for (i = 1; i <= 5; ++i)
{
    llSay(0, "Count: " + (string)i);
}

Making Things Move

Movement is where a lot of scripters get stuck because Second Life has two completely different ways to move objects: Keyframed (Non-Physical) and Physical.

Method 1: Keyframed Animation (Best for Doors, Platforms, Elevators)

If your object does not need to roll down a hill or get shot out of a cannon, use non-physical movement. It is smooth, zero-lag, and does not load down the simulator server.

llSetLinkPrimitiveParamsFast(LINK_THIS, [
    PRIM_POSITION, llGetPos() + <0.0, 0.0, 2.0> // Slide straight up 2 meters
]);

Method 2: Physical Velocity (Best for Vehicles, Projectiles, Dice)

When you turn physics on, the Havok physics engine takes over. The object gains mass, falls with gravity, and responds to momentum.

llSetPhysics(TRUE); // Turns physics engine processing on
llApplyImpulse(<0.0, 10.0, 0.0>, FALSE); // Shove it forward with real force

Listening & Blue Menus

Want to give users choices? Blue pop-up menus are perfect for customization. To build one, you need two things working together: a llDialog() controller and a listen() event listener.

The Menu Blueprint

Here is how you spawn a menu and safely handle the choice the user clicks:

integer MENU_CHANNEL = -99284; // Use a hidden negative channel to stop spam

default
{
    touch_start(integer total)
    {
        key user = llDetectedKey(0);
        
        // Start listening to the hidden channel for just this user
        llListen(MENU_CHANNEL, "", user, "");
        
        llDialog(user, "Choose a color:", ["Red", "Blue", "Green"], MENU_CHANNEL);
    }

    listen(integer channel, string name, key id, string message)
    {
        if (message == "Red")   llSetColor(<1,0,0>, ALL_SIDES);
        if (message == "Blue")  llSetColor(<0,0,1>, ALL_SIDES);
        if (message == "Green") llSetColor(<0,1,0>, ALL_SIDES);
    }
}

Linkset Messaging: Modular Coding

When you link multiple prims together to build a complex object like a car, you do not want to load a separate script into every single piece. That causes heavy lag and makes updates impossible.

Instead, put one primary controller script in the root prim, and pass fast background signals to the child prims using llMessageLinked(). It is completely silent and near-instantaneous.

Root Controller Code:

// Put this in your main root prim
default
{
    touch_start(integer t)
    {
        llMessageLinked(LINK_ALL_OTHERS, 0, "LIGHTS_ON", NULL_KEY);
    }
}

VirtuaPrim LSL Code Checker

Paste your script into the engine below. Our backend will scan your code for missing brackets, bad states, misspelled functions, and mechanics that cause region lag.

Code Editor

Diagnostic Report

AWAITING INPUT
Ready. Hit Run Diagnostics to check your structural integrity.

VirtuaPrim Engine Glossary

Welcome to the ultimate quick reference guide. Use the instant search box below to filter down variables, types, events, and functions on the fly.

integer Data Type

Usage: integer numbers = 42;

A whole number with no decimals. Used for channels, loops, flags, and true/false switches.

float Data Type

Usage: float scale = 1.75;

A high-precision number that accepts decimal points. Essential for timers, physics weights, and coordinate math.

vector Data Type

Usage: vector position = <x, y, z>;

A structured group of three floats representing 3D coordinates, directional velocities, or RGB colors.

rotation Data Type

Usage: rotation rot = <x, y, z, s>;

A quaternion used to calculate 3D angles without gimbal lock. Very complex. Often generated using llEuler2Rot.

string Data Type

Usage: string word = "Hello";

A sequence of text characters. Always wrapped in double quotes.

list Data Type

Usage: list inventory = ["Apple", 5, <1,1,1>];

An array that can hold multiple pieces of data, even different types, inside square brackets.

key Data Type

Usage: key id = "1234-abcd-5678-efgh";

A specialized string that holds a UUID. Used to uniquely identify avatars, textures, and sounds.

state_entry() Event

Fires immediately when a state initializes, when the script resets, or when compiling finishes.

touch_start() Event

Parameters: (integer total_number)

Fires the exact frame an avatar clicks down on the primitive container box.

listen() Event

Parameters: (integer channel, string name, key id, string msg)

Triggers when the script overhears an active voice string or digital channel transmission.

timer() Event

An ongoing loop alarm. Fires repeatedly at regular clock intervals set by llSetTimerEvent.

changed() Event

Parameters: (integer change)

Fires when something about the object structurally changes. Useful for detecting when an avatar sits down or stands up.

on_rez() Event

Parameters: (integer start_param)

Fires immediately when the object is dragged from inventory into the 3D world.

attach() Event

Parameters: (key attached)

Fires when a user wears the object on their avatar body, or detaches it back to inventory.

run_time_permissions() Event

Parameters: (integer perm)

Fires after the script asks the user for permission to do something invasive, like trigger an animation or take money.

http_response() Event

Parameters: (key id, integer status, list meta, string body)

Fires when a web server responds to an llHTTPRequest sent by the script.

money() Event

Parameters: (key id, integer amount)

Fires when an avatar pays Linden Dollars (L$) to the object.

llSay() Function

Syntax: llSay(integer channel, string message);

Broadcasts text over a channel within a 20 meter radius.

llRegionSay() Function

Syntax: llRegionSay(integer channel, string message);

Broadcasts text across the entire simulator region. Cannot be used on channel zero.

llOwnerSay() Function

Syntax: llOwnerSay(string message);

Prints a private message directly into the chat console box of the object owner.

llSetText() Function

Syntax: llSetText(string text, vector color, float alpha);

Creates floating hover text directly above the prim.

llSetColor() Function

Syntax: llSetColor(vector color, integer face);

Changes the tint of the object surface.

llSetLinkPrimitiveParamsFast() Function

Syntax: llSetLinkPrimitiveParamsFast(integer link, list rules);

The ultimate powerhouse function. Instantly alters shape, position, rotation, texture, and color without a delay.

llRezObject() Function

Syntax: llRezObject(string inv_item, vector pos, vector vel, rotation rot, integer param);

Spawns a new item out of the object's inventory into the world. Used for guns shooting bullets or vendors delivering boxes.

llDie() Function

Syntax: llDie();

Instantly deletes the object from the simulator. Cannot be undone.

llSetTimerEvent() Function

Syntax: llSetTimerEvent(float sec);

Starts a recurring clock that triggers the timer event. Passing a zero stops the clock.

llListen() Function

Returns: integer handle

Opens a communication port to start hearing chat messages.

llListenControl() Function

Syntax: llListenControl(integer handle, integer active);

Temporarily mutes or unmutes an open listen handle to save simulator performance.

llGetPos() Function

Returns: vector coordinate

Returns the exact global 3D vector coordinate position of the primitive element.

llPreloadSound() Function

Syntax: llPreloadSound(string sound);

Forces nearby viewers to download a sound file before it plays, preventing audio lag.

llTargetOmega() Function

Syntax: llTargetOmega(vector axis, float spinrate, float gain);

Causes the object to spin smoothly on the viewer side without updating physics servers.

llHTTPRequest() Function

Returns: key request_id

Sends an HTTP request out to the real web to talk to external databases or APIs.

llGiveMoney() Function

Syntax: llGiveMoney(key id, integer amount);

Transfers Linden Dollars from the object owner's account directly to the specified avatar. Requires debit permissions.