Difference between revisions of "Modular Pathfinding Kit"
Myopic Mole (talk | contribs) m (fixed glaring scripting error before anyone noted ;-)) |
m (language tags to <source>) |
||
(10 intermediate revisions by 4 users not shown) | |||
Line 15: | Line 15: | ||
==pathfinding character skeleton== | ==pathfinding character skeleton== | ||
< | <source lang="lsl2"> | ||
doSomething() | 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 | default | ||
{ | { | ||
on_rez(integer start_param) | |||
{ | |||
llResetScript(); | |||
} | |||
state_entry() | |||
{ | |||
startup(); | |||
} | |||
} | } | ||
</ | </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. | ||
< | <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, | ||
</ | 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. | ||
< | <source lang="lsl2"> | ||
llUpdateCharacter(CHARACTER_LENGTH, 6.5); | llUpdateCharacter([CHARACTER_LENGTH, 6.5]); | ||
</ | </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: | ||
< | <source lang="lsl2"> | ||
llExecCharacterCmd( | llExecCharacterCmd(CHARACTER_CMD_STOP, []); | ||
llSleep(15.0); | |||
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. | ||
< | <source lang="lsl2"> | ||
animateGrassEating() | animateGrassEating() | ||
{ | { | ||
// insert a cunning grass eating animation here | |||
} | } | ||
animateTailWiggle() | animateTailWiggle() | ||
{ | { | ||
// insert very cute tail wiggle animation here | |||
} | } | ||
startup() | startup() | ||
{ | { | ||
llCreateCharacter([ CHARACTER_MAX_SPEED, 2.0, CHARACTER_DESIRED_SPEED, 1.0, CHARACTER_DESIRED_TURN_SPEED, 2.0]); | |||
if (TRUE) | |||
{ | |||
state wander; | |||
} | |||
} | } | ||
default | default | ||
{ | { | ||
on_rez(integer start_param) | |||
{ | |||
llResetScript(); | |||
} | |||
state_entry() | |||
{ | |||
startup(); | |||
} | |||
} | } | ||
state wander | 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 graze | ||
{ | { | ||
state_entry() | |||
{ | |||
animateGrassEating(); | |||
llSetTimerEvent(15.0); | |||
} | |||
timer() | |||
{ | |||
llSetTimerEvent((float)FALSE); | |||
animateTailWiggle(); | |||
state wander; | |||
} | |||
} | } | ||
</ | </source> | ||
=== | ===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: | ||
< | <source lang="lsl2"> | ||
llSetTimerEvent(llFrand(15.0)); | // 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) ); | |||
</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=== | |||
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:
- create a small object with a tall root prim - not recommended, causes excessive collisions
- 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