Recipe 5.1: Car | Heaton Research

Recipe 5.1: Car

Get the entire book!
Scripting Recipes for Second Life

    The example car for this recipe is a bright-red two seater convertible. The car is not a true convertible, in that it does not convert. It is always in top-down mode. Because it never rains in Second Life, this is not a problem! The little red sports car is shown in Figure 5.2.

Figure 5.2: A Car in Second Life

A Car in Second Life

    The next few sections will explain different aspects of the car, and how it was constructed and scripted.

Vehicle Materials

    All prims in Second Life have a material. The following materials are supported in Second Life: stone, metal, glass, wood, flesh, plastic and rubber. Materials affect the mass and friction of the vehicle. The majority of prims in Second Life are made of wood. This is because wood is the default material. Material type is specified in the object window. Figure 5.3 shows the material type being set.

Figure 5.3: Setting the Material Type

Setting the Material Type

    Material types are very important for vehicles in Second Life. The material type for the tires of the car is of particular importance. Which of the above material types should be chosen for the tires? It may seem that rubber is the logical choice. Rubber would create too much friction. Remember, that in the simplistic physics of Second Life, the tires do not really turn. They appear to turn, due to a trick used later in this recipe. But they are not really turning. Imagine a parked car being pushed along the ground. If the parking brake is on, that car will not move as well. The rubber would burn and the car would merely bump along. This is what happens when the tires are made of rubber in Second Life. The car barely moves.

    The material of choice is surprising. It is the material with the least friction - glass. Any part of the vehicle that comes into contact with the ground should be made of glass. Durability is not an issue with a prim! Glass wheels are the norm in Second Life. Don't think of the materials as the actual materials. Rather, think of the material types as specifying the amount of friction the prim will cause.

The Root Prim

     The car, like any other object in Second Life, consists of several primitives. However, not all primitives are equal. The most important primitive, or prim, is the root prim. When an object is selected, the root prim is shown outlined in yellow. The root prim is a very important concept for vehicles.

    The root prim is the last prim selected when the vehicle was linked together. Therefore, it is very easy to accidentally change the root prim when new prims are being added to the object. For example, consider adding a bumper sticker to the car. Consider if the car was selected, the bumper sticker shift-selected and a link created. The bumper sticker would now be a part of the car object. However, the bumper sticker would now be the root prim! The bumper sticker was the last prim selected, so it is now the root prim. This would prevent the car from functioning properly. This is because the car is designed for the driver's seat to be the root prim.

    The correct way to add the bumper sticker to the car would be to do the procedure described above in reverse. First, select the bumper sticker. Then shift-select the car and create the link. Now the car is the last object selected and the root prim will not change.

    The root primitive is critical to a vehicle because the root prim is where the main vehicle script resides. Think of the root prim as the motor for the vehicle. It is the root prim that is moving, everything else is only attached to the root prim. It is also convenient if the driver sits on the root prim. This is why most vehicles in Second Life always make the root prim the driver's seat.

    It also makes vehicle creation considerably easier if the root prim is at zero rotation in all three directions. At the very least, the root prim should only be rotated in 90-degree intervals in the three dimensions.

    The root prim must be made a physical object for the vehicle to operate. Normally, this is done in code using a call to llSetStatus. However, being physical imposes an important limitation on vehicles. Physical objects in Second Life can contain no more than 31 prims. Because of this, no vehicle can contain more than 31 prims. If prim number 32 is added, the vehicle will stop.

    The root prim is where the main car script resides. The car script can be seen in Listing 5.1.

Listing 5.1: Main Car Script for the Root Prim (Car.lsl)

// From the book:
//
// Scripting Recipes for Second Life
// by Jeff Heaton (Encog Dod in SL)
// ISBN: 160439000X
// Copyright 2007 by Heaton Research, Inc.
//
// This script may be freely copied and modified so long as this header
// remains unmodified.
//
// For more information about this book visit the following web site:
//
// http://www.heatonresearch.com/articles/series/22/

float forward_power = 15; //Power used to go forward (1 to 30)
float reverse_power = -15; //Power ued to go reverse (-1 to -30)
float turning_ratio = 2.0; //How sharply the vehicle turns. Less is more sharply. (.1 to 10)
string sit_message = "Ride"; //Sit message
string not_owner_message = "You are not the owner of this vehicle ..."; //Not owner message

default
{
    state_entry()
    {
        llSetSitText(sit_message);
        // forward-back,left-right,updown
        llSitTarget(<0.2,0,0.45>, ZERO_ROTATION );
        
        llSetCameraEyeOffset(<-8, 0.0, 5.0>);
        llSetCameraAtOffset(<1.0, 0.0, 2.0>);
        
        llPreloadSound("car_start");
        llPreloadSound("car_run");
        
        //car
        llSetVehicleType(VEHICLE_TYPE_CAR);
        llSetVehicleFloatParam(VEHICLE_ANGULAR_DEFLECTION_EFFICIENCY, 0.2);
        llSetVehicleFloatParam(VEHICLE_LINEAR_DEFLECTION_EFFICIENCY, 0.80);
        llSetVehicleFloatParam(VEHICLE_ANGULAR_DEFLECTION_TIMESCALE, 0.10);
        llSetVehicleFloatParam(VEHICLE_LINEAR_DEFLECTION_TIMESCALE, 0.10);
        llSetVehicleFloatParam(VEHICLE_LINEAR_MOTOR_TIMESCALE, 1.0);
        llSetVehicleFloatParam(VEHICLE_LINEAR_MOTOR_DECAY_TIMESCALE, 0.2);
        llSetVehicleFloatParam(VEHICLE_ANGULAR_MOTOR_TIMESCALE, 0.1);
        llSetVehicleFloatParam(VEHICLE_ANGULAR_MOTOR_DECAY_TIMESCALE, 0.5);
        llSetVehicleVectorParam(VEHICLE_LINEAR_FRICTION_TIMESCALE, <1000.0, 2.0, 1000.0>);
        llSetVehicleVectorParam(VEHICLE_ANGULAR_FRICTION_TIMESCALE, <10.0, 10.0, 1000.0>);
        llSetVehicleFloatParam(VEHICLE_VERTICAL_ATTRACTION_EFFICIENCY, 0.50);
        llSetVehicleFloatParam(VEHICLE_VERTICAL_ATTRACTION_TIMESCALE, 0.50);
    }
    
    changed(integer change)
    {
        
        
        if (change & CHANGED_LINK)
        {
            
            key agent = llAvatarOnSitTarget();
            if (agent)
            {                
                if (agent != llGetOwner())
                {
                    llSay(0, not_owner_message);
                    llUnSit(agent);
                    llPushObject(agent, <0,0,50>, ZERO_VECTOR, FALSE);
                }
                else
                {
                    llTriggerSound("car_start",1);
                    
                    
                    llMessageLinked(LINK_ALL_CHILDREN , 0, "WHEEL_DRIVING", NULL_KEY);
                    llSleep(.4);
                    llSetStatus(STATUS_PHYSICS, TRUE);
                    llSleep(.1);
                    llRequestPermissions(agent, PERMISSION_TRIGGER_ANIMATION | PERMISSION_TAKE_CONTROLS);

                    llLoopSound("car_run",1);
                }
            }
            else
            {
                llStopSound();
                
                llSetStatus(STATUS_PHYSICS, FALSE);
                llSleep(.4);
                llReleaseControls();
                llTargetOmega(<0,0,0>,PI,0);
                
                llResetScript();
            }
        }
        
    }
    
    run_time_permissions(integer perm)
    {
        if (perm)
        {
            llTakeControls(CONTROL_FWD | CONTROL_BACK | CONTROL_DOWN | CONTROL_UP | CONTROL_RIGHT | 
                            CONTROL_LEFT | CONTROL_ROT_RIGHT | CONTROL_ROT_LEFT, TRUE, FALSE);
        }
    }
    
    control(key id, integer level, integer edge)
    {
        integer reverse=1;
        vector angular_motor;
        
        //get current speed
        vector vel = llGetVel();
        float speed = llVecMag(vel);

        //car controls
        if(level & CONTROL_FWD)
        {
            llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, <forward_power,0,0>);
            reverse=1;
        }
        if(level & CONTROL_BACK)
        {
            llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, <reverse_power,0,0>);
            reverse = -1;
        }

        if(level & (CONTROL_RIGHT|CONTROL_ROT_RIGHT))
        {
            angular_motor.z -= speed / turning_ratio * reverse;
        }
        
        if(level & (CONTROL_LEFT|CONTROL_ROT_LEFT))
        {
            angular_motor.z += speed / turning_ratio * reverse;
        }

        llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION, angular_motor);

    } //end control   
    
    
} //end default

    The following sections explain the various parts of the car script.

Obtaining Permission

    The car will be driven in a similar way to how an avatar walks. Cursor keys will turn and move it forward and backward. However, for a script to do this, it must get permission from the avatar. This is done with the run_time_permissions event handler. This event handler is shown here.

run_time_permissions(integer perm)
{
	if (perm)
	{
		llTakeControls(CONTROL_FWD | CONTROL_BACK |
		CONTROL_DOWN | CONTROL_UP | CONTROL_RIGHT | 
     	CONTROL_LEFT | CONTROL_ROT_RIGHT | 
		CONTROL_ROT_LEFT, TRUE, FALSE);
	}
}

    The same event handler is used for all of the vehicles in this chapter.

Sitting Down as the Driver

    When an avatar sits down to drive the car, the car must perform a setup before the avatar can begin driving the car. The changed event handler is called when an avatar sits on an object. First, the changed event handler checks to see whether it was called because an object was linked to it. In this case, it was the avatar that was linked to the car object.

changed(integer change)
{        
	if (change & CHANGED_LINK)
	{

    Next, the script checks to see what avatar sat on it. If it was an avatar that sat on the car, the car checks to see whether the avatar is the car's owner.

		key agent = llAvatarOnSitTarget();
		if (agent)
		{                
			if (agent != llGetOwner())
			{

    If it is not the car's owner, the car informs the avatar that they are not allowed to drive the car. The avatar is pushed away.

				llSay(0, not_owner_message);
				llUnSit(agent);
				llPushObject(agent, <0,0,50>, ZERO_VECTOR, FALSE);
			}

    If it is the car's owner, it is time to set up the car so that it can be driven. First, the car_start sound is played. The car is then enabled as a physical object. A physical object can be pushed by external or internal forces. Permission to take the controls is then requested. Finally, the car_run sound is looped.

			else
			{
				llTriggerSound("car_start",1);                    
				llSleep(.4);
                    llSetStatus(STATUS_PHYSICS, TRUE);
                    llSleep(.1);
                    llRequestPermissions(agent, PERMISSION_TRIGGER_ANIMATION | PERMISSION_TAKE_CONTROLS);

				llLoopSound("car_run",1);
			}
		}

    If the avatar is getting up, stop the sound and turn off physics. Controls are released. If physics are left on, any avatar who bumped into the parked car would move it.

		else
		{
			llStopSound();
                
			llSetStatus(STATUS_PHYSICS, FALSE);
			llSleep(.1);
			llSleep(.4);
			llReleaseControls();
			llTargetOmega(<0,0,0>,PI,0);            
			llResetScript();
		}
	}
}

    The call to llTargetOmega is very important. Without this call, the parked car will sometimes begin to rotate. This is a very strange, undesirable effect.

    This script is very similar to the changed script used for all other vehicles in this chapter. The only changes will be in the sounds played and in the case of the helicopter starting, the blades rotating. The boat will also start a “wave trail” behind it. These differences will be covered in the other recipes in this chapter.

Controlling the Car

    The control event handler is called when the user interacts with the control keys. The control keys are the cursor keys and page up/down, as well as other control keys. The car will only use the cursor keys.

    Vehicles in Second Life are moved by two motors; the linear motor and the angular motor. The linear motor can move the vehicle in any direction in the x, y and z coordinate planes. The angular motor can rotate the object in any of the x, y and z coordinate planes. The car uses both motors. The linear motor moves the car forwards and backwards. The angular motor turns the car.

    The control event handler begins by setting up some variables that will be needed by the handler. Because cars turn differently when in reverse, a flag is required to indicate if we are in reverse. Also, a variable is created to hold the direction of the angular motor.

control(key id, integer level, integer edge)
{
	integer reverse=1;
	vector angular_motor;

    The current speed is obtained. This will be used with turning. Cars need to be in motion to turn.

	//get current speed
	vector vel = llGetVel();
	float speed = llVecMag(vel);

    Next, each of the relevant controls will be checked. The first control to be checked is the forward control. When the user presses forward, the linear motor is used to apply the force to move the car forward. Note also that the car is moving forward by setting the reverse variable to one.

	//car controls
	if(level & CONTROL_FWD)
	{
		llSetVehicleVectorParam(
	VEHICLE_LINEAR_MOTOR_DIRECTION, <forward_power,0,0>);
		reverse=1;
	}

    If the user presses back, apply power in the opposite direction. Note also that the car has been put in reverse by setting the reverse variable to -1.

	if(level & CONTROL_BACK)
	{
		llSetVehicleVectorParam(
	VEHICLE_LINEAR_MOTOR_DIRECTION, <reverse_power,0,0>);
		reverse = -1;
	}

    For a right turn, rotate the car in the z-coordinate. Rotate by the specified angle and take into account whether the car is going in reverse.

	if(level & (CONTROL_RIGHT|CONTROL_ROT_RIGHT))
	{
		angular_motor.z -= speed / 
			turning_ratio * reverse;
	}

    For a left turn, rotate the car in the z-coordinate. Rotate by the specified angle and take into account whether the car is going in reverse.

	if(level & (CONTROL_LEFT|CONTROL_ROT_LEFT))
	{
		angular_motor.z += speed / 
			turning_ratio * reverse;
	}

    Now the angular motor can be set.

	llSetVehicleVectorParam(
		VEHICLE_ANGULAR_MOTOR_DIRECTION, angular_motor);

} 

    The control script is different for each vehicle type. This is because each vehicle handles differently. However, each vehicle shares some similarity in the control event handler.

Initializing the Car

    The state_entry event handler initializes the car. Initialization is very different for each vehicle type. The car begins by setting the sit text and sit target.

    state_entry()
    {
        llSetSitText(sit_message);
        // forward-back,left-right,updown
        llSitTarget(<0.2,0,0.45>, ZERO_ROTATION );

    Next, the camera is placed. The camera is offset behind and above the car. Now the camera looks into the car.

        llSetCameraEyeOffset(<-8, 0.0, 5.0>);
        llSetCameraAtOffset(<1.0, 0.0, 2.0>);

    The two sounds that are used are preloaded. This prevents any pause when the sounds are played for the first time.

        llPreloadSound("car_start");
        llPreloadSound("car_run");

    Next the vehicle parameters are set. The first is the vehicle type, which is set by calling llSetVehicleType. Valid values for llSetVehicleType are listed in Table 5.1.

Table 5.1: Vehicle Types

Vehicle Type Purpose
VEHICLE_TYPE_NONE Not a vehicle.
VEHICLE_TYPE_SLED Simple vehicle that bumps along the ground, has a tendency to move along its local x-axis.
VEHICLE_TYPE_CAR Vehicle that bounces along the ground but requires motors to be driven from external controls or other source.
VEHICLE_TYPE_BOAT Hovers over water with a great deal of friction and some angular deflection.
VEHICLE_TYPE_AIRPLANE Uses linear deflection for lift, no hover, and must bank to turn.
VEHICLE_TYPE_BALLOON Hover, and friction, and no deflection.

    Additionally vehicle parameters are set using the llSetVehicleFloatParam, llSetVehicleVectorParam and llSetVehicleRotationParam function calls. Table 5.2 summarizes the values that can be set with the llSetVehicleFloatParam.

Table 5.2: Floating Point Vehicle Parameters

Parameter Purpose
VEHICLE_ANGULAR_DEFLECTION_EFFICIENCY Value between 0 (no deflection) and 1 (maximum strength).
VEHICLE_ANGULAR_DEFLECTION_TIMESCALE Exponential timescale for the vehicle to achieve full angular deflection.
VEHICLE_ANGULAR_MOTOR_DECAY_TIMESCALE Exponential timescale for the angular motor's effectiveness to decay toward zero.
VEHICLE_ANGULAR_MOTOR_TIMESCALE Exponential timescale for the vehicle to achieve its full angular motor velocity.
VEHICLE_BANKING_EFFICIENCY Value between -1 (leans out of turns), 0 (no banking), and +1 (leans into turns).
VEHICLE_BANKING_MIX Value between 0 (static banking) and 1 (dynamic banking).
VEHICLE_BANKING_TIMESCALE Exponential timescale for the banking behavior to take full effect.
VEHICLE_BUOYANCY Value between -1 (double-gravity) and 1 (full anti-gravity).
VEHICLE_HOVER_HEIGHT Height at which the vehicle will try to hover.
VEHICLE_HOVER_EFFICIENCY Value between 0 (bouncy) and 1 (critically damped) hover behavior.
VEHICLE_HOVER_TIMESCALE The period of time for the vehicle to achieve its hover height.
VEHICLE_LINEAR_DEFLECTION_EFFICIENCY Value between 0 (no deflection) and 1 (maximum strength).
VEHICLE_LINEAR_DEFLECTION_TIMESCALE An exponential timescale for the vehicle to redirect its velocity along its x-axis.
VEHICLE_LINEAR_MOTOR_DECAY_TIMESCALE An exponential timescale for the linear motor's effectiveness to decay toward zero.
VEHICLE_LINEAR_MOTOR_TIMESCALE An exponential timescale for the vehicle to achieve its full linear motor velocity.
VEHICLE_VERTICAL_ATTRACTION_EFFICIENCY Value between 0 (bouncy) and 1 (critically damped) attraction of vehicle z-axis to world z-axis (vertical).
VEHICLE_VERTICAL_ATTRACTION_TIMESCALE An exponential timescale for the vehicle to align its z-axis to the world z-axis (vertical).

    Table 5.3 summarizes the values that can be set with the llSetVehicleVectorParam.

Table 5.3: Vector Vehicle Parameters

Parameter Purpose
VEHICLE_ANGULAR_FRICTION_TIMESCALE The vector of timescales for exponential decay of angular velocity about the three vehicle axes.
VEHICLE_ANGULAR_MOTOR_DIRECTION The angular velocity that the vehicle will try to achieve.
VEHICLE_LINEAR_FRICTION_TIMESCALE The vector of timescales for exponential decay of linear velocity along the three vehicle axes.
VEHICLE_LINEAR_MOTOR_DIRECTION The linear velocity that the vehicle will try to achieve.
VEHICLE_LINEAR_MOTOR_OFFSET The offset from the center of mass of the vehicle where the linear motor is applied.

    Table 5.4 summarizes the values that can be set with the llSetVehicleRotationParam.

Table 5.4: Rotation Point Vehicle Parameters

Parameter Purpose
VEHICLE_REFERENCE_FRAME The rotation of vehicle axes relative to local frame. Useful for when the root prim must be rotated.

    The settings for the vehicle parameters of the car will now be reviewed. First, the vehicle type is set to car. Angular deflection is the tendency of a vehicle to move in certain directions. For example, a car will not tend to move in the z-coordinate (up and down). The angular deflection efficiency determines how effective angular deflection is. A value of 0.2 specifies angular deflection at 20%. This allows the car to turn fairly easily.

llSetVehicleType(VEHICLE_TYPE_CAR);
llSetVehicleFloatParam(
VEHICLE_ANGULAR_DEFLECTION_EFFICIENCY, 0.2);

    A value of 0.8 specifies that linear deflection has 80% power. This means it takes more effort for the car to change its linear velocity.

llSetVehicleFloatParam(
VEHICLE_LINEAR_DEFLECTION_EFFICIENCY, 0.80);

    It takes the car one tenth of a second for both linear and angular deflection to commence.

llSetVehicleFloatParam(
VEHICLE_ANGULAR_DEFLECTION_TIMESCALE, 0.10);
llSetVehicleFloatParam(
VEHICLE_LINEAR_DEFLECTION_TIMESCALE, 0.10);

    It takes one second for the linear motor to reach full power.

llSetVehicleFloatParam(
VEHICLE_LINEAR_MOTOR_TIMESCALE, 1.0);

    The linear motor will drop off in one fifth of a second. The car will not coast well.

llSetVehicleFloatParam(
VEHICLE_LINEAR_MOTOR_DECAY_TIMESCALE, 0.2);

    The angular motor will reach full power in one tenth of a second.

llSetVehicleFloatParam(
VEHICLE_ANGULAR_MOTOR_TIMESCALE, 0.1);

    The angular motor will drop off in 0.5 seconds. The car will stop turning fairly quickly when the user lets up on the control.

llSetVehicleFloatParam(
VEHICLE_ANGULAR_MOTOR_DECAY_TIMESCALE, 0.5);

    Friction affects the car only in the y-coordinate, which is how the car moves forwards and backwards. The car can quickly fall or turn.

llSetVehicleVectorParam(
VEHICLE_LINEAR_FRICTION_TIMESCALE, <1000.0, 2.0, 1000.0>);

    The car rotates fairly easily in the z-coordinate, but x and y are more difficult to rotate in.

llSetVehicleVectorParam(
VEHICLE_ANGULAR_FRICTION_TIMESCALE, <10.0, 10.0, 1000.0>);

    A car should always stay right-side-up. The vertical attraction feature allows this.

llSetVehicleFloatParam(
VEHICLE_VERTICAL_ATTRACTION_EFFICIENCY, 0.50);
llSetVehicleFloatParam(
VEHICLE_VERTICAL_ATTRACTION_TIMESCALE, 0.50);   

    These values work well for a car. However, they will be considerably different for a boat or helicopter.

Who Sits Where

    The car allows for one passenger, in addition to the driver. Additional passengers will be ejected. Figure 5.4 shows the car with a driver and one passenger.

Figure 5.4: A Car with Two Passengers

A Car with Two Passengers

    Extra seats must be provided to allow additional people, other than the driver to ride in a vehicle. The passenger seat has a simple llSitTarget function call in its state_entry event handler. The passenger seat can be seen here.

Listing 5.2: Car Passenger Seat (CarSeat.lsl)

// From the book:
//
// Scripting Recipes for Second Life
// by Jeff Heaton (Encog Dod in SL)
// ISBN: 160439000X
// Copyright 2007 by Heaton Research, Inc.
//
// This script may be freely copied and modified so long as this header
// remains unmodified.
//
// For more information about this book visit the following web site:
//
// http://www.heatonresearch.com/articles/series/22/

default
{
    state_entry()
    {
        llSitTarget(<0.2,0,0.45>, ZERO_ROTATION );
    }
}

    The driver's seat should be the root prim, which is the last prim selected. The passenger's seat should be the second to the last prim selected. A third script is also required, to disallow further seating. The third to the last prim selected should contain a script that prevents the user from sitting down. Such a script can be seen in Listing 5.3.

Listing 5.3: Can't Sit Here (DontSitHere.lsl)

// From the book:
//
// Scripting Recipes for Second Life
// by Jeff Heaton (Encog Dod in SL)
// ISBN: 160439000X
// Copyright 2007 by Heaton Research, Inc.
//
// This script may be freely copied and modified so long as this header
// remains unmodified.
//
// For more information about this book visit the following web site:
//
// http://www.heatonresearch.com/articles/series/22/

default
{
    state_entry()
    {
        llSitTarget(<0.2,0,0.45>, ZERO_ROTATION );
    }

    changed(integer change)
    {
        
        
        if (change & CHANGED_LINK)
        {
            key agent = llAvatarOnSitTarget();
            if (agent)
            {     
                llUnSit(agent);
                llSay(0,"Sorry, this vehicle is full.");
            }
        }
    }
}

    The “don't sit here” script is needed because an avatar will try to choose a seat in the following order.

  • If the exact prim selected can be sat on, choose it
  • Next, try to sit on the root prim
  • Next, try to sit on the prim selected just before the root prim
  • Next, try to sit on the prim selected two before the root prim and so on

    Because of this, the “chain” of sit targets must be broken just beyond the last passenger seat. The do not sit here script specifies a sit target in the state_entry, as for the passenger seat:

default
{
    state_entry()
    {
        llSitTarget(<0.2,0,0.45>, ZERO_ROTATION );
    }

    Whenever the changed event handler is called, the avatar should be ejected with the llUnSit function call.

    changed(integer change)
    {
        if (change & CHANGED_LINK)
        {
            key agent = llAvatarOnSitTarget();
            if (agent)
            {     
                llUnSit(agent);
                llSay(0,"Sorry, this vehicle is full.");
            }
        }
    }
}

    This prevents avatars from sitting on unintended parts of the vehicle.

Turning the Wheels

    To appear more realistic, the car turns its wheels when in motion. There are several ways that this is commonly done in Second Life vehicles. The method used for this car is shown in Listing 5.4.

Listing 5.4: Car Wheel (WheelScript.lsl)

// From the book:
//
// Scripting Recipes for Second Life
// by Jeff Heaton (Encog Dod in SL)
// ISBN: 160439000X
// Copyright 2007 by Heaton Research, Inc.
//
// This script may be freely copied and modified so long as this header
// remains unmodified.
//
// For more information about this book visit the following web site:
//
// http://www.heatonresearch.com/articles/series/22/
default
{
    state_entry()
    {
        llSetTimerEvent(0.20);
    }
    timer()
    {
        vector vel = llGetVel();
        float speed = llVecMag(vel);
        if(speed > 0)
        {
            llSetTextureAnim(ANIM_ON | SMOOTH | LOOP, 0, 0, 0, 0, 1, speed*0.5);            
        }
        else
        {
            llSetTextureAnim(ANIM_ON | SMOOTH | LOOP | REVERSE, 0, 0, 0, 0, 1, speed*0.5);
        }
    }
}

    The above script is contained in all four wheels of the car. The script works by rotating the texture of the wheel in one direction when the car is moving forward, and in another direction when moving backwards. The speed of the car can be obtained by calling llGetVel, as seen here.

vector vel = llGetVel();
float speed = llVecMag(vel);
if(speed > 0)
{
	llSetTextureAnim(ANIM_ON | SMOOTH | LOOP, 0, 0, 0, 0, 1, speed*0.5);            
}
else
{
	llSetTextureAnim(ANIM_ON | SMOOTH | LOOP | REVERSE, 0, 0, 0, 0, 1, speed*0.5);
}

    The hubcaps must be rotated too. However, they need to be rotated along a different coordinate than the tires. Other than that, their script is identical to the wheel script. The hubcap script is shown in Listing 5.4.

Listing 5.4: Rotate the Hubcaps (WheelScript.lsl)

// From the book:
//
// Scripting Recipes for Second Life
// by Jeff Heaton (Encog Dod in SL)
// ISBN: 160439000X
// Copyright 2007 by Heaton Research, Inc.
//
// This script may be freely copied and modified so long as this header
// remains unmodified.
//
// For more information about this book visit the following web site:
//
// http://www.heatonresearch.com/articles/series/22/
default
{
    state_entry()
    {
        llSetTimerEvent(0.20);
    }
    timer()
    {
        vector vel = llGetVel();
        float speed = llVecMag(vel);
        if(speed > 0)
        {
            llSetTextureAnim(ANIM_ON | SMOOTH | LOOP, 0, 0, 0, 0, 1, speed*0.5);            
        }
        else
        {
            llSetTextureAnim(ANIM_ON | SMOOTH | LOOP | REVERSE, 0, 0, 0, 0, 1, speed*0.5);
        }
    }
}

    There quite a few parts to the car. Unlike previous recipes, one script can not handle the entire object. Individual scripts are needed in several of the prims that make up the car. Some parts of the scripts will be reused in other vehicles. However, the other vehicles in this chapter are either air or sea based. This introduces some differences from the land based car.

Copyright 2005-2008 by Heaton Research, Inc.