Listing 7.5. Player Direction Enumeration
///
/// The direction the player model is facing
///
public enum PlayerDirection
{
Down,
Up,
Left,
Right
}
You also add a new method to the Player class that will accept this enumeration as a parameter and use it to update the current rotation of the player. Use the method in Listing 7.6.
Listing 7.6. Updating Player Direction
public void SetDirection(PlayerDirection dir)
{
switch(dir)
{
case PlayerDirection.Down:
rotation = RotationMatrixFacingDown;
break;
case PlayerDirection.Up:
rotation = RotationMatrixFacingUp;
break;
case PlayerDirection.Left:
rotation = RotationMatrixFacingLeft;
break;
case PlayerDirection.Right:
rotation = RotationMatrixFacingRight;
break;
}
}
As you see here, you are simply taking the enumeration and applying the correct transformation matrix to the stored variable. The methodology to accomplish this task is quite readable but could be done slightly more efficiently at the cost of readability. For example, you could eliminate the case statement completely by using an array of matrix constants in place of the four named directional constants, such as the following:
private static readonly Matrix[] RotationMatrices = new Matrix[] {
Matrix.RotationY((float)Math.PI * 3.0f / 2.0f),
Matrix.RotationY((float)Math.PI / 2.0f),
Matrix.RotationY((float)Math.PI * 2),
Matrix.RotationY((float)Math.PI) };
This code would allow you to update the SetDirection method into a single line:
public void SetDirection(PlayerDirection dir)
{
rotation = RotationMatrices[(byte)dir];
}
Although it is syntactically identical to the previous "version" of the code, this section is much less readable and only marginally better performing. Sometimes you need to get every bit of performance out of a method, but other times you want it to be more "maintainable." Determining which is the most appropriate quality for a particular scenario is really the crux of good development.
Anyway, movement appears at least partially covered because the current position of the player and the position the player is moving to have been separated, but currently there isn't anywhere that you update the physical position of the player and move it closer to where it's going. Remember, the main game engine has a method called every frame where the game state is updated, and the player's position seems like a great bit of game state that needs updating. Rather than do the work in the game engine class, though, you should add a method to the Player class that will do the work. You find this method in Listing 7.7.
Listing 7.7. Updating the Player
///
/// Update the player's position based on time
///
/// elapsed time since last frame
/// total time
public void Update(float elapsed, float total)
{
// Calculate the new player position if needed
playerHeight = (float)(Math.Abs(Math.Sin(total * BounceSpeed))) *
MaxBounceHeight;
if (pos != moveToPos)
{
Vector3 diff = moveToPos - pos;
// Are we close enough to just move there?
if (diff.LengthSq() > (MaxMovement * elapsed))
{
// No we're not, move slowly there
diff.Normalize();
diff.Scale(MaxMovement * elapsed);
pos += diff;
}
else
{
isMoving = false;
// Indeed we are, just move there
pos = moveToPos;
}
}
else
{
isMoving = false;
}
}
This method uses three constants to govern player movement that you will need to add to your class's declarations:
// Maximum speed the player will move per second
private const float MaxMovement = 30.0f;
// Maximum 'bounce' speed
private const float BounceSpeed = 10.0f;
private const float MaxBounceHeight = 0.4f;
These constants control the maximum speed the player can move per second (30 units is the default), the speed at which the player bounces, and the maximum height of the bounce. The playerHeight variable, which is used during the translation for rendering, is calculated first. Taking the absolute value of the sine of the total time the application has been running gives us a consistently updating range of numbers between 0 and 1. Multiplying them by the BounceSpeed constant can artificially increase the range (in this case, from 0 through 10). Increasing this constant causes the bouncing to become quicker, and decreasing it has the opposite effect.
After the player's height is established, you need to check whether you're in the position you're trying to move to. If you are, you set the isMoving variable to false because there isn't anything else to do and you are no longer moving. If you are not, however, there are some things you need to do. First, find the difference vector between where you are and where you want to be. The length of this vector tells you how far you need to go to get to where you want to be.
You'll notice that there is a Length property on the Vector, yet this code uses the LengthSq property instead, which is the Length property squared. Actually, the length of a vector is calculated by taking the square root of x2 + y2 + z2. Obviously, taking the square root is an "expensive" operation, so eliminating that calculation can only help. You take the squared length variable and see whether that is greater than the maximum distance you could move this frame; if so, simply move there.
If you cannot simply move there, you need to normalize the difference vector. Normalization is the act of taking the vector and making it have a unit length (or a length of 1.0f). The "ratio" of the components stays the same; the length is simply scaled to unit size. You can then scale the now normalized difference vector by the maximum movement you can make this frame (based on the elapsed time since the last frame). Adding the newly scaled difference vector to your position moves you toward the position you are trying to get to. |