Archive for December, 2012

Pointing and moving towards a target in XNA (2D)

Friday, December 28th, 2012

In this tutorial I will cover how to make something point and move towards a target. This is useful for homing missiles, steering zombies towards your player, pointing a car in the direction you're driving or a gun towards a target, etc, etc. (you get the idea Smiley).

What we will cover

  • What vectors are
  • How to get the direction from one point to another
  • How to move gradually towards a specific target
  • How to point a Texture2D in the correct direction
  • How to choose a rotation center for your images

What vectors are

Imagine you've found a treasure map looking something like this:

treasuremap

This map instructs you to "walk south 4 miles, west 3 miles, and south 2 miles again".

So the instructions consist of three parts, each with a direction and a distance.

In physics a vector is defined as  a direction and a force (think of our distance). So we're basically influencing our position using three vectors.

Those instructions above will lead you to the same end-point as the ones below:

 treasuremap2

But here there is only one instruction: "go in a direction south-south-west, a direction which leads you 6 miles south and 3 miles west".

In XNA we have a class for storing movement in 2 dimensions: the Vector2 class. A Vector2 stores the movement horizontally (X) and vertically (Y), which also makes it useful for storing the position of something. When you are programming in 3D, you can use the Vector3, which gives you an extra dimension (the Z-axis).

But back to 2D... Smiley

In XNA  the top map's instructions would be written:

//create the three parts of the journey
Vector2 first = new Vector2 (0,4);
Vector2 second = new Vector (-3,0);
Vector2 third = new Vector (0,2);

//add them all up
Vector2 result = first + second + third;

//present the result 
Console.WriteLine(result); //writes "{X:-3 Y:6}"

Notice that the arithmethic operators "+", "-", "*", "/" all work on Vector2 - which is very nifty for calculating the resulting force on an object. For example if three forces: gravity, thrust from your rocket engines and meteor collisions all have to result in a believable combined effect on your trusty ol' spaceship Smiley.

How to get the direction from one point to another

Let's look at a different example in XNA. In the illustration below we have two Vector2 objects (3,2) and (4,6). Notice that in this case we are using the Vector2 objects as positions, not movement. So feel free to visualize them as two points:

  • 3 pixels to the right and 2 pixels down from the top-left corner
  • 4 pixels to the right and 6 pixels down from the top-left corner

Targeting_in_xna

If you're wondering why (0,0) is at the top left corner, instead of the bottom left, that's just because that's the way it has been in programming since the dawn of computers Smiley.

Now we want to figure out what the direction is from the shooter to the target (for now as a vector, not radians).

When we want to calculate the direction (and distance) from one object to another we subtract the position of the targeting object from the position of the target. This gives us a vector which contains what we are missing in order to get to the target from the target. Later we'll look at how to turn this into degrees, in order to draw the targeting image correctly.

Targeting_in_xna2

In XNA this would be

//initialize the shooter and target
Vector2 shooter = new Vector2(3,2);
Vector2 target = new Vector2(4,6);

//get the difference from shooter to target
Vector2 differenceToTarget = target - shooter;

//present the result 
Console.WriteLine(result);  //writes "{X:1 Y:4}"

Helping you remember what to subtract from what

If (like me) you have difficulty remembering whether to subtract the shooter's position from the target's or vice-versa, do like me: think of a very simple example: shooter at (1,1), target at (2,2).

(This means the shooter should fire down and right (1,1))

Then find the correct method quickly by trial and error:

shooter minus target: (1,1) - (2,2) = (-1,-1) wrong! ...because (-1,-1) is up and left, away from the target

target minus shooter: (2,2) - (1,1) = (1,1) correct!

Another way to think about it is: when you subtract something from something else, the part you subtract becomes the new "zero", "reference point" or "world center". It becomes the part you compare to or look at other things in relation to.

Think of a grandchild (age 7) and her grandfather (age 67). subtracting the grandchild (7) from the grandfather (67) tells you how much older the grandfather is compared to the grandchild: 67 - 7 = 60.

subtracting the grandfather (67) from the grandchild (7) tells you how much younger the grandchild is compared to the grandfather: 7 - 67 = -60.

When you target something - you want the target's position IN RELATION TO (compared to) the shooter - therefore you subtract the shooter from the target (anyway - it makes sense to me Smiley, der blinker).

 

How to move gradually towards a specific target: getting direction without distance

We still need one more step before we can use the difference between current position and our destination for firing or movement. If we used the calculated difference as the movement for a bullet/car/stumpy dwarf/crusty orc in our Game's Update() method, the moved object would arrive at the destination at once.

In this code sample, imagine we have two Vector2s, storing the position of the player and an orc. We want the orc to chase our player. Here is how to NOT do it Smiley

public void Update(GameTime gameTime)
{
	
	//get the difference from orc to player
	Vector2 differenceToPlayer = playerPosition - orcPosition;
	
	//move the orc towards the player
	//NOT the way to do it - this will make the orc "teleport"
	orcPosition += differenceToPlayer;

}

The result is we set the orc's position to our player's position in an instant:

orc-teleport

(Original images by Krohn, www.OpenGameArt.org)

What we DO want to to do

What we want is to move gradually, so we want to move only part of the distance, but in the right direction.

To get only the direction of a vector you normalize it, resulting in what is called a "unit vector" (a vector with a length of 1). This means that you have the relationship between the Y and X intact but if you use the vector for movement you only move 1 unit of whatever scale your world is in.

Here you can see our original vector and the normalized version

vector_normalized_01vector_normalized_02

Here is the code for normalizing a vector in XNA:

//get difference to target
Vector2 difference = target - shooter;

//normalize to only keep direction
difference.Normalize();

Moving at a certain speed

When you have the unit vector, you can go ahead and multiply your speed onto that vector. This will get you a vector with the length you need for a certain speed. Then you can add the resulting vector to what you want to move:

float orcSpeed = .2f;  //the orc's current speed

public void Update(GameTime gameTime)
{
	
	//get the difference from orc to player
	Vector2 differenceToPlayer = playerPosition - orcPosition;
	
	//** Move the orc towards the player **
	//first get direction only by normalizing the difference vector
	//getting only the direction, with a length of one
	differenceToPlayer.Normalize();

	//then move in that direction
	//based on how much time has passed
	orcPosition  += differenceToPlayer * (float)gameTime.ElapsedGameTime.TotalMilliseconds * orcSpeed;

}

This will make the orc gradually move towards our player - YAY! Smiley med åben mund

orc-move-gradual[4]

How to point a Texture2D in the correct direction

Now that we know how to get a unit vector pointing towards a specific target, we can use it to draw an image pointing towards that target.

To do that we

  1. calculate the direction in radian
  2. draw the image using one of the many overloaded SpriteBatch.Draw() methods, which include a rotation.

But before we can do that, we need to know how to convert a Vector2 to XNA radian.

How the XNA radians differ from the ones you have heard about in geometry

For some reason, in XNA they've chosen to go with a different system of angles than what you may remember from geometry.

In XNA zero degrees radian is UP - traditionally it is RIGHT.

In XNA you go CLOCKWISE as the angle gets greater - traditionally you go COUNTER-CLOCKWISE.

xnadegrees_800

What does this mean for you as a game programmer?

Stay in the traditional coordinate system for all calculation of rotation and movement for units, etc.

Then right before you draw the texture, convert the rotation to the XNA coordinate system Smiley.

So now you know that, here's the code:

How to convert a Vector2 to Xna radian, for drawing a texture

//converts a Vector2 to Radian for use in rotating an image
private float Vector2ToRadian(Vector2 direction)
{
    return (float)Math.Atan2(direction.X, -direction.Y);
}

How to convert euclidian radian to XNA radian for drawing a texture

//converts a euclidean angle in radian to XNA radian for use in rotating an image
private float EuclideanRadianToXnaRadian(float direction)
{
   return (float)Math.Atan2(Math.Cos(angle), (float)Math.Sin(angle));
}

To give you an example of how to use this in a game - I've made a little demo of steering an arrow towards the mouse cursor:

Sample project - steering towards the mouse cursor

Here is a small video showing the contents of the sample project.

Rotation and movement towards a Vector2

Below here is the core of the code from the sample. At the top of the code you can see the variables I need to store information about the arrow and the mouse. In the Update() method you can see how I calculate the rotation of the image using the Vector2ToRadian method above.

Vector2 _arrowDirection;        //direction from the arrow to the mousecursor
Vector2 _arrowposition;         //current position of the arrow
Vector2 _mousePosition;         //mouse's current position
float _arrowRotationInRadians;  //amount to rotate the arrow image to point at the mouse


protected override void Update(GameTime gameTime)
{
    _mouseState = Mouse.GetState();                                 //get the current mouse state

    _mousePosition = new Vector2(_mouseState.X, _mouseState.Y);     //get the target (the mouse cursor)
    _arrowDirection = (Vector2)_mousePosition - _arrowposition;     //get the direction from arrow to cursor

    //make the vector a length of 1 but keep direction
    _arrowDirection.Normalize();

    //for rotating the arrow texture correct (PNG with arrow pointing up)
    _arrowRotationInRadians = Vector2ToRadian(_arrowDirection);

    //if left mousebutton is clicked and the distance is larger than half of the arrow sprite's width:
    if (_mouseState.LeftButton == ButtonState.Pressed && Vector2.Distance(_mousePosition, _arrowposition) > 64)
    {
        //move the arrow sprite
        _arrowposition += _arrowDirection * (float)gameTime.ElapsedGameTime.TotalMilliseconds / 5;
    }

    base.Update(gameTime);
}

Drawing the image

For easy drawing, with the correct rotation in your game, it is necessary to have the image you want to use pointing upwards, as this is 0 degrees radian in XNA as you saw above in the chapter "How the XNA radians differ from the ones you have heard about in geometry".

So if you want a missile to chase someone, or a zombie to face somebody - find or create an image where it is facing up! Like this:

Sprite pointing up

Here is the Draw() method from the sample application.

protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.Navy);   //darkblue background
    spriteBatch.Begin();                //begin drawing

    //draw the arrow image (PNG 128x128 pixels) rotated around the center 
    spriteBatch.Draw(_arrow                     //the arrow texture
                    , _arrowposition            //it's location as a Vector2
                    , null                      //no source rectangle - use entire Texture2D
                    , Color.White               //with the image's own colors
                    , _arrowRotationInRadians   //at the current XNA radians
                    , Vector2.One * 64          //rotate the image around the center
                    , 1                         //use the original scale
                    , SpriteEffects.None        //don't flip or otherwise manipulate image
                    , 0                         //no changes to layer depth
                    );

    base.Draw(gameTime);    //call base class' Draw()
    DrawText();             //draw info
    spriteBatch.End();      //end drawing
}

The three important parameters to understand here are

  1. the _arrowposition - which is where we want the arrow to be drawn
  2. the _arrowRotationInRadians - which is the rotation (in XNA radians) of the image
  3. the Vector2.One * 64 - which is the same as a new Vector2(64, 64), which needs a little more explanation:

How to choose a rotation center for your images

When you use the SpriteBatch.Draw() method which allows you to rotate the Texture2D you're drawing, you also have to pass in the point you're rotating around. By default this is the top left corner of the image, which rarely gives you what you want.

Here you can see how rotation around different positions on a texture gives different results:

rotation

(click image for larger version)

So you can draw your images rotated around any position (even outside the Texture2D) - nifty! Smiley

The code

The complete chase mouse solution as ZIP (xna 4.0 vs2010)

I hope you enjoyed the tutorial, if you have ideas for improving it - please write me at "kr [at] rup [dot] dk"