XNA Game Programming—Wandering
| CSharp-Online.NET:Articles |
| .NET Articles |
| © 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.
|

