Difference between revisions of "Modular Pathfinding Kit"

From Second Life Wiki
Jump to navigation Jump to search
m (I hadn't intended to describe brain eating, must cut back on zombie movies)
m (language tags to <source>)
 
(7 intermediate revisions by 4 users not shown)
Line 15: Line 15:
==pathfinding character skeleton==
==pathfinding character skeleton==


<lsl>
<source lang="lsl2">
doSomething()
doSomething()
{
{
//insert pathfinding functionality here
// insert pathfinding functionality here
}
}


startup() //called as necessary from events such as script reset or rezzing
// called as necessary from events such as script reset or rezzing
startup()
{
{
llCreateCharacter([CHARACTER_MAX_SPEED, 25, CHARACTER_DESIRED_SPEED, 15.0]); //create the character
//  create the character
doSomething();
    llCreateCharacter([ CHARACTER_MAX_SPEED, 25,
                        CHARACTER_DESIRED_SPEED, 15.0]);
 
    doSomething();
}
}


default
default
{
{
state_entry() //standard event, called when the script resets
    on_rez(integer start_param)
{
    {
startup();
        llResetScript();
}
    }
 
on_rez(integer start_param) //good to include so it scuttles off when rezzed from inventory or a rezzer
    state_entry()
{
    {
startup();
        startup();
}
    }
}
}
</lsl>
</source>


==Behavioural Tropes==
==Behavioural Tropes==
Line 51: Line 55:
The example below creates a character with a 1.5m tall and 0.25m wide envelope. If the object itself if 0.25 it will appear to be "flying", or, more accurately, hovering.
The example below creates a character with a 1.5m tall and 0.25m wide envelope. If the object itself if 0.25 it will appear to be "flying", or, more accurately, hovering.


<lsl>
<source lang="lsl2">
llCreateCharacter([CHARACTER_MAX_SPEED, 25, CHARACTER_DESIRED_SPEED, 15.0, CHARACTER_RADIUS, 0.25, CHARACTER_LENGTH, 1.5]);
    llCreateCharacter([ CHARACTER_MAX_SPEED, 25,
</lsl>
                        CHARACTER_DESIRED_SPEED, 15.0,
                        CHARACTER_RADIUS, 0.25,
                        CHARACTER_LENGTH, 1.5]);
</source>


In order to further the illusion of flying we can do a number of things. If the creature is something that works at near ground height, such as a hummingbird or dragonfly just use one of the movement tropes, such as wandering.
In order to further the illusion of flying we can do a number of things. If the creature is something that works at near ground height, such as a hummingbird or dragonfly just use one of the movement tropes, such as wandering.
Line 63: Line 70:
For ground hugging creatures we can something similar that looks natural for hovering creatures by dynamically changing the envelope size of the creature by calling [[llUpdateCharacter]], as below. Call this function repeatedly with different values and the character will change the level it hovers at.
For ground hugging creatures we can something similar that looks natural for hovering creatures by dynamically changing the envelope size of the creature by calling [[llUpdateCharacter]], as below. Call this function repeatedly with different values and the character will change the level it hovers at.


<lsl>
<source lang="lsl2">
llUpdateCharacter(CHARACTER_LENGTH, 6.5);
    llUpdateCharacter([CHARACTER_LENGTH, 6.5]);
</lsl>
</source>


===fleeing===
===fleeing===
Line 93: Line 100:
To make a character pause you can use a state change with a new event trigger making the character active again, or simply use something like:
To make a character pause you can use a state change with a new event trigger making the character active again, or simply use something like:


<lsl>
<source lang="lsl2">
llExecCharacterCmd([CHARACTER_CMD_STOP]);
    llExecCharacterCmd(CHARACTER_CMD_STOP, []);
llSleep(15.0);
 
llWanderWithin(llGetPos(), <10.0, 10.0, 5.0>, []); //resume with any desired pathfinding function
    llSleep(15.0);
</lsl>


It's a personal preference but generally speaking state changes are easier to manage and debug.
    vector currentPosition = llGetPos();
//  resume with any desired pathfinding function
    llWanderWithin(currentPosition, <10.0, 10.0, 5.0>, []);
</source>


===wandering===
===wandering===
Line 123: Line 132:
States are an excellent way of managing pathfinding creatures, but note however that pathfinding behaviour survives state changes - it's simple a simpler way of managing a status than using a variable. See the example below, which simulates a rabbit grazing.
States are an excellent way of managing pathfinding creatures, but note however that pathfinding behaviour survives state changes - it's simple a simpler way of managing a status than using a variable. See the example below, which simulates a rabbit grazing.


<lsl>
<source lang="lsl2">
animateGrassEating()
animateGrassEating()
{
{
//insert a cunning grass eating animation here
// insert a cunning grass eating animation here
}
}
 
animateTailWiggle()
animateTailWiggle()
{
{
//insert very cute tail wiggle animation here
// insert very cute tail wiggle animation here
}
}
 
startup() //called as necessary from events such as script reset or rezzing
startup()
{
{
llCreateCharacter([CHARACTER_MAX_SPEED, 2, CHARACTER_DESIRED_SPEED, 1.0]); //create the character
    llCreateCharacter([ CHARACTER_MAX_SPEED, 2.0, CHARACTER_DESIRED_SPEED, 1.0, CHARACTER_DESIRED_TURN_SPEED, 2.0]);
state wander;
    if (TRUE)
    {
        state wander;
    }
}
}
 
default
default
{
{
state_entry() //standard event, called when the script resets
    on_rez(integer start_param)
{
    {
startup();
        llResetScript();
}
    }
on_rez(integer start_param) //good to include so it scuttles off when rezzed from inventory or a rezzer
    state_entry()
{
    {
startup();
        startup();
}
    }
}
}
 
state wander
state wander
{
{
state_entry()
    state_entry()
{
    {
llWanderWithin(llGetPos(), <10.0, 10.0, 5.0>, []);
        vector currentPosition = llGetPos();
}
        llWanderWithin(currentPosition, <10.0, 10.0, 5.0>, []);
    }
path_update(integer type, list reserved)
{
    path_update(integer type, list reserved)
if (type == PU_SLOWDOWN_DISTANCE_REACHED)
    {
{
        if (type == PU_SLOWDOWN_DISTANCE_REACHED)
//we're near the goal, we need to switch behaviour
        {
llExecCharacterCmd([CHARACTER_CMD_SMOOTH_STOP]);
            llExecCharacterCmd(CHARACTER_CMD_SMOOTH_STOP, []);
state graze;
            state graze;
}
        }
}
    }
}
}
 
state graze
state graze
{
{
state_entry()
    state_entry()
{
    {
animateGrassEating();
        animateGrassEating();
llSetTimerEvent(15.0); //replace this with random timer, see next section
}
        llSetTimerEvent(15.0);
    }
timer()
{
    timer()
llSetTimerEvent(0);
    {
animateTailWiggle();
        llSetTimerEvent((float)FALSE);
state wander;
        animateTailWiggle();
}
        state wander;
    }
}
}
</lsl>
</source>


===Random Timers===
===random timers===


In the example above the rabbit eat grass for 15 seconds then wanders again, for a random period defined by how long it takes to reach it's next random wander target. This 15 second wait will make it an easy target for hunting creatures or agents (hunting game? what if this was a zombie bunny with a taste for brains? hmm, brains).
In the example above the rabbit eat grass for 15 seconds then wanders again, for a random period defined by how long it takes to reach it's next random wander target. This 15 second wait will make it an easy target for hunting creatures or agents (hunting game? what if this was a zombie bunny with a taste for brains? hmm, brains).
Line 194: Line 207:
To make things less predictable use:
To make things less predictable use:


<lsl>
<source lang="lsl2">
llSetTimerEvent(llFrand(15.0));
//  random period in between [0.0, 15.0)
</lsl>
//  which means the random float returned could be 0.0 but not 15.0
 
    llSetTimerEvent( llFrand(15.0) );
</source>
 
<source lang="lsl2">
//  random period in between (0.0, (30.0-random number up to 15)]
//  which means the resulting float could be 0 but not (30 - random number up to but not including 15)
 
    llSetTimerEvent( 30.0 - llFrand(15.0) );
 
</source>
 
===enabled check===


This generates a random grazing period of between 0s and 15s, each time it's called.
If you're giving other people your creations, you'd be wise to have your script check to see if pathfinding is enabled where they rez it. Pathfinding can be disabled on private regions.


<source lang="lsl2">
// returns TRUE if patfinding is enabled, FALSE if not.
integer IsPFEnabled()
{
    if ( llGetEnv("dynamic_pathfinding") == "enabled" )
        return TRUE;
    return FALSE;
}
</source>
----
----
Return to [[Good_Building_Practices]]
Return to [[Good_Building_Practices]]

Latest revision as of 14:34, 25 January 2015

There is a lot of options for how to create pathfinding creatures. It’s best to start by having an idea of what you want to create, either a real world analogue like a horse or a dog, or something mythical such as a ghost or fairy.

things that don’t work (at present):
   1. 3D spatial movement - for example underwater free movement or aerial free movement
   2. mixing physics and pathfinding (for instance using llApplyImpulse with a pathfinding character)

Quick Guide

> object + pathfinding character skeleton + behavioural tropes = autonomous pathfinding creature

This guide gives quick guidance of how to create specific behavioural tropes (commonly found autonomous characteristics) and doesn't go into detail about individual functions - see Pathfinding LSL Functions for more details.

Components

pathfinding character skeleton

doSomething()
{
//  insert pathfinding functionality here
}

//  called as necessary from events such as script reset or rezzing
startup()
{
//  create the character
    llCreateCharacter([ CHARACTER_MAX_SPEED, 25,
                        CHARACTER_DESIRED_SPEED, 15.0]);

    doSomething();
}

default
{
    on_rez(integer start_param)
    {
        llResetScript();
    }

    state_entry()
    {
        startup();
    }
}

Behavioural Tropes

flying/hovering

3D spatial free flight is at present unavailable. We can however fake it but one of the following methods:

  1. create a small object with a tall root prim - not recommended, causes excessive collisions
  2. create a small object and create a large character envelope - recommended

The example below creates a character with a 1.5m tall and 0.25m wide envelope. If the object itself if 0.25 it will appear to be "flying", or, more accurately, hovering.

    llCreateCharacter([ CHARACTER_MAX_SPEED, 25,
                        CHARACTER_DESIRED_SPEED, 15.0,
                        CHARACTER_RADIUS, 0.25,
                        CHARACTER_LENGTH, 1.5]);

In order to further the illusion of flying we can do a number of things. If the creature is something that works at near ground height, such as a hummingbird or dragonfly just use one of the movement tropes, such as wandering.

If you want the creature to fly higher than the 10.0m maximum character height (which will give a centre of mass height of 5.0m), we can use a platform. If you want a parrot to fly between trees place a prim or series of prims that stretch between them and use something like llPatrolPoints or llWanderWithin. Even if you use a series of narrow pathways the pathfinding will find a way to valid targets.

We can make a really nice effect by using a mesh surface with an undulating form - the creature will follow the dips and rises giving the appearance of varying flight.

For ground hugging creatures we can something similar that looks natural for hovering creatures by dynamically changing the envelope size of the creature by calling llUpdateCharacter, as below. Call this function repeatedly with different values and the character will change the level it hovers at.

    llUpdateCharacter([CHARACTER_LENGTH, 6.5]);

fleeing

There are two distinct behaviours we can exploit. If we wanted a character to run from a specific point such as an explosion we can use llFleeFrom. If we want to run from a specific object or agent (avatar) use llEvade

hiding

Part of llEvade is hiding (blocked line of sight) from the agent/object the character is fleeing from. See llEvade for more details.

move-wait-repeat

By responding to path events we can create varying behaviour. All pathfinding functions are moving to specific targe coordinates, whether determined randomly by something like llWanderWithin, specific as in llPatrolPoints or even llPursue.

Have a look at Path update for guidance, and see the example script in the multiple states discussion at the end of this article.

chase

Use llPursue and specify a target object or avatar.

hunt

Use a sensor of some description, such as llSensorRepeat or some sort of collision detection such llVolumeDetect

wait

To make a character pause you can use a state change with a new event trigger making the character active again, or simply use something like:

    llExecCharacterCmd(CHARACTER_CMD_STOP, []);

    llSleep(15.0);

    vector currentPosition = llGetPos();
//  resume with any desired pathfinding function
    llWanderWithin(currentPosition, <10.0, 10.0, 5.0>, []);

wandering

Use llWanderWithin to specify an area to wander within.

hopping/jumping

Use llExecCharacterCmd(CHARACTER_CMD_JUMP, [height, 1.0]) to make the character hop 1m into the air. If done with a timer event while using another pathfinding command such as llWanderWithin the hop will have an arc rather than just vertical movement.

Optional Extras

To add to immersion it's worth adding some extras. If desired add...

animation

see Animation Streamlined for tips

sounds

see Creature Sounds and Event Driven Sounds

more than one state

States are an excellent way of managing pathfinding creatures, but note however that pathfinding behaviour survives state changes - it's simple a simpler way of managing a status than using a variable. See the example below, which simulates a rabbit grazing.

animateGrassEating()
{
//  insert a cunning grass eating animation here
}
 
animateTailWiggle()
{
//  insert very cute tail wiggle animation here
}
 
startup()
{
    llCreateCharacter([ CHARACTER_MAX_SPEED, 2.0, CHARACTER_DESIRED_SPEED, 1.0, CHARACTER_DESIRED_TURN_SPEED, 2.0]);
    if (TRUE)
    {
        state wander;
    }
}
 
default
{
    on_rez(integer start_param)
    {
        llResetScript();
    }
 
    state_entry()
    {
        startup();
    }
}
 
state wander
{
    state_entry()
    {
        vector currentPosition = llGetPos();
        llWanderWithin(currentPosition, <10.0, 10.0, 5.0>, []);
    }
 
    path_update(integer type, list reserved)
    {
        if (type == PU_SLOWDOWN_DISTANCE_REACHED)
        {
            llExecCharacterCmd(CHARACTER_CMD_SMOOTH_STOP, []);
            state graze;
        }
    }
}
 
state graze
{
    state_entry()
    {
        animateGrassEating();
 
        llSetTimerEvent(15.0);
    }
 
    timer()
    {
        llSetTimerEvent((float)FALSE);
        animateTailWiggle();
        state wander;
    }
}

random timers

In the example above the rabbit eat grass for 15 seconds then wanders again, for a random period defined by how long it takes to reach it's next random wander target. This 15 second wait will make it an easy target for hunting creatures or agents (hunting game? what if this was a zombie bunny with a taste for brains? hmm, brains).

To make things less predictable use:

//  random period in between [0.0, 15.0)
//  which means the random float returned could be 0.0 but not 15.0

    llSetTimerEvent( llFrand(15.0) );
//  random period in between (0.0, (30.0-random number up to 15)]
//  which means the resulting float could be 0 but not (30 - random number up to but not including 15)

    llSetTimerEvent( 30.0 - llFrand(15.0) );

enabled check

If you're giving other people your creations, you'd be wise to have your script check to see if pathfinding is enabled where they rez it. Pathfinding can be disabled on private regions.

// returns TRUE if patfinding is enabled, FALSE if not.
integer IsPFEnabled()
{
    if ( llGetEnv("dynamic_pathfinding") == "enabled" ) 
        return TRUE;
    return FALSE;
}

Return to Good_Building_Practices