XNA Game Programming—Wandering


Jump to: navigation, search
CSharp-Online.NET:Articles
.NET Articles

Create a third person shooter game

© 2008 Lobão, et. al.

Wandering

In the Wandering state, the enemy walks randomly through the map, without a specific goal. To execute this action, you need to generate random positions over the map within a radius from the enemy's actual position and make the enemy move to these positions. Following are the attributes of the Enemy class used by the Wandering state:

static int WANDER_MAX_MOVES = 3;
static int WANDER_DISTANCE = 70;
static float WANDER_DELAY_SECONDS = 4.0f;
 
static float MOVE_CONSTANT = 35.0f;
static float ROTATE_CONSTANT = 100.0f;
 
// Wander
int wanderMovesCount;
Vector3 wanderStartPosition;
Vector3 wanderPosition;

The WANDER_MAX_MOVES variable defines the number of random movements that the enemy makes until he returns to his initial position, and the wanderMovesCount variable stores the number of movements that the unit has already made. You can use these variables to restrict the distance that the enemy could reach from his initial position, forcing him to return to his start position after a fixed number of random movements. Besides that, the WANDER_DELAY_SECONDS variable stores the delay time between each movement of the unit. The WANDER_DISTANCE variable stores the minimum distance that the unit walks in each movement, and the variables wanderStartPosition and wanderPosition store, respectively, the enemy's initial position and destination while in the Wandering state. Finally, MOVE_CONSTANT and ROTATE_CONSTANT store a constant value used to move and rotate the enemy.

To execute the enemy's Wandering state you'll create the Wander method. In the Wander method, you first check if the enemy has already reached his destination position, which is stored in the wanderPosition attribute. To do that, you create a vector from the enemy's position to his destination and use the length of this vector to check the distance between them. If the distance is below a defined epsilon value (for example, 10.0), the enemy has reached his destination and a new destination must be generated:

// Calculate wander vector on X, Z axis
Vector3 wanderVector = wanderPosition - Transformation.Translate;
wanderVector.Y = 0.0f;
float wanderLength = wanderVector.Length();
 
// Reached the destination position
if (wanderVector.Length() < DISTANCE_EPSILON)
{
   // Generate a new wander position
}

Note that when the enemy is created, his first destination position is equal to his start position.

If the number of random movements the enemy makes is lower than the maximum number of consecutive random movements that he could make, his new destination position will be a random generated position. Otherwise, the next enemy destination will be his start position.

// Generate a new random position
if (wanderMovesCount < WANDER_MAX_MOVES)
{
   wanderPosition = Transformation.Translate +
      RandomHelper.GeneratePositionXZ(WANDER_DISTANCE);
   wanderMovesCount++;
}
 
// Go back to the start position
else
{
   wanderPosition = wanderStartPosition;
   wanderMovesCount = 0;
}
 
// Next time wander
nextActionTime = (float)time.TotalGameTime.TotalSeconds +
   WANDER_DELAY_SECONDS + WANDER_DELAY_SECONDS *
   (float)RandomHelper.RandomGenerator.NextDouble();

The enemy's random destination position is generated through the GeneratePositionXZ method of your RandomHelper class. After generating the enemy's destination, you also generate a random time used to start moving the enemy to his new destination. Following is the complete code for the Wander method of the Enemy class:

private void Wander(GameTime time)
{
   // Calculate wander vector on X, Z axis
   Vector3 wanderVector = wanderPosition - Transformation.Translate;
   wanderVector.Y = 0.0f;
   float wanderLength = wanderVector.Length();
 
   // Reached the destination position
   if (wanderLength < DISTANCE_EPSILON)
   {
      SetAnimation(EnemyAnimations.Idle, false, true, false);
 
      // Generate a new random position
      if (wanderMovesCount < WANDER_MAX_MOVES)
      {
         wanderPosition = Transformation.Translate +
            RandomHelper.GeneratePositionXZ(WANDER_DISTANCE);
         wanderMovesCount++;
      }
      // Go back to the start position
      else
      {
         wanderPosition = wanderStartPosition;
         wanderMovesCount = 0;
      }
 
      // Next time wander
      nextActionTime = (float)time.TotalGameTime.TotalSeconds +
         WANDER_DELAY_SECONDS + WANDER_DELAY_SECONDS *
      (float)RandomHelper.RandomGenerator.NextDouble();
   }
 
   // Wait for the next action time
   if ((float)time.TotalGameTime.TotalSeconds > nextActionTime)
   {
      wanderVector *= (1.0f / wanderLength);
      Move(wanderVector);
   }
}

At the end of the Wander method, you check if the time for the next wander action has arrived. In this case, you normalize the wanderVector, which contains the direction from the enemy to his destination, and makes the enemy move in this direction through the Move method.

You'll create the Move method tomove the enemy from his original position using an arbitrary direction vector. You can move the enemy by setting his linear velocity as the desired direction vector, inside the Move method. Remember that the enemy's position is updated according to his linear velocity by the Update method's base class (TerrainUnit). While moving the unit, you also need to set its angular velocity, heading the unit in the same direction it is moving. Following is the code for the Move method:

private void Move(Vector3 direction)
{
   // Change enemy's animation
   SetAnimation(EnemyAnimations.Run, false, true,
      (CurrentAnimation == EnemyAnimations.TakeDamage));
   // Set the new linear velocity
   LinearVelocity = direction * MOVE_CONSTANT;
 
   // Angle between heading and move direction
   float radianAngle = (float)Math.Acos(
      Vector3.Dot(HeadingVector, direction));
   if (radianAngle >= 0.1f)
   {
      // Find short side to rotate
      // Clockwise (CW) or CCW (Counterclockwise)
      float sideToRotate = Vector3.Dot(StrafeVector, direction);
      Vector3 rotationVector = new Vector3(0, ROTATE_CONSTANT *
         radianAngle, 0);
      if (sideToRotate > 0)
         AngularVelocity = -rotationVector;
      else
         AngularVelocity = rotationVector;
   }
}

In the Move method, you first set the linear velocity of the enemy as its direction parameter multiplied by the MOVE_CONSTANT variable. Next, you calculate the angle between the enemy's heading vector and its direction vector. You need this angle to rotate the unit and head it in the same direction it is moving. You can use the Dot method of XNA's Vector3 class to get the cosine of the angle between the enemy's heading vector and its direction vector, and the Acos method of the Math class to get the angle between these vectors from its cosine. After calculating the angle between the enemy's heading and direction, you still need to know from which side to rotate the unit—clockwise (CW) or counterclockwise (CCW). For example, you can find that the angle between the enemy's heading and direction is 90 degrees, but you still don't know from which side to rotate him.

You can find the correct side to rotate the enemy, calculating the cosine of the angle between the enemy's strafe vector—which is perpendicular to the heading vector—and its direction vector. If the cosine is positive, you need to apply a negative rotation on the enemy, making him rotate clockwise; otherwise, you need to apply a positive rotation, making him rotate counterclockwise. The rotation is set as the enemy's AngularVelocity and is multiplied by the ROTATE_CONSTANT variable.


Previous_Page_.gif Next_Page_.gif


Personal tools