Recipe 5.2: Boat
Boats are also very popular in Second Life. The boat is designed to work with sea water. That is, the built-in water that is at a specific height, usually 20 meters. Sea areas can usually be found by examining the map. Or look for a “water sandbox” on the search. Sandboxes are public areas that allow anyone to build there. The boat shares some characteristics with the car. However, there are some important differences.
- The boat should roll slightly when it turns.
- The boat has buoyancy, and floats on the water.
- The boat leaves waves in its wake.
- The boat uses different sounds.
- The boat has no wheels to turn.
Most boats in Second Life will not work with artificial water. This is because the vehicle engine does not recognize artificial water as water. Artificial water is a prim that is textured to look like water. Recipe 3.1 shows how to create artificial water. To create a “boat” that traveled on artificial water, an “air vehicle” would need to be created to hover just above it.
All of the scripts necessary for the boat will be shown. However, only the aspects of the boat that are different from the car will be explained. If you have not reviewed the car, Recipe 5.1, should be reviewed prior to learning how to create a boat. The boat can be seen in Figure 5.5.
Figure 5.5: A Boat in Second Life

Most of the script necessary to handle the boat is contained in the root prim. The root prim is the driver's seat of the boat. This script is shown in Listing 5.4.
Listing 5.4: The Boat Script (Boat.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 = 25; //Power used to go forward (1 to 30) float reverse_power = -15; //Power ued to go reverse (-1 to -30) float turning_ratio = 5.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 //Anything past this point should only be modfied if you know what you are doing default { state_entry() { llSetSitText(sit_message); // forward-back,left-right,updown llSitTarget(<0.2,0,0.45>, ZERO_ROTATION ); llSetCameraEyeOffset(<-12, 0.0, 5.0>); llSetCameraAtOffset(<1.0, 0.0, 2.0>); llPreloadSound("boat_start"); llPreloadSound("boat_run"); llSetVehicleFlags(0); llSetVehicleType(VEHICLE_TYPE_BOAT); llSetVehicleFlags(VEHICLE_FLAG_HOVER_UP_ONLY | VEHICLE_FLAG_HOVER_WATER_ONLY); llSetVehicleVectorParam( VEHICLE_LINEAR_FRICTION_TIMESCALE, <1, 1, 1> ); llSetVehicleFloatParam( VEHICLE_ANGULAR_FRICTION_TIMESCALE, 2 ); llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, <0, 0, 0>); llSetVehicleFloatParam(VEHICLE_LINEAR_MOTOR_TIMESCALE, 1); llSetVehicleFloatParam(VEHICLE_LINEAR_MOTOR_DECAY_TIMESCALE, 0.05); llSetVehicleFloatParam( VEHICLE_ANGULAR_MOTOR_TIMESCALE, 1 ); llSetVehicleFloatParam( VEHICLE_ANGULAR_MOTOR_DECAY_TIMESCALE, 5 ); llSetVehicleFloatParam( VEHICLE_HOVER_HEIGHT, 0.15); llSetVehicleFloatParam( VEHICLE_HOVER_EFFICIENCY,.5 ); llSetVehicleFloatParam( VEHICLE_HOVER_TIMESCALE, 2.0 ); llSetVehicleFloatParam( VEHICLE_BUOYANCY, 1 ); llSetVehicleFloatParam( VEHICLE_LINEAR_DEFLECTION_EFFICIENCY, 0.5 ); llSetVehicleFloatParam( VEHICLE_LINEAR_DEFLECTION_TIMESCALE, 3 ); llSetVehicleFloatParam( VEHICLE_ANGULAR_DEFLECTION_EFFICIENCY, 0.5 ); llSetVehicleFloatParam( VEHICLE_ANGULAR_DEFLECTION_TIMESCALE, 10 ); llSetVehicleFloatParam( VEHICLE_VERTICAL_ATTRACTION_EFFICIENCY, 0.5 ); llSetVehicleFloatParam( VEHICLE_VERTICAL_ATTRACTION_TIMESCALE, 2 ); llSetVehicleFloatParam( VEHICLE_BANKING_EFFICIENCY, 1 ); llSetVehicleFloatParam( VEHICLE_BANKING_MIX, 0.1 ); llSetVehicleFloatParam( VEHICLE_BANKING_TIMESCALE, .5 ); llSetVehicleRotationParam( VEHICLE_REFERENCE_FRAME, ZERO_ROTATION ); } 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("boat_start",1); llMessageLinked(LINK_ALL_CHILDREN , 0, "start", NULL_KEY); llSleep(.4); llSetStatus(STATUS_PHYSICS, TRUE); llSleep(.1); llRequestPermissions(agent, PERMISSION_TRIGGER_ANIMATION | PERMISSION_TAKE_CONTROLS); llLoopSound("boat_run",1); } } else { llStopSound(); llSetStatus(STATUS_PHYSICS, FALSE); llSleep(.1); llMessageLinked(LINK_ALL_CHILDREN , 0, "stop", NULL_KEY); 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; angular_motor.x += 15; } if(level & (CONTROL_LEFT|CONTROL_ROT_LEFT)) { angular_motor.z += speed / turning_ratio * reverse; angular_motor.x -= 15; } llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION, angular_motor); } //end control } //end default
The next few sections explain how the boat functions.
Initialization of the Boat
The car was initialized by setting its vehicle parameters to values that work well for a car. The boat works similarly. The boat is setup in the state_entry event handler. First, the boat sets the sit target and sit text.
state_entry()
{
llSetSitText(sit_message);
// forward-back,left-right,updown
llSitTarget(<0.2,0,0.45>, ZERO_ROTATION );Next, the camera is moved to a good distance behind the boat and above it. The camera looks into the boat. The camera for the boat is placed further back than the car because the boat is longer than the car.
llSetCameraEyeOffset(<-12, 0.0, 5.0>);
llSetCameraAtOffset(<1.0, 0.0, 2.0>);The boat's two sounds are preloaded.
llPreloadSound("boat_start");
llPreloadSound("boat_run");Next, the vehicle parameters are set. For more information on vehicle parameters refer to Tables 5.1, 5.2 and 5.3.
llSetVehicleType(VEHICLE_TYPE_BOAT); llSetVehicleFlags(VEHICLE_FLAG_HOVER_UP_ONLY | VEHICLE_FLAG_HOVER_WATER_ONLY); llRemoveVehicleFlags( VEHICLE_FLAG_HOVER_TERRAIN_ONLY | VEHICLE_FLAG_LIMIT_ROLL_ONLY | VEHICLE_FLAG_HOVER_GLOBAL_HEIGHT);
The boat sets two of the vehicle flags using the llSetVehicleFlags function. These flags are summarized in Table 5.4.
Table 5.4: Vector Vehicle Parameters
| Flag | Purpose |
|---|---|
| VEHICLE_FLAG_NO_DEFLECTION_UP | Prevents ground vehicles from deflecting up. Also prevents ground vehicles from “climbing” low prims in their path. |
| VEHICLE_FLAG_LIMIT_ROLL_ONLY | Allows the vehicle to climb and dive. Useful for airplanes. |
| VEHICLE_FLAG_HOVER_WATER_ONLY | Ignore terrain height when hovering. |
| VEHICLE_FLAG_HOVER_TERRAIN_ONLY | Ignore water height when hovering. |
| VEHICLE_FLAG_HOVER_GLOBAL_HEIGHT | Hover at global height instead of height above ground or water. |
| VEHICLE_FLAG_HOVER_UP_ONLY | Always stay at hover height, but go up. Useful for hover vehicles that need to jump. |
| VEHICLE_FLAG_LIMIT_MOTOR_UP | Keep ground vehicles on the ground. |
| VEHICLE_FLAG_MOUSELOOK_STEER | Use mouselook to steer the vehicle. |
| VEHICLE_FLAG_MOUSELOOK_BANK | Use mouselook to bank the vehicle. |
| VEHICLE_FLAG_CAMERA_DECOUPLED | The camera moves independently of the vehicle. |
Friction affects the boat equally in all three coordinate planes.
llSetVehicleVectorParam( VEHICLE_LINEAR_FRICTION_TIMESCALE, <1, 1, 1> );
It will take two seconds for angular friction to have its full effect on the boat.
llSetVehicleFloatParam( VEHICLE_ANGULAR_FRICTION_TIMESCALE, 2 );
The linear motor will operate with no rotation.
llSetVehicleVectorParam( VEHICLE_LINEAR_MOTOR_DIRECTION, <0, 0, 0>); llSetVehicleFloatParam( VEHICLE_LINEAR_MOTOR_TIMESCALE, 1);
The linear motor will decay very quickly.
llSetVehicleFloatParam( VEHICLE_LINEAR_MOTOR_DECAY_TIMESCALE, 0.05);
The angular motor will take one second to reach full effect.
llSetVehicleFloatParam( VEHICLE_ANGULAR_MOTOR_TIMESCALE, 1 );
The angular motor will decay in five seconds.
llSetVehicleFloatParam( VEHICLE_ANGULAR_MOTOR_DECAY_TIMESCALE, 5 );
The center of the boat will hover slightly above the water. This accounts for the underside of the boat, which should only be partially under water.
llSetVehicleFloatParam( VEHICLE_HOVER_HEIGHT, 0.15);
The boat has 50% hover efficiency.
llSetVehicleFloatParam( VEHICLE_HOVER_EFFICIENCY,.5 );
It will take two seconds for the hover to reach full effect.
llSetVehicleFloatParam( VEHICLE_HOVER_TIMESCALE, 2.0 );
A boat has 100% buoyancy.
llSetVehicleFloatParam( VEHICLE_BUOYANCY, 1 );
Linear deflection will be at 50% for the boat.
llSetVehicleFloatParam( VEHICLE_LINEAR_DEFLECTION_EFFICIENCY, 0.5 );
It will take three seconds for linear deflection to have its full effect.
llSetVehicleFloatParam( VEHICLE_LINEAR_DEFLECTION_TIMESCALE, 3 );
Angular deflection will be at 50% for the boat.
llSetVehicleFloatParam( VEHICLE_ANGULAR_DEFLECTION_EFFICIENCY, 0.5 );
It will take ten seconds for angular deflection to reach full effect.
llSetVehicleFloatParam( VEHICLE_ANGULAR_DEFLECTION_TIMESCALE, 10 );
A car should always stay right-side-up. The vertical attraction feature allows this.
llSetVehicleFloatParam( VEHICLE_VERTICAL_ATTRACTION_EFFICIENCY, 0.5 ); llSetVehicleFloatParam( VEHICLE_VERTICAL_ATTRACTION_TIMESCALE, 2 );
The boat will have 100% banking effectiveness. A boat banks slightly when turning, this setting allows this.
llSetVehicleFloatParam( VEHICLE_BANKING_EFFICIENCY, 1 );
The boat's banking will be only 10% dynamic. This allows a smooth controlled bank.
llSetVehicleFloatParam( VEHICLE_BANKING_MIX, 0.1 );
It will take the boat only half a second to reach full banking. This allows the bank to be seen quickly when the boat begins to turn.
llSetVehicleFloatParam( VEHICLE_BANKING_TIMESCALE, .5 );
There is no rotation on the root prim.
llSetVehicleRotationParam( VEHICLE_REFERENCE_FRAME, ZERO_ROTATION );
There are some variables the boat has that the car does not. These variables generally deal with banking, which is something the car does not support. Additionally, the values were set to numbers that make sense for a boat.
Controlling the Boat
The controls for the boat are similar to the controls for the car. However, there are some important differences for banking. Because of these differences, the entire control event handler will be reviewed.
The control event handler begins by setting up some variables that will be needed by the handler. Because boats, like cars, turn differently when in reverse, a flag must be kept to indicate if the boat is in reverse. A variable is also 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. Boats do not turn when they are not in motion.
//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 force to move the boat forward. Note also that the boat moves forward when reverse is set to one.
//boat 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 boat moves in reverse when the reverse variable is set to -1.
if(level & CONTROL_BACK)
{
llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, <reverse_power,0,0>);
reverse = -1;
}For a right turn, rotate the boat in the z-coordinate. Rotate by the specified angle and take into account if the boat is going in reverse. Also roll the boat by 15 degrees on the x coordinate.
if(level & (CONTROL_RIGHT|CONTROL_ROT_RIGHT))
{
angular_motor.z -= speed / turning_ratio * reverse;
angular_motor.x += 15;
}For a left turn, rotate the boat in the z-coordinate. Rotate by the specified angle and take into account if the boat is going in reverse. Also roll the boat by 15 degrees on the x coordinate.
if(level & (CONTROL_LEFT|CONTROL_ROT_LEFT))
{
angular_motor.z += speed / turning_ratio * reverse;
angular_motor.x -= 15;
}Now the angular motor can be set.
llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION, angular_motor);
} //end control The primary difference between the boat's control event handler and the car's is that the boat must take banking into account.
Waves in the Wake
The boat is designed to waves in its wake. This is done with a particle emitter script. The particle emitter script is based on the basic particle emitter script found in Recipe 4.1. The boat's wake script can be seen in Listing 5.5.
Listing 5.5: Boat Wake (BoatWake.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/ generalParticleEmitterOn() { llParticleSystem([ PSYS_PART_FLAGS , 0 //| PSYS_PART_BOUNCE_MASK //Bounce on object's z-axis //| PSYS_PART_WIND_MASK //Particles are moved by wind | PSYS_PART_INTERP_COLOR_MASK //Colors fade from start to end | PSYS_PART_INTERP_SCALE_MASK //Scale fades from beginning to end | PSYS_PART_FOLLOW_SRC_MASK //Particles follow the emitter | PSYS_PART_FOLLOW_VELOCITY_MASK//Particles are created at the velocity of the emitter //| PSYS_PART_TARGET_POS_MASK //Particles follow the target | PSYS_PART_EMISSIVE_MASK //Particles are self-lit (glow) //| PSYS_PART_TARGET_LINEAR_MASK//Undocumented--Sends particles in straight line? , //PSYS_SRC_TARGET_KEY , NULL_KEY,//The particles will head towards the specified key //Select one of the following for a pattern: //PSYS_SRC_PATTERN_DROP Particles start at emitter with no velocity //PSYS_SRC_PATTERN_EXPLODE Particles explode from the emitter //PSYS_SRC_PATTERN_ANGLE Particles are emitted in a 2-D angle //PSYS_SRC_PATTERN_ANGLE_CONE Particles are emitted in a 3-D cone //PSYS_SRC_PATTERN_ANGLE_CONE_EMPTY Particles are emitted everywhere except for a 3-D cone PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_ANGLE ,PSYS_SRC_TEXTURE, "wake" //UUID of the desired particle texture, or inventory name ,PSYS_SRC_MAX_AGE, 0.0 //Time, in seconds, for particles to be emitted. 0 = forever ,PSYS_PART_MAX_AGE, 4.0 //Lifetime, in seconds, that a particle lasts ,PSYS_SRC_BURST_RATE, 0.5 //How long, in seconds, between each emission ,PSYS_SRC_BURST_PART_COUNT, 6 //Number of particles per emission ,PSYS_SRC_BURST_RADIUS, 10.0 //Radius of emission ,PSYS_SRC_BURST_SPEED_MIN, 0.75 //Minimum speed of an emitted particle ,PSYS_SRC_BURST_SPEED_MAX, 1.5 //Maximum speed of an emitted particle ,PSYS_SRC_ACCEL, <0,0,0> //Acceleration of particles each second ,PSYS_PART_START_COLOR, <0.85,0.85,1.0> //Starting RGB color ,PSYS_PART_END_COLOR, <1,1,1> //Ending RGB color, if INTERP_COLOR_MASK is on ,PSYS_PART_START_ALPHA, 0.0 //Starting transparency, 1 is opaque, 0 is transparent. ,PSYS_PART_END_ALPHA, 1.0 //Ending transparency ,PSYS_PART_START_SCALE, <2.5,1,0.0> //Starting particle size ,PSYS_PART_END_SCALE, <2.5,1,0.0> //Ending particle size, if INTERP_SCALE_MASK is on ,PSYS_SRC_ANGLE_BEGIN, 0 * DEG_TO_RAD //Inner angle for ANGLE patterns ,PSYS_SRC_ANGLE_END, 0 * DEG_TO_RAD//Outer angle for ANGLE patterns ,PSYS_SRC_OMEGA, <0.0,0.0,0.0> //Rotation of ANGLE patterns, similar to llTargetOmega() ]); } generalParticleEmitterOff() { llParticleSystem([]); } default { state_entry() { generalParticleEmitterOff(); } link_message(integer sender_num, integer num, string str, key id) { if(str=="stop") { generalParticleEmitterOff(); } if(str=="start") { generalParticleEmitterOn(); } } }
The boat, with a wake behind it, can be seen in Figure 5.6.
Figure 5.6: A Boat with Wake

The wake script turns on and off as commanded by the main boat script in the root prim. To turn the wake on, the following command is issued by the main boat script.
llMessageLinked(LINK_ALL_CHILDREN , 0, "start", NULL_KEY);
To turn the wake script off, the following command is issued by the main boat script.
llMessageLinked(LINK_ALL_CHILDREN , 0, "stop", NULL_KEY);
Both commands are issued in the changed event handler. The wake trail will begin when the driver sits. The wake trail will end when the driver stands. This communication is done with a linked message. A linked message is sent to all linked prims. The wake script handles the linked message in the link_message event handler.
link_message(integer sender_num, integer num, string str, key id)
{
if(str=="stop")
{
generalParticleEmitterOff();
}
if(str=="start")
{
generalParticleEmitterOn();
}
}If the message is to stop, the particle script is turned off. If the message is to start, the particle script is turned on. For more information on how the particle script is constructed, refer to Chapter 4.
