Second Life PathFinding Tutorial

A Script

While figuring these things out I came up with this script. It is more about revealing things and allowing me to test features of Pathfinding than it is for actually doing something.

Update: Falcon Linden asked that I point out llDeleteCharacter() is no longer needed, in the way I used it to reset the character. Quoting Falcon, “We’ve changed llCreateCharacter() to do the resetting you want. No need for llDeleteCharacter() first.”

// Test script Nalates Urrian 2/2012

key    my_owner;
vector curr_rot;
vector new_rot;

default
{
   state_entry()
    {
      llSay(0, "Touch me to start");
      llDeleteCharacter();
   }

   touch_end(integer total_number)
   {
      llDeleteCharacter(); // Not needed
      llCreateCharacter([ CHARACTER_DESIRED_SPEED, 5.0,
         CHARACTER_MAX_ANGULAR_ACCEL, 6.0,
         CHARACTER_RADIUS, 0.125,
         CHARACTER_LENGTH, 3.35]);
   my_owner = llDetectedOwner(0);
   llPursue(my_owner, [PURSUIT_OFFSET, <-2.0, 0.0, 0.0>,
         PURSUIT_FUZZ_FACTOR, 0.2] );
   llSay(0,"Pursuing");
   state pursuing;
   }
}

state pursuing {
   state_entry(){
      llSleep(2.0);
   }

   touch_end(integer total_number){
      llDeleteCharacter(); //Still needed
      llSay(0,"Stopped");
      state default;
   }

   moving_start( ){
      llSay(0,"Started moving");
      curr_rot = llRot2Euler(llGetRot());
      new_rot  = (vector) <curr_rot.x, 10.0, curr_rot.z>;
      llSetRot(llEuler2Rot(new_rot));
      llSetPrimitiveParams([ PRIM_COLOR,4, <0,255,0>, 1.0 ]);
   }

   moving_end( ){
      llSay(0,"STOPPED moving");
      llSetPrimitiveParams([ PRIM_COLOR,4, <255,0,0>, 1.0 ]);
      llSleep(2.0);
      llSetPrimitiveParams([ PRIM_COLOR,4, <255,255,255>, 1.0 ]);
   }
   path_update( integer i, list reserved){
      //if( i == PU_SLOWDOWN_DISTANCE_REACHED){ llSay(0,"Slowing");}
      if( i == PU_GOAL_REACHED){ llSay(0,"Hi");}
      if( i == PU_FAILURE_INVALID_START){ llSay(0,"Can't start");}
      if( i == PU_FAILURE_INVALID_GOAL){ llSay(0,"No good target");}
      if( i == PU_FAILURE_UNREACHABLE){ llSay(0,"I can't get there");}
      if( i == PU_FAILURE_TARGET_GONE){ llSay(0,"I lost my target");}
      if( i == PU_FAILURE_NO_VALID_DESTINATION){
                               llSay(0,"I got no valid distination");}
      if( i == PU_EVADE_HIDDEN){ llSay(0,"I have hidden");}
      if( i == PU_EVADE_SPOTTED){ llSay(0,"Yikes! Run!");}
      if( i == PU_EVADE_SPOTTED){ llSay(0,"Yikes! Run!");}
      if( i == PU_FAILURE_NO_NAVMESH){ llSay(0,"No nav mesh");}
      if( i == PU_FAILURE_OTHER){ llSay(0,"Somethings wrong");}

   }
}

This section of code:

   state_entry()
      {
         llSay(0, "Touch me to start");
         llDeleteCharacter();
      }
   }

Is required because the Pathfinding attributes do not clear. (This is the part that Falcon says changed.) I’m not sure, but it seems like changing attributes with a second or third llCreateCharacter() just adds more settings. To clear settings one needs to use the llDeleteCharacter() function. (Not any more) Since this is a testing script for me, I want to clear the settings any time I start to setup the PURSUE action.

In response to a TOUCH I use this code:

   touch_end(integer total_number){
      llDeleteCharacter(); // Not needed
      llCreateCharacter([ CHARACTER_DESIRED_SPEED, 5.0,
         CHARACTER_MAX_ANGULAR_ACCEL, 6.0,
         CHARACTER_RADIUS, 0.125,
         CHARACTER_LENGTH, 0.35]);
      my_owner = llDetectedOwner(0);
   llPursue(my_owner, [PURSUIT_OFFSET, <-2.0, 0.0, 0.0>,
      PURSUIT_FUZZ_FACTOR, 0.2] );
   llSay(0,"Pursuing");
   state pursuing;
   }

You can see I am serious about clearing the attributes before setting new values. Using the llDeleteCharacter() function here is needed because I have a stop function and want to restart. The line of code is actually redunent and could be omitted.

llCreateCharacter() is required. It has to be setup before one can use the llPursue() function. You can see I am setting the speed. The value used is meters per second. The value 5m/s is about 18 km/hr or 6 or 7 miles/hr. I should be able to out run it. It should be able to keep up if I walk.

The value for MAX_ANGULAR_ACCEL controls how fast the bot can rotate around the Z-axis as it turns. I haven’t played with it enough to understand its affect.

      CHARACTER_RADIUS, 0.125,
      CHARACTER_LENGTH, 0.35]);

This code is what I had to research to figure out how to get my little guy down on the ground. The llCreateCharacter() function places a convex hull around the link set. It uses that hull to calculate collisions and contact with the ground. The hull centers around the link set trying to encompass the whole object. My sculpty is throwing it off. Since the sculpty’s bounding box is probably larger than the whole links set, the hull is centered on it. It seems as I shrink the length, which is the height of the hull, I can move my guy down to the ground. If I increase it, I can float him above the ground.

I am using the minimum values. If my sculpty had a bounding box any larger, I would have had to go fix it in Blender. While 0.35 is the minimum I can go larger, which makes it easy to cause things to float.

I am not clear on the default values. I think the default radius is 2m and the default length is 4.1. Whatever the case, you use these values to handle your bot’s relationship with the ground.

This is the actual PURSUE code:

      my_owner = llDetectedOwner(0);
      llPursue(my_owner, [PURSUIT_OFFSET, <-2.0, 0.0, 0.0>,
         PURSUIT_FUZZ_FACTOR, 0.2] );
      llSay(0,"Pursuing");
      state pursuing;

The llDetectedOwner() doesn’t have to feed into a value. I did that from habit. I could have placed the function inside llPursue().

The llPursue() function is easy to setup. There are only 4 options. They change the pursue behavior. One can make it so it looks like a dog chasing or a missile on an interception course.

The offset is centered on the target, in this case me. The -2.0 places it 2 meters behind me. The fuzz factor puts some randomness in calculating the target location. So, the bot is not always exactly behind me. Things get chaotic when I walk toward it. Sometimes it seems to run away. Sometimes it just runs around me. If I spin in place, odd things happen. The MAX_ANGULAR_ACCEL setting seems to affect that behavior a lot. If I spin counter-clockwise, things seem somewhat predictable. If I spin clockwise, every so often my bot makes a break for it and runs away. Sometimes he would travel 10 or 20 meters away before turning around to come back. I can’t always repeat the behavior.

The llSay() is there just so I know where I am in the script while watching the bot.

The state command moves the script into the ‘sensing’ part of the script where the bot is in pusuit. It and the entire pursuit section is not required. I use the move_start and end to see how they work. They trigger PrimPrams just so I can see if they work. I added llSay’s just as a backup and indicators. Those events  work.

You’ll see I have llSetRot() commands. I can’t get that to work. If it worked, it would lean my bot forward as it starts to move.

The path_update is the event that returns events from the Hvok engine. You can see I have a bunch of silly stuff so I can see which events are firing. I have the Evade events just for completeness. I’ll copy paste this code to other scripts, so it is handy to have it include everything.

Summing It Up

Counting blank lines there is about 72 lines of code. Prior to Pathfinding I would need hundreds of lines and the result would not be as realistic or as fast or smart.

I’ll be playing with Evade and Patrol. I’m not sure they will interesting enough to be worth writing a tutorial about. But, they should be fun.

2 thoughts on “Second Life PathFinding Tutorial

  1. Pingback: Pathfinding parte 2. Scriptare un personaggio in LSL. « VIRTUAL WORLDS MAGAZINE

  2. Pingback: Pathfinding parte 2. Scriptare un personaggio in LSL. | VIRTUAL WORLDS MAGAZINE

Leave a Reply

Your email address will not be published. Required fields are marked *