Join us in Outworldz at www.outworldz.com:9000 or follow us:

How to make a fish swim in Second Life™ - making the fish swim script

In this post I'll show you how to script and teach a blue whale to swim around your sim. This script will let you position the whale, move from one point to another, broach the water surface randomly, and dive to any depth. This section will also prepare the whale with the control logic necessary to turn on the water effect script, the spout script, and the swimming motion scripts we will use in the next chapters.

moving tail script

If you followed the previous example, you will have a whale that looks like the one that is hovering inside my Proton Dome.


Blue whale in Dome

Have you ever ridden a jerky, slow, laggy ride? (I have). It infuriates me. Second life can do much better than a simple llSetPos(). This whale will use all of my best tricks to make the motion as smooth as possible.

The first script we will add is the movement script. Rez your whale somewhere, and edit it. Check that the cube that is just above the whale is outlined in Yellow. This cube was the last item linked. It is called the 'root' prim. It is important that this prim be shown in Yellow when you first edit the whale. If it is not outlined in yellow, unlink the whale with Ctrl+shift+L, and re-link it in any order, but be certain to click the cube last. Then press Ctrl+L to link the root prim.

Click on "Content" in the root prim. Click on "New Script...". Double click on the script called "New Script" that has been created. Download and save this script from the Script Repository. Be sure and click the download button to get it, as the HTML that sometimes creeps in when you screen-scrape the code will not compile.

Replace the definition of the script by a paste action. Click on "Save" button and close the script window. Rename this script to "Whale Global Movement script". Note that this script has HTML in it.

This script will move the whale in a square. By adding your own coordinates to the list coords, you can make the whale swim anywhere.

The script requires that some variables be available from anywhere in the program. These variables are 'global' variables. The first part of the script contains them:

Download


integer indexDestination;		// index to where we are heading
integer iRunning;			// flag that indicates we are running, or stopped.
integer iSwimCounter = 0;		// a state machine counter
string  FWD_DIRECTION   = "-y";	// the whale swims in the Y direction
float INTERVAL = 0.2;		// How often we move
vector vDestination;		// the next destination we swim towar
float DAMPING = 0.2;           	// point to the new destinatin in this number of seconds
integer indexDestination;		// index to where we are heading
integer iRunning;			// flag that indicates we are running, or stopped.
integer iSwimCounter = 0;		// a state machine counter
string  FWD_DIRECTION   = "-y";	// the whale swims in the Y direction
float INTERVAL = 0.2;		// How often we move
vector vDestination;		// the next destination we swim towar
float DAMPING = 0.2;		  	// point to the new destinatin in this number of seconds

Movement of your whale is going to be controlled by a list of coordinates in the list 'coords'. A simple list is like this:


// move in a square from 1,50 to 50,50
list coords = [
<1,1,25>,
<1,50,25>,
<50,50,25>,
<50,1,25>
];

These coordinates are in Second life's coordinate system:

The first coordinate <1,1,25> means " 1 meter from the bottom left (SW) at 25 meters high. Since "water level" is at 20 meters, the root prim of the whale will be 5 meters above the water. Since your root prim is only a few meters above the whale, the whale will most likely be swimming partly under water.

The resulting movement is shown in red on this map:

You can change this route to go around the edge of the sim. Just replace the list with this one:

List coords = [
<5,20,20>,
<5,235,20>,
<20,250,20>,
<235,250,20>,
<250,235,20>,
<250,20,20>,
<235,5,20>,
<20,5,20>
]; 
The resulting swim area will be as shown below. In this case, we would move the root prim as far away from the whale as we can top the right hand-side. The whale will circle clockwise from the SW corner. The whale will then 'hang out' over the edge of the sim.

Now that we have the globals defined and the route defined, it is time for the actual code. The first thing we will need is a subroutine to calculate a sinusoidal swimming motion. The llSin() function makes the sine wave, the 20 is the water height in SL, the 40 comes from the counter we will use later, PI is used to convert to radians from degrees, and the 4 is the amplitude. The whale will rise up 4 meters.

			
float calc (float i)
{
     return llSin((i-20)*40 / PI) * 4;    
}

All Second life LSL scripts must have a default{..} procedure. Inside that default{..} , between the starting { and ending } braces, is the code. All code MUST be inside a function, which is a name and parenthesis (). The first function we will use is the on_rez() function. This function will be called by the server whenever our whale is rezzed. We will use this to reset the script with the llResetScript() function. The forces the whale to start at the beginning of the route. If you leave out the on-rez, the whale will continue doing whatever it was doing before you took it into inventory, such as move from one point to another, which could be on the other side of the sim! I prefer it to stay where I rezzed it until I click it to start.


default
{
	on_rez(integer p)
	{
		llResetScript();
	}
        .. more code will follow

The next function we will use is the state_entry() function. It is executed after the script is reset. We will use this to initialize some global variables and the make our whale ready to swim.

	// startup from reset
	state_entry()
	{
		indexDestination = 0;				// point to the first coordinate
		vDestination = llList2Vector(coords,indexDestination); // get the first destination
		llSetStatus(STATUS_PHYSICS, FALSE);		// make the whale into a prim
		llSetStatus(STATUS_PHANTOM, TRUE);		// make it phantom so the sculpt's boundary box is smaller
		llMessageLinked(LINK_SET,0,"off",NULL_KEY);	// tell any other scripts in other prims that we are 'off'
		llSetAlpha( 1.0, ALL_SIDES);			// make the box visible.
	}	

The whale could be programmed to just go shooting off to the first destination. I chose to make it wait for a command. It makes it a lot easier to debug code and play with shapes and textures when it is just stitting there. The function I use to get it started is touch_start().

touch_start() is called by the server when anyone touches the whale. Anyone can touch it. Multiple people can touch it at the same time. The variable p in 'touch_start(integer p)' will be set to the number of people that touched it.


	touch_start(integer p)
	{
	

We don't really need to look at integer p as we only want the owner of the whale to start and stop it. So the next line of code gets the detected key of the person that touched the prim, and compares it to the key of the owner.

		if (llDetectedKey(0) == llGetOwner())
		{
		

If they are equal, the system will check to see if the whale is running or not. One of the global flags 'iRunning' is set by the server to 0. This is a FALSE value. So the first time we touch it, the 'else' code will run. If the whales was 'running', i.e., moving, the first part will be executed instead. In that case, the owner will be told that the 'whale is stopped', the iRunning global variable will be set to FALSE, the root prim (the cube) will be made visible, and the Havok physics engine will be shut off. A message is sent to all other prim's so that any running scripts can be shit down. The last thing that happens is the timer is shut of by passing it a 0 value.

If the whale was stopped when we touched it, the opposite happens. Along with turning on scripts, setting the Havoc physics engine on, and so on, the important thing that happens here is that the timer is set to run 5 times a second.

			if (iRunning)
			{
				llOwnerSay("whale is stopped");
				iRunning = FALSE;
				llSetAlpha( 1.0, ALL_SIDES);
				llSetStatus(STATUS_PHYSICS, FALSE);
				llMessageLinked(LINK_SET,0,"off",NULL_KEY);   // make the scripts run
				llSetTimerEvent(0.0);
				llSetStatus(STATUS_PHYSICS, FALSE);
			}
			else
			{
				llOwnerSay("whale is  running");
	 
				llSetAlpha( 0.0, ALL_SIDES);
				iRunning = TRUE;
				llSetTimerEvent(INTERVAL);			// start the timer
				llSetStatus(STATUS_PHYSICS, TRUE);
				llMessageLinked(LINK_SET,0,"on",NULL_KEY);	// make the root prim invisible
	 			llSetStatus(STATUS_PHYSICS, TRUE);
			}
		}
	}

The timer event is going to be called by the server 5 times a second because INTERVAL = 0.2. The physics engine is will be made to push the whale towards the destination that often.

The llMoveToTarget() function is used often to position an object at a destination vector. It has lots of limits. For example, you can't move more than 65 meters, or it will just do nothing. The really bad part is that the llMoveToTarget will move slower and slower as it gets closer and closer to a target. If your choice of destination is 'far away', the system will push you towards the destination with a lot of energy. Maybe you will make it. Probably not.

The cure is to move a consistent, short distance toward the destination. In the whale system, we will attempt to move 10 meters a second. The timer will be called 5 times a second. So we must move at least 2 meters each timer tick. And each timer tick happens 0.2 seconds, so we will need to tell the system to move 2 meters every 0.2 seconds. The movement code is 'llMoveToTarget(newdest, DAMPING);'.

In summary, we will move only a little bit towards the goal. We will do it often. And we will make it overshoot each time so that we always move at an almost constant rate.

	timer()
	{
		// Go 2 meters from  the current point, towards the destination
		// with the timer at 0.2 second, we will travel 10 meters a second
		vector newdest = (llVecNorm(vDestination - llGetPos()) * 2) + llGetPos();

We could just move to this target 'newdest ', but the physics engine will give us only one shove in that direction and coast us to a stop (hopefully) at the correct sport. We want smooth motion, so we break it up into smaller pieces and push, coast, push, coast until we get to the correct spot.

Imagine clutching an arrow by the feather in our hand and pointing it to an apple. We need the vector vDestination - llGetPos(), which is the the same direction and distance we are wanting to go. But we only want to go a small distance, not the entire distance. By getting the llVecNorm(X) , we have an arrow that points in the correct direction. I use the llVecNorm(X) system call to return a 'normalized' vector that 'points' in the direction of X. Think of it as an arrow pointing 'thataway'. It's about the same as "North', for example. It has a length of one, as it has been 'normalized'.

Now that we have a normalized vector, we add the 2 meters we need to move. This is the * 2 operator (yes, you multiply vectors to add them, and divide to subtract). To this, we add our current position. Now we have a destination vector for the arrow tip-end that is 2 meters long, pointing towards the destination we want to go, starting from the feathers on our arrow which is our current position.

This is identical to clutching a2 -meter long arrow by the feathers in our hand, waving the arrow around, and saying 'go thataway' to the tip.

Before we move, we may want to surface or dive. I use a simple counter, iSwimCounter, which increments each timer tick. This happens 5 times a second.


		iSwimCounter++ ;		// count the state machine upwards to see if we sink, or swim

The 'swim' counter will change rapidly. The code is designed to count for 4 seconds (20 iSwimCounters), then make the whale surface, dive, then repeat over and over. At each stage, it may send a llMessageLinked command to the other prims and scripts to make the whale blow water from the blowhole, or turn on a splash script.

It ends up with a swim action like this chart. The actual shape is a sine wave.

The whale is also designed to turn slowly upward, and turn quickly downward. The turn speed is called 'DAMPING'.

	if (swim == 20)
            llMessageLinked(LINK_SET,0,"on",NULL_KEY);    
            
        if (swim == 27)
            llMessageLinked(LINK_SET,0,"off",NULL_KEY);
            
        if (swim == 35)
            llMessageLinked(LINK_SET,0,"on",NULL_KEY);
              
        if (swim > 37)
            llMessageLinked(LINK_SET,0,"off",NULL_KEY);
        
        if (swim > 20 && swim < 40)
            newdest.z = calc((float) swim) + 20;
        else
            newdest.z = 20;
  
            
        llLookAt(newdest, 1, 1.0); // look at the new destination in 1 second.

Now that we have the correct destination, we need to actually move the whale. I use llVecDist(a,b) to get the distance from the whales current position to the destination. If we are close to the destination, such as 1 meter away, we can pick a new destination. Obviously, for a whale, 1 meter is plenty good. If this was a bird bird trying to perch on my arm, I would want to be a lot closer to the final resting spot! If we are further away, we issue (finally) the llMoveToTarget system call. The whale will head off 'thataway', trying to cover a 2 meters distance in 0.2 seconds, so it will travel 10 meters per second. For a slower fish or whale, you need only to adjust the DAMPING.


		float dist = llVecDist(vDestination,llGetPos());		// see how close we are to the destination
		if (dist > 1.0)
			llMoveToTarget(newdest, DAMPING);			// get +there in .2 seconds
		else
		{
			indexDestination++;			// look at next destination
			if (indexDestination >=  llGetListLength(coords))
				indexDestination = 0;	// start at the beginning if we run off the end
			vDestination = llList2Vector(coords,indexDestination);	
			//vDestination.z += 10;  // move 10 meters upwards	(uncomment for debugging)
		}	

Finally, we need to close out our timer function, and close out the default function with their matching braces }.

	
	}	
}

// end of script	

You whale is all ready to swim. Click it and watch it move. If it moves backwards, or stands on it's tail (it happened to mine, with much hilarity), then click it again to stop it, and rotate the cube (the root prim) to point in the correct direction. You may want to try it up in the sky, by adding to vDestination.z by uncommenting the last line of code:

vDestination.z += 10; // move 10 meters upwards

Next - > Part 4- Making the fins move - fin movement script >

Back to the Best Free Tools in Second Life and OpenSim.