For the moment, I'm not worrying about vehicle velocity or acceleration. I'd be happy if I can get the thing moving forwards and backwards, turning, and staying on the ground.
Has anyone had much success in scripting a non-physical vehicle?
This is what I have so far, and it kind of works, but not all that well:
CODE
// Script for moving a non-physical vehicle
// author: Erratic Rambler
// notes:
// - Uses separate scripts for doing movement and rotation, since those operations cause delay.
// Link messages to communicate with movement and rotation scripts.
// - Keeps track of current position and rotation instead of using llGetPos / llGetRot,
// because the movement is done in separate scripts and current values may not be
// the correct target values.
// constants
vector SIT_POS; // sit offset
vector SIT_ROT; // sit rotation
vector CAMERA_POS; // offset of camera when avatar sits
vector CAMERA_LOOK_AT; // where camera looks when avatar sits
integer CONTROLS; // list of control keys to use
vector FORWARD_MOVE; // forward movement rate
vector BACK_MOVE; // backwards movement rate
float TURN_RATE; // turn rate
float TIMER_DURATION; // timer delay
integer MOVER_MAX_INDEX; // number of movement listeners - 1
integer TURNER_MAX_INDEX; // number of turn listeners - 1
// globals
integer forwardKeyPressed = FALSE; // true when forward key pressed
integer backKeyPressed = FALSE; // true when back key pressed
integer leftKeyPressed = FALSE; // true when left key pressed
integer rightKeyPressed = FALSE; // true when right key pressed
integer moverIndex = 0; // current mover script
integer turnerIndex = 0; // current turner script
vector currentPos; // current position
vector currentRot; // current rotation
// from LSL wiki
// AXIS_* constants, represent the unit vector 1 unit on the specified axis.
vector AXIS_UP = <0,0,1>;
vector AXIS_LEFT = <0,1,0>;
vector AXIS_FWD = <1,0,0>;
// from LSL wiki
// getRotToPointAxisAt()
// Gets the rotation to point the specified axis at the specified position.
// @param axis The axis to point. Easiest to just use an AXIS_* constant.
// @param target The target, in region-local coordinates, to point the axis at.
// @return The rotation necessary to point axis at target.
// Created by Ope Rand, modifyed by Christopher Omega
rotation getRotToPointAxisAt(vector axis, vector target) {
return llGetRot() * llRotBetween(axis * llGetRot(), target - llGetPos());
}
default
{
state_entry()
{
// initialize global 'constants'
SIT_POS = <0.0, 0.0, 0.5>;
SIT_ROT = ZERO_VECTOR;
CAMERA_POS = <-10.0, 0.0, 5.0>;
CAMERA_LOOK_AT = <0.0, 0.0, 0.0>;
CONTROLS = CONTROL_FWD | CONTROL_BACK | CONTROL_ROT_LEFT | CONTROL_ROT_RIGHT | CONTROL_LEFT | CONTROL_RIGHT;
FORWARD_MOVE = <2, 0, 0>;
BACK_MOVE = <-1, 0, 0>;
TURN_RATE = 7.2;
TIMER_DURATION = 0.1;
MOVER_MAX_INDEX = 1;
TURNER_MAX_INDEX = 1;
// initialize global variables
currentPos = llGetPos();
currentRot = RAD_TO_DEG * llRot2Euler( llGetRot() );
// set up vehicle parameters
llSetSitText( "Drive" );
llCollisionSound( "", 0.0 );
llSitTarget( SIT_POS, llEuler2Rot( DEG_TO_RAD * SIT_ROT ) );
llSetText( "", <1,1,1>, 1.0 );
llSetStatus( STATUS_PHYSICS, FALSE );
llSetCameraEyeOffset( CAMERA_POS );
llSetCameraAtOffset( CAMERA_LOOK_AT );
// disable timer
llSetTimerEvent( 0 );
}
// sit / unsit / etc
changed( integer change )
{
if( change & CHANGED_LINK )
{
key agent = llAvatarOnSitTarget();
if( agent )
{
if( agent != llGetOwner() )
{
// only let owner drive vehicle
llSay( 0, "Only the owner can drive this vehicle." );
llUnSit( agent );
llPushObject( agent, <0, 0, 10>, ZERO_VECTOR, FALSE );
}
else
{
// take controls
if( llGetPermissions() & PERMISSION_TAKE_CONTROLS == PERMISSION_TAKE_CONTROLS )
llTakeControls( CONTROLS, TRUE, FALSE );
else
llRequestPermissions( agent, PERMISSION_TAKE_CONTROLS );
// read position and rotation
currentPos = llGetPos();
currentRot = RAD_TO_DEG * llRot2Euler( llGetRot() );
// enable timer
llSetTimerEvent( TIMER_DURATION );
}
}
else
{
// release controls
llReleaseControls();
// disable timer
llSetTimerEvent( 0 );
}
}
}
// permissions granted to take controls
run_time_permissions( integer perm )
{
if( perm )
{
llTakeControls( CONTROLS, TRUE, FALSE );
}
}
// control input
control( key id, integer level, integer edge )
{
// set key press status variables based on control input
if( edge & level & CONTROL_FWD ) forwardKeyPressed = TRUE;
if( edge & ~level & CONTROL_FWD ) forwardKeyPressed = FALSE;
if( edge & level & CONTROL_BACK ) backKeyPressed = TRUE;
if( edge & ~level & CONTROL_BACK ) backKeyPressed = FALSE;
if( ( edge & level & CONTROL_ROT_LEFT ) || ( edge & level & CONTROL_LEFT ) ) leftKeyPressed = TRUE;
if( ( edge & ~level & CONTROL_ROT_LEFT ) || ( edge & ~level & CONTROL_LEFT ) ) leftKeyPressed = FALSE;
if( ( edge & level & CONTROL_ROT_RIGHT ) || ( edge & level & CONTROL_RIGHT ) ) rightKeyPressed = TRUE;
if( ( edge & ~level & CONTROL_ROT_RIGHT ) || ( edge & ~level & CONTROL_RIGHT ) ) rightKeyPressed = FALSE;
}
// timer event runs while vehicle is active
timer()
{
// cause vehicle to remain upright
// llLookAt does not work correctly here, but getRotToPointAxisAt
// function from wiki in combination with llRotLookAt does.
// (llLookAt seems to do rotation around z axis for some reason??)
vector up = currentPos + llGroundNormal( ZERO_VECTOR );
llRotLookAt( getRotToPointAxisAt( AXIS_UP, up ), 1.0, 1.0 );
// do movement
if( forwardKeyPressed && !backKeyPressed )
{
currentPos += FORWARD_MOVE * llEuler2Rot( DEG_TO_RAD * currentRot ); // move forward
currentPos.z = llGround( ZERO_VECTOR ) + 1.0; // stay on ground
string targetPosString = (string)currentPos; // convert position to string
llMessageLinked( LINK_THIS, moverIndex, "move", (key)targetPosString ); // call move script
}
if( backKeyPressed && !forwardKeyPressed )
{
currentPos += BACK_MOVE * llEuler2Rot( DEG_TO_RAD * currentRot ); // move backward
currentPos.z = llGround( ZERO_VECTOR ) + 1.0; // stay on ground
string targetPosString = (string)currentPos; // convert position to string
llMessageLinked( LINK_THIS, moverIndex, "move", (key)targetPosString ); // call move script
}
// do turning
if( leftKeyPressed && !rightKeyPressed )
{
currentRot.z += TURN_RATE; // rotate left
string targetRotString = (string)llEuler2Rot( DEG_TO_RAD * currentRot ); // convert rotation to string
llMessageLinked( LINK_THIS, turnerIndex, "rotate", (key)targetRotString ); // call rotate script
}
if( rightKeyPressed && !leftKeyPressed )
{
currentRot.z -= TURN_RATE; // rotate right
string targetRotString = (string)llEuler2Rot( DEG_TO_RAD * currentRot ); // convert rotation to string
llMessageLinked( LINK_THIS, turnerIndex, "rotate", (key)targetRotString ); // call rotate script
}
// increment indices of movement scripts to use
moverIndex++;
turnerIndex++;
if( moverIndex > MOVER_MAX_INDEX ) moverIndex = 0;
if( turnerIndex > TURNER_MAX_INDEX ) turnerIndex = 0;
}
}
CODE
// script which does movement
// have N of these in the object, with consecutive 0-based indices
string ACTION_TYPE = "move";
integer INDEX = 0;
default
{
state_entry()
{
}
// listen for link messages
link_message( integer sender_num, integer num, string str, key id )
{
// if message is for this script
if( str == ACTION_TYPE && num == INDEX )
{
string val = (string)id;
llSetPos( (vector)val );
}
}
}
CODE
// script which does rotations
// have N of these in the object, with consecutive 0-based indices
string ACTION_TYPE = "rotate";
integer INDEX = 0;
default
{
state_entry()
{
}
// listen for link messages
link_message( integer sender_num, integer num, string str, key id )
{
// if message is for this script
if( str == ACTION_TYPE && num == INDEX )
{
string val = (string)id;
llSetRot( (rotation)val );
}
}
}
If I can't get a non-physical vehicle working well, the next thing I plan to try is multiple small physical vehicles which all follow the main vehicle (making it essentially 1 large multi-part vehicle). That seems kind of messy though, so I'd much rather get a non-physical vehicle that works nicely.