Collision Avoidance FSM
Overview
Implemented in Visual Studio 2010 / Microsoft's XNA Game Studio 4 extending the Academic Graphics MonoGames (XNA) Starter Kit
Collision spheres have a fixed radius of 50, and after considerable experimentation the most effective layout of 5 sensors was created as shown in the image above. The two outside sensors, “Left Side” and “Right Side” are used primarily for wall following, by keeping a sensor “active” the default action of turning towards the destination is negated. 49 out of 50 frames using a random number generator nothing happens when only this outside sensor is active, that 1/50th frame turns slightly away from the wall. Combined with the default action of turning towards the destination which keeps the agent against the wall the interplay between these 2 commands creates a very smooth motion when running along a long, straight wall.
Sensor |
Size |
Location |
Description |
Left Side |
50 |
200 forward, 180 left |
wall following |
Left |
50 |
200 forward, 60 left |
trigger to turn right |
Middle |
50 |
200 forward |
amplify turns |
Right |
50 |
200 forward, 60 right |
trigger to turn left |
Right Side |
50 |
200 forward, 180 right |
wall following |
Sensors * (left to right) |
Description |
Action |
Notes |
O XXX X |
wall-following |
turn right 1/50 frames |
Randomly skipping 49 out of 50 updates is used to smooths jitter when wall-following |
X XXX O |
wall-following |
turn left 1/50 frames |
Randomly skipping 49 out of 50 updates is used to smooths jitter when wall-following |
_ OXX X |
obstacle hit |
medium speed turn right |
|
X XXO _ |
obstacle hit |
medium speed turn left |
|
O OO_ X |
blocked on left |
fast speed turn right |
|
X _OO O |
blocked on right |
fast speed turn left |
|
O OOO O |
completely blocked |
** |
If last turn was to the right, very fast turn right. else very fast turn to the left |
X XXX X |
no sensors hit |
** |
Slowly turn towards last valid destination |
* O = triggered X = not triggered _ = sensor state ignored
Obstacle Avoidance Issues
The inner three sensors are used for turning to avoid obstacles. When the “left”/”right” sensor alone are triggered, the agent turns at a medium speed in the opposite direction. When the middle sensor and a side sensor are triggered, the turning rate is increased to fast.
One problem exists when all of the sensors are triggered simultaneously. This condition when the pathway is completely blocked uses a simple Boolean flag “rightTurn” to look at the recent history in order to determine which direction to turn. If the agent was previously turning left, continuing to turn left when all the sensors are triggered is usually the ideal action. If the agent was previously turning right, continuing to turn right … etc… This Boolean value is only used for this one special case and nothing else.
Another issue is returning to a previous destination. A new vector3 variable “destination” was added to the Object3D class to keep track of this information and code throughout the rest the program was modified to set this value every time the agent turns towards another treasure or nav point. Turning slowly back towards this destination every time no sensors were impacted keeps the agent moving towards its real goal whenever possible. To prevent nearby treasures from interfering with obstacle avoidance, the change path method will not trigger if obstacle avoidance is running.
Code
float fX = sphereDistance * orientation.Forward.X; // shorthand
float fZ = sphereDistance * orientation.Forward.Z;
float rX = sphereDistance * orientation.Right.X;
float rZ = sphereDistance * orientation.Right.Z;
float slowTurn = 0.005f; // Turning
rates
float mediumTurn = 0.01f;
float fastTurn = 0.04f;
float veryFastTurn = 0.08f;
//Sensors: X ... . // Wall
following, makes an adjustment only every 50 frames roughly
if (SensorLeftSide
&& !SensorLeft && !SensorMiddle && !SensorRight
&& !SensorRightSide)
{
if (random.NextDouble()
< 0.02) // 1/50th of the time
{
turnToFace(Translation + new
Vector3(fX + fastTurn * rX, 0.0f,
fZ
+ fastTurn * rZ)); // turn right
} rightTurn = true;
}
//Sensors: . ... X // Wall
following
if (!SensorLeftSide
&& !SensorLeft && !SensorMiddle && !SensorRight
&& SensorRightSide)
{
if (random.NextDouble()
< 0.02) // 1/50th of the time
{
turnToFace(Translation + new
Vector3(fX - fastTurn * rX, 0.0f,
fZ
- fastTurn * rZ)); // turn left
}
rightTurn = false;
}
//Sensors: _ X.. . // Obstacle
hit on left
if (SensorLeft
&& !SensorMiddle && !SensorRight && !SensorRightSide)
{
turnToFace(Translation + new Vector3(fX + mediumTurn
* rX, 0.0f, fZ + mediumTurn * rZ)); // right
rightTurn = true;
}
//Sensors: . ..X _ // Obstacle hit on right
if (!SensorLeftSide
&& !SensorLeft && !SensorMiddle && SensorRight)
{
turnToFace(Translation + new Vector3(fX - mediumTurn
* rX, 0.0f, fZ - mediumTurn * rZ)); // left
rightTurn = false;
}
//Sensors: X XX_ . // Blocked the left, turn right quickly
if (SensorLeftSide
&& SensorLeft && SensorMiddle
&& !SensorRightSide)
{
turnToFace(Translation + new Vector3(fX + fastTurn
* rX, 0.0f, fZ + fastTurn * rZ)); // right
rightTurn = true;
}
//Sensors: . _XX X // Blocked on
the right, turn left quickly
if (!SensorLeftSide
&& SensorMiddle && SensorRight && SensorRightSide)
{
turnToFace(Translation + new Vector3(fX - fastTurn
* rX, 0.0f, fZ - fastTurn * rZ)); // left
rightTurn = false;
}
//Sensors: X XXX X // All sensors are triggered, look at RightTurn variable to
// determine which way to continue turning based on last
turn direction
if (SensorLeftSide
&& SensorLeft && SensorRight
&& SensorRightSide)
{
if (rightTurn)
turnToFace(Translation + new
Vector3(fX + veryFastTurn * rX, 0.0f,
fZ
+ veryFastTurn * rZ)); // right
else
turnToFace(Translation + new
Vector3(fX - veryFastTurn * rX, 0.0f,
fZ
- veryFastTurn * rZ)); // left
}
// no sensor data then slowly turn to treasure
if (!SensorMiddle &&
!SensorLeft && !SensorRight
&& !SensorLeftSide && !SensorRightSide)
{
turnToFace(Vector3.Lerp(Translation + new
Vector3(fX, 0.0f, fZ),
destination,
slowTurn));
}