What is an Event?
We covered this briefly in Class 1, but now you're ready for the full picture. LSL is an event-driven language. A script does nothing on its own — it waits. When something happens in-world, the simulator sends an event to the script, which wakes up and handles it.
An event is always written as a function inside a state block:
default
{
event_name(parameters)
{
// Code that runs when this event fires
}
}
The parameters vary by event — some pass the number of detected avatars, some pass a message string, some pass nothing at all. Let's work through the four most essential events.
touch_start
touch_start fires every time an avatar left-clicks your object in-world. Its parameter tells you how many avatars clicked simultaneously — almost always 1.
Inside the event, you can use llDetectedKey(0) and llDetectedName(0) to find out who clicked. The 0 is the index — the first (and usually only) detected avatar.
default
{
touch_start(integer num_detected)
{
key toucherKey = llDetectedKey(0);
string toucherName = llDetectedName(0);
llOwnerSay(toucherName + " touched this object.");
llOwnerSay("Their key: " + (string)toucherKey);
}
}
- touch_start
- Fires once when the click begins
- touch
- Fires repeatedly while the mouse button is held down
- touch_end
- Fires once when the click is released
listen
The listen event fires when your script hears a chat message on a channel it's registered to. First, you register with llListen. Then, whenever a matching message is heard, the event fires.
integer listenHandle;
default
{
state_entry()
{
// Listen on channel 42 for any speaker, any message
listenHandle = llListen(42, "", NULL_KEY, "");
llOwnerSay("Listening on channel 42.");
llOwnerSay("Type /42 hello in nearby chat to test.");
}
listen(integer channel, string name, key id, string message)
{
llOwnerSay(name + " said on ch." + (string)channel + ": " + message);
}
}
llListen returns a handle — an integer you save so you can remove the listener later. The four parameters to llListen are a filter: channel, speaker name, speaker key, and message. An empty string or NULL_KEY means "match anything."
Removing Listeners
Each active listener consumes resources on the simulator. Best practice is to remove listeners when you're done with them using llListenRemove.
integer listenHandle;
default
{
state_entry()
{
listenHandle = llListen(42, "", NULL_KEY, "");
}
listen(integer channel, string name, key id, string message)
{
llOwnerSay("Got: " + message);
// Remove the listener after the first message
llListenRemove(listenHandle);
llOwnerSay("Listener removed.");
}
}
Open listeners that you never remove keep running forever and contribute to sim lag. Always remove listeners you no longer need, especially in high-traffic areas.
collision_start
collision_start fires when a physical object (or avatar) collides with your object. It only works if your object has a physical component — either your object itself is physical, or you're using a phantom sensor trigger.
default
{
collision_start(integer num_detected)
{
string collideName = llDetectedName(0);
string collideType = "";
// AGENT = 1, meaning the collider is an avatar
if (llDetectedType(0) & AGENT)
{
collideType = "avatar";
}
else
{
collideType = "object";
}
llOwnerSay(collideName + " (" + collideType + ") hit this object.");
}
}
- collision_start
- Fires once at the moment of first contact
- collision
- Fires continuously while in contact
- collision_end
- Fires once when contact ends
timer
The timer event is your repeating heartbeat. You set it up with llSetTimerEvent(seconds) and it fires every N seconds until you stop it with llSetTimerEvent(0).
integer count = 0;
default
{
state_entry()
{
llSetTimerEvent(3.0); // Fire every 3 seconds
llOwnerSay("Timer started.");
}
timer()
{
count++;
llOwnerSay("Tick " + (string)count);
if (count >= 5)
{
llSetTimerEvent(0); // Stop the timer
llOwnerSay("Timer stopped after 5 ticks.");
}
}
}
Each script can only have one timer running at a time. Calling llSetTimerEvent again while a timer is running replaces it with the new interval.
Combining Events
The real power of LSL comes from combining multiple events in one script. Here's a complete example that uses touch, listen, and timer together:
integer listenHandle;
integer active = FALSE;
default
{
state_entry()
{
listenHandle = llListen(10, "", NULL_KEY, "");
llOwnerSay("Ready. Touch to start timer. Say /10 stop to stop.");
}
touch_start(integer num_detected)
{
if (!active)
{
active = TRUE;
llSetTimerEvent(2.0);
llOwnerSay("Timer started by " + llDetectedName(0));
}
}
timer()
{
llOwnerSay("Tick...");
}
listen(integer channel, string name, key id, string message)
{
if (message == "stop")
{
active = FALSE;
llSetTimerEvent(0);
llOwnerSay("Timer stopped by " + name);
}
}
}
Notice how the events all coexist neatly inside the same default state. Each one is independent but they share script-level variables like active and listenHandle.