LSL-202 Intermediate The School of Creation
Lead Instructor

Adam Berger - Advanced expert with 20+ years of experience in virtual worlds


All classes take place in Alife Virtual World at our dedicated Alife Virtual School region

LSL Object Manipulation - Movement, Rotation & Physics

Explore the immersive 3D world of Alife Virtual - Your free virtual world alternative

Explore the immersive 3D world of Alife Virtual - Your free virtual world alternative
High-resolution image (1920×1080 pixels) from Alife Virtual World School

Course Code: LSL-202 | The School of Creation

1. Course Overview

Welcome to LSL-202!

Welcome, scripters! If you've mastered the basics in LSL-101 and are ready to make things move, you're in the right place. This course is where your creations come to life. We'll be diving deep into the LSL functions that control an object's position, orientation, and physical behavior. From simple sliding doors to complex vehicles, the principles you learn here are the foundation of all interactive objects in Alife Virtual and other OpenSim grids.

This course is designed to be hands-on. You won't just read about scripting; you'll be writing code, testing it in-world, and building functional objects from scratch, all with zero cost for uploads or land use here at Alife Virtual School.

What You Will Master:

  • Moving objects smoothly and instantly using LSL functions.
  • Controlling object rotation with precision using quaternions and Euler angles.
  • Understanding and manipulating the OpenSim physics engine.
  • Making objects physical and responsive to collisions and forces.
  • Building practical, multi-part objects like elevators, vehicles, and animated displays.
  • Debugging and optimizing movement and physics scripts for performance.

Why is LSL Object Manipulation so Powerful?

Scripting is the soul of a virtual world. Without it, everything is static. By learning to manipulate objects, you gain the power to create immersive experiences, useful tools, and dynamic art. You can build a car that drives, a door that opens, a platform that carries avatars across a chasm, or a pet that follows you around. This skill set transforms you from a static builder into a true creator of interactive content.

Prerequisites Review

Prerequisite: LSL-101 (LSL Fundamentals). Before starting this course, you should be comfortable with:
  • The basic structure of an LSL script (states and events).
  • Declaring variables (integers, floats, strings, vectors).
  • Using common events like touch_start and state_entry.
  • Using conditional statements (if/else).
  • Writing basic output with llSay.

Meet Your Instructor

This course is led by Adam Berger, one of Alife Virtual School's senior instructors. With nearly 20 years of experience in LSL scripting and advanced building, Adam specializes in creating complex systems, vehicles, and interactive environments. He brings a wealth of practical knowledge and a passion for teaching others how to push the boundaries of what's possible in a virtual world.

2. Lesson 1: The Foundation - Absolute Positioning and Rotation

Theory & Concepts

The simplest way to move an object is to tell it exactly where to go. This is called absolute positioning. In LSL, we do this using coordinates within the region. An object's position is stored as a vector data type, which holds three float values: X, Y, and Z. Similarly, its rotation is stored as a rotation data type, which is a four-part number called a quaternion.

  • vector: Represents a point or direction in 3D space. Format: <X, Y, Z>. For example, <128.0, 128.0, 50.0> is the center of a region at an altitude of 50 meters.
  • rotation: Represents an object's orientation. While it's stored as a quaternion <x, y, z, s>, we often create it using easier-to-understand Euler angles (pitch, roll, yaw) and convert them using llEuler2Rot().

The two fundamental functions for this are llSetPos() and llSetRot().

Step-by-Step Tutorial: Creating a Simple "Up/Down" Platform

Let's create a platform that moves up when you touch it and resets to its original position when you touch it again. This demonstrates basic state management and absolute positioning.

  1. Create a prim (a simple cube is fine).
  2. Create a new script inside the prim's "Content" tab.
  3. Paste the following code into the script and save it.

Working Code Example:

// LSL-202 Lesson 1: Simple Up/Down Mover
// This script moves a platform up 5 meters when touched,
// and back down when touched again.

// Global variables to store the positions
vector gv_vStartPos;    // The starting position of the prim
vector gv_vEndPos;      // The target "up" position

default
{
    // The state_entry event runs once when the script starts
    // or when it is reset. It's perfect for initialization.
    state_entry()
    {
        // Get the prim's current position and store it.
        gv_vStartPos = llGetPos();
        
        // Calculate the "up" position by adding 5 meters to the Z-axis.
        // We add a vector <0, 0, 5> to the starting position.
        gv_vEndPos = gv_vStartPos + <0.0, 0.0, 5.0>;
        
        // Let the owner know the script is ready.
        llOwnerSay("Platform ready. Touch to move to the 'down' state.");
    }

    // The touch_start event runs when an avatar clicks on the prim.
    touch_start(integer vInt_total_number)
    {
        // When touched in the default state, it means we are at the
        // bottom. So, we move to the 'up' state.
        state up;
    }
}

// A new state to handle the "up" position.
state up
{
    state_entry()
    {
        // As soon as we enter this state, move the prim to the target position.
        llSetPos(gv_vEndPos);
        llOwnerSay("Platform is UP. Touch to move to the 'down' state.");
    }
    
    touch_start(integer vInt_total_number)
    {
        // When touched in the 'up' state, we want to go back down.
        // We achieve this by switching back to the 'default' state.
        state default;
    }
}

// We need another state_entry in the default state to handle
// the return trip. When we switch from 'up' back to 'default',
// the state_entry in 'default' runs again.
state default
{
    state_entry()
    {
        // This time, when we enter 'default' from the 'up' state,
        // we move the prim back to its starting position.
        llSetPos(gv_vStartPos);
        llOwnerSay("Platform is DOWN. Touch to move up.");
    }

    // We still need the touch event here to move back to the 'up' state.
    touch_start(integer vInt_total_number)
    {
        state up;
    }
}

Code Explanation: This script has two states: default (down position) and up (up position). When the script starts, it saves its initial position in gv_vStartPos and calculates the end position. Touching it switches the state. The state_entry() event in each state is what triggers the llSetPos() function, instantly moving the platform. Notice we have two `default` state blocks. LSL combines them. This is a common pattern for clarity, separating initial setup from the "return" logic.

3. Lesson 2: Smooth & Targeted Movement

Instant movement with llSetPos() can be jarring. For a better user experience, we need smooth, animated movement. LSL provides functions for this, primarily llMoveToTarget() and llRotLookAt(). These are more efficient than trying to create smooth movement manually with a timer.

Key Functions Explained

  • llMoveToTarget(vector target, float tau): This function smoothly moves an object towards a target position. tau is the "time constant" - a smaller number means faster movement. A good starting value is often around 1.0 to 2.0. The object will reach the target in approximately tau seconds.
  • llRotLookAt(rotation target, float strength, float damping): This smoothly rotates an object to face a target rotation. strength determines the speed, and damping prevents overshooting. Good starting values are strength = 10.0 and damping = 1.0.
  • llStopMoveToTarget() / llStopLookAt(): These functions cancel the smooth movement or rotation.
  • at_target / not_at_target events: These events fire when an object reaches (or fails to reach) its destination set by llMoveToTarget().

Working Code Example: A Security Camera

This script makes a prim act like a security camera. It will scan back and forth, but if an avatar comes within 10 meters, it will stop and look at them until they leave.

// LSL-202 Lesson 2: Security Camera
// Scans left and right, but stops to look at nearby avatars.

// --- CONFIGURATION ---
float gFlt_scanAngle = 45.0; // How far to scan left/right in degrees
float gFlt_scanSpeed = 1.0;  // Speed of the scanning rotation
float gFlt_trackSpeed = 10.0; // Speed of tracking an avatar
float gFlt_sensorRange = 10.0; // How far away to detect avatars

// --- GLOBALS ---
rotation gRot_center;   // The initial, forward-facing rotation
rotation gRot_left;     // The calculated left-most rotation
rotation gRot_right;    // The calculated right-most rotation
integer gInt_isScanning = TRUE; // A flag to control behavior

default
{
    state_entry()
    {
        llOwnerSay("Security Camera Initializing...");
        
        // Store the initial rotation
        gRot_center = llGetRot();
        
        // Calculate the left and right scan rotations
        // We use llEuler2Rot to convert degrees into a rotation
        // PI*2 is a full circle in radians, so we convert degrees to radians.
        float angleInRadians = gFlt_scanAngle * DEG_TO_RAD;
        gRot_left = llEuler2Rot(<0, 0, angleInRadians>) * gRot_center;
        gRot_right = llEuler2Rot(<0, 0, -angleInRadians>) * gRot_center;

        // Activate the sensor to detect avatars
        llSensorRepeat("", NULL_KEY, AGENT, gFlt_sensorRange, PI, 0.2);
        
        // Start scanning to the right
        llRotLookAt(gRot_right, gFlt_scanSpeed, 1.0);
        llOwnerSay("Scanning...");
    }

    // This event fires when the sensor finds something
    sensor(integer vInt_num_detected)
    {
        gInt_isScanning = FALSE; // Stop scanning
        
        // Get the position of the detected avatar (the first one found)
        vector detectedPos = llDetectedPos(0);
        
        // Calculate the rotation needed to look at that position
        vector myPos = llGetPos();
        rotation lookAtRot = llLookAt(detectedPos - myPos, <0,0,1>, <0,0,1>);
        
        // Smoothly rotate to look at the avatar
        llRotLookAt(lookAtRot, gFlt_trackSpeed, 2.0);
        llOwnerSay("Target detected! Tracking...");
    }

    // This event fires when the sensor no longer finds anything
    no_sensor()
    {
        if (!gInt_isScanning) // Only restart if we weren't already scanning
        {
            gInt_isScanning = TRUE;
            llOwnerSay("Target lost. Resuming scan.");
            // Go back to scanning right
            llRotLookAt(gRot_right, gFlt_scanSpeed, 1.0);
        }
    }
    
    // This event fires when a llRotLookAt() completes
    at_rot_target()
    {
        // If we are in scanning mode, this means we reached a scan point
        if (gInt_isScanning)
        {
            // Check which direction we are currently facing
            // llAngleBetween checks the difference between two rotations
            if (llAngleBetween(llGetRot(), gRot_right) < 0.1)
            {
                // We are at the right, so now scan left
                llRotLookAt(gRot_left, gFlt_scanSpeed, 1.0);
            }
            else
            {
                // We are at the left, so now scan right
                llRotLookAt(gRot_right, gFlt_scanSpeed, 1.0);
            }
        }
    }
}

Code Explanation: This script uses a sensor to detect avatars. When one is found, it uses llLookAt() to calculate the required rotation and llRotLookAt() to smoothly turn. When the avatar leaves, the no_sensor event fires, and it resumes its patrol. The at_rot_target event is used to make the camera scan back and forth when no target is present.

4. Lesson 3: Harnessing the Physics Engine

So far, we've been moving objects in a "non-physical" way. They pass through walls and other objects. To create vehicles, projectiles, or objects that can be pushed, we must enable their physical property and use physics-based functions.

First, in the editor, you must check the "Physical" box in the "Object" tab of the build floater. A physical object is affected by gravity and can collide with other objects.

Physics Functions and Vehicle Types

LSL gives us a powerful set of "vehicle" parameters that pre-configure an object's physical behavior. This is much easier than setting all the physics properties manually.

  • llSetVehicleType(integer type): Sets the object's physics model. Common types are VEHICLE_TYPE_NONE, VEHICLE_TYPE_SLED, VEHICLE_TYPE_CAR, and VEHICLE_TYPE_BOAT.
  • llSetVehicleFlags(integer flags): Modifies the vehicle's behavior. For example, VEHICLE_FLAG_HOVER_UP_ONLY for a hovercraft or VEHICLE_FLAG_NO_DEFLECTION_UP to prevent a car from flying into the air on bumps.
  • llSetVehicleVectorParam(integer param, vector value): Sets vector-based parameters, like VEHICLE_LINEAR_FRICTION_TIMESCALE.
  • llSetVehicleFloatParam(integer param, float value): Sets float-based parameters, like VEHICLE_ANGULAR_MOTOR_TIMESCALE.
  • llApplyImpulse(vector force, integer local): Pushes the object with a sudden force. This is great for jumping or launching projectiles.
  • llApplyRotationalImpulse(vector force, integer local): Pushes the object into a spin.

Working Code Example: A Simple "Jump Pad"

This script will turn a prim into a jump pad. When an avatar steps on it, it will fling them (and itself, since it's physical) into the air.

// LSL-202 Lesson 3: Physical Jump Pad
// Remember to set the prim to "Physical" in the build editor!

// The force of the jump. Higher numbers mean a higher jump.
vector gv_vJumpForce = <0, 0, 500.0>; 

default
{
    state_entry()
    {
        llOwnerSay("Jump Pad Active. Step on me!");
        
        // We set the object to be physical via script as well.
        // This is good practice. STATUS_PHYSICS is a constant.
        llSetStatus(STATUS_PHYSICS, TRUE);
        
        // This makes the prim "phantom" so avatars don't get stuck on it,
        // but it will still register collisions.
        llSetPhantom(TRUE);
    }
    
    // The collision_start event fires when a physical object
    // bumps into something else.
    collision_start(integer vInt_num_detected)
    {
        // We need to check if what we hit was an avatar.
        if (llDetectedType(0) & AGENT)
        {
            llOwnerSay("JUMP!");
            
            // Apply a strong upward impulse to the prim.
            // Since the avatar is standing on the prim, they will
            // be carried along for the ride!
            // The 'FALSE' means the force is applied in world coordinates.
            llApplyImpulse(gv_vJumpForce, FALSE);
        }
    }
    
    // This event fires when the prim stops colliding with something.
    collision_end(integer vInt_num_detected)
    {
        // Optional: Could add a sound effect or particle burst here.
    }
}

Code Explanation: This script uses the collision_start event. This event only works on physical objects. When it detects a collision with an AGENT (an avatar), it uses llApplyImpulse to apply a massive upward force. Because the object is physical, it shoots into the air, carrying the avatar with it before gravity brings it back down.

5. Lesson 4: Practical Application - Building an Elevator

It's time to combine everything we've learned: states, smooth movement, and user interaction. We will build a complete, working elevator that travels between two floors. This is a classic LSL project that demonstrates many core concepts.

This script will go in the main elevator platform. You will also need two "call" buttons, one for each floor. We will use llListen() to make the elevator respond to the buttons.

Combining Concepts for a Complex Object

Our elevator will need:

  • States to know if it's at the top, bottom, or moving.
  • llMoveToTarget() for smooth travel.
  • llListen() to receive commands from call buttons.
  • llSay() on a specific channel to communicate.

Complete Script: The Elevator Platform

// LSL-202 Lesson 4: Elevator Platform Script

// --- CONFIGURATION ---
integer gInt_comChannel = 101;  // Communication channel. Buttons must use the same channel.
float   gFlt_travelSpeed = 3.0; // Time in seconds to travel between floors.
float   gFlt_floorOffset = 10.0; // How many meters to move up.

// --- GLOBALS ---
vector  gv_vBottomPos;
vector  gv_vTopPos;

// --- STATES ---
// 'bottom' state: The elevator is at the bottom floor.
// 'top' state: The elevator is at the top floor.
// 'moving' state: The elevator is in transit.

default
{
    state_entry()
    {
        // Go to the 'bottom' state to initialize.
        state bottom;
    }
}

state bottom
{
    state_entry()
    {
        llOwnerSay("Elevator is at the bottom floor.");
        
        // Set up the listener for the call buttons
        llListen(gInt_comChannel, "", NULL_KEY, "");
        
        // Define the floor positions
        gv_vBottomPos = llGetPos();
        gv_vTopPos = gv_vBottomPos + <0, 0, gFlt_floorOffset>;
        
        // Ensure we are at the correct position
        llSetPos(gv_vBottomPos);
        llStopMoveToTarget(); // Stop any previous movement
    }
    
    listen(integer channel, string name, key id, string message)
    {
        // If we get a "go_up" command and we are at the bottom...
        if (message == "go_up")
        {
            // Switch to the 'moving' state to begin travel.
            state moving;
        }
    }
    
    // If the script is reset, go back to the default state for re-initialization.
    on_rez(integer start_param) { llResetScript(); }
}

state top
{
    state_entry()
    {
        llOwnerSay("Elevator is at the top floor.");
        llListen(gInt_comChannel, "", NULL_KEY, "");
        
        // Ensure we are at the correct position
        llSetPos(gv_vTopPos);
        llStopMoveToTarget();
    }
    
    listen(integer channel, string name, key id, string message)
    {
        // If we get a "go_down" command and we are at the top...
        if (message == "go_down")
        {
            state moving;
        }
    }
    
    on_rez(integer start_param) { llResetScript(); }
}

state moving
{
    state_entry()
    {
        llOwnerSay("Elevator is now moving.");
        llListenRemove(llGetListenHandle()); // Stop listening while moving
        
        // Which way are we going? Check our current position.
        // llVecDist measures the distance between two vectors.
        if (llVecDist(llGetPos(), gv_vBottomPos) < 0.1)
        {
            // We are at the bottom, so move to the top
            llMoveToTarget(gv_vTopPos, gFlt_travelSpeed);
        }
        else
        {
            // We must be at the top, so move to the bottom
            llMoveToTarget(gv_vBottomPos, gFlt_travelSpeed);
        }
    }
    
    // This event fires when we arrive at our destination
    at_target()
    {
        // Where did we arrive?
        if (llVecDist(llGetPos(), gv_vTopPos) < 0.1)
        {
            // We have arrived at the top.
            state top;
        }
        else
        {
            // We have arrived at the bottom.
            state bottom;
        }
    }
    
    on_rez(integer start_param) { llResetScript(); }
}

The Call Button Script

Create two small prims for buttons. Place one at the bottom floor and one at the top. Put this script in both, but change the gStr_messageToSend variable for each one.

// LSL-202 Lesson 4: Elevator Call Button Script

// --- CONFIGURATION ---
integer gInt_comChannel = 101;        // MUST match the elevator script's channel
string  gStr_messageToSend = "go_up"; // Change to "go_down" for the top button

default
{
    state_entry()
    {
        llSetText("Call Elevator", <1,1,1>, 1.0);
    }
    
    touch_start(integer total_number)
    {
        // When touched, say the command on the channel.
        llSay(gInt_comChannel, gStr_messageToSend);
        llSetText("Called!", <1,1,0>, 1.0);
        
        // Reset the button text after a few seconds
        llSetTimerEvent(3.0);
    }
    
    timer()
    {
        llSetText("Call Elevator", <1,1