Page 1 of 1

Uniform Motion of a Sprite with XNA User controlled with keyboard

#1 SixOfEleven  Icon User is offline

  • using Caffeine;
  • member icon

Reputation: 945
  • View blog
  • Posts: 6,342
  • Joined: 18-October 08

Post icon  Posted 30 November 2009 - 07:28 PM

Uniform Motion of a Sprite with XNA


A common problem in some games is that when you move the sprite on the screen diagonally it will move faster than when you move the sprite in cardinal directions, left, right, up or down. To see why think of a right angles triangle where the adjacent sides are both 1 unit long. The Pythagorean tells us that the hypotenuse of that triangle is the square root of 1^2 + 1^2 which is approximately 1.4 and is greater than 1. So you can see that if a sprite was traveling diagonally across the screen it would travel faster than if it was just going left , right, up or down. Fortunately there is an easy way to fix this.

To get started create a new XNA Game Studio project, it can be XNA 3.0 or 3.1, it doesn't matter, and name it UniformMotion. You will need a small image for this. It doesn't really matter what one you choose. I chose the ship below that I made for an asteroids clone. The pink parts of the image are by default in XNA transparent when you use the Alpha blend option in the call to the Begin method of the SpriteBatch class. Right click the Content folder of the game project in the solution explorer select Add and then Existing Item. Navigate to your image and add it to the content folder. My image was named ship.bmp if yours was named something else you will need to change the asset name when you load the image in the LoadContent method.

Attached File  ship.bmp (3.05K)
Number of downloads: 331

For this tutorial, and future tutorials, I will be using a class called GameObject for dealing with sprites. So right click the game in solution explorer, select Add and then Class and call it GameObject. In my tutorials I usually show code if there is a lot of it and then explain what it does. This is the code for the GameObject class.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace UniformMotion
{
    class GameObject
    {
        Texture2D texture;
        Vector2 position;
        float speed = 3f;
        int windowWidth;
        int windowHeight;

        public GameObject(Texture2D texture, int windowWidth, int windowHeight)
        {
            this.texture = texture;
            this.windowWidth = windowWidth;
            this.windowHeight = windowHeight;

            this.position = new Vector2(
                (windowWidth - texture.Width) / 2,
                (windowHeight - texture.Height) / 2);
        }

        public void Draw(SpriteBatch spriteBatch)
        {
            spriteBatch.Draw(texture,
                position,
                Color.White);
        }

        public void Update(Vector2 motion)
        {
            if (motion != Vector2.Zero)
            {
                motion.Normalize();
                position += motion * speed;
            }
        }
    }
}



Since this class uses some of the classes in the XNA framework I've included using statements for the XNA framework and the XNA framework Graphics classes. There are five fields in the class: a Texture2D called texture that holds the image of the sprite, a Vector2 called position that will hold the position of the sprite in the window, a float called speed that will control the speed that the sprite moves at on the window and two int variables that will hold the width and the height of the window called windowWidth and windowHeight respectively. All of the fields are private because I'm using object-oriented programming for this. The person using the class should not need to know how the class works internally. They should just know what it does and what methods and properties of the class they can use. The speed field is initially set to 3f which is a reasonable speed for a sprite to travel across the window.

The constructor of this class takes three parameters: a Texture2D called texture for the image of the sprite and two int parameters for the width and height of the window called windowWidth and windowHeight. The constructor sets the texture and windowWidth and windowHeight fields with the arguments passed in. It then places the sprite in the middle of the window. To center an object in the window you take the width of the window, subtract the width of the object and divide the result by two to get the X value. Similarly for the Y value you take the height of the window, subtract the height of the object and divide that by two.

The Draw method in this class will be used to draw the sprite. It takes as a parameter a SpriteBatch object called spriteBatch. This method should be called between calls to the Begin and End method of the SpriteBatch class. The call to the Draw method of the SpriteBatch class uses the overload that takes as parameters: a Texture2D for the texture of the object, a Vector2 for the position of the object and a color parameter for the color you want to tint the object. You can tint sprites different colors by specifying other colors. Colors are defined using A, R, G and B byte values. A stands for alpha, R stands for red, G stands for Green and B stands for blue. A color that is all white has values of 255 for all R, G and B values. By specifying those values the object will not be tinted. By lower the A, R, G or B values will cause the sprite to be tinted. A is special. If you reduce it the sprite will start to become more and more transparent. If you specify 0 for A the sprite will be fully transparent and you will not be able to see it at all. By reducing the R value you increase the level of red in the sprite. Similarly for G and B decreasing G will make the sprite greener and B will make the sprite bluer.

The Update method takes as a parameter a Vector2 called motion. This is what you will use to control the rate at which the sprite moves across the window. What you do is first check to make sure that the motion vector is not the zero vector (0, 0). The reason is that I will be normalizing the vector. What that does is create a unit vector that has a length of 1. The cardinal directions already have a length of 1 but diagonals have a length of approximately 1.4 which is why an object moves faster on diagonals. You can't normalize a vector that has no length. If you try you will get an exception. After normalizing the vector you can multiply it by the speed value which will have it move at a uniform rate. So to have the sprite move I set its position to be its old position plus the motion vector times the speed. Though the vector will have a length of 1 it can still have a negative direction so the sprite can travel in all directions.

Now that we have our game object that will update and draw itself we need to add it to the game. Switch to the code for your game. I will be using the keyboard for this tutorial, in a future tutorial I will cover using the XBOX 360 game controller to have uniform motion. You will need to add three fields to the class. One for a GameObject called gameObject, a Vector2 called motion and a KeyboardState called keyboardState. These are the fields.

GameObject gameObject;
Vector2 motion;
KeyboardState keyboardState;



You will need a Texture2D to create the GameObject so you will do that in the LoadContent method. What you will do is create a new instance of the GameObject class passing in a Texture2D for the GameObject as well as the width and height of the window. This is the code for the LoadContent method.

protected override void LoadContent()
{
    spriteBatch = new SpriteBatch(GraphicsDevice);
    gameObject = new GameObject(
        Content.Load<Texture2D>("ship"),
        window.ClientBounds.Width,
        window.ClientBounds.Height);
}



You use the window.ClientBounds.Width property to get the width of the window and the Windows.ClientBounds.Height property to get the height of the window. I used the Content.Load<Texture2D> method to load in the image. If you used something other than the ship image I used you will want to put in the name of the file with out the extension there. The reason you don't include the extension is that when you compile your game to run it XNA will you to Content Pipeline to import the asset, process it and write it out as an XNB file. Then when your game runs it reads in the XNB file, not the actual file that you used. You can create custom Content Pipeline importers, processors, writers and readers to protect custom assets from being tampered with. This is great if you want to use an XML file to add scripts or levels to your game and don't want other people messing with your content.

Now that we have our GameObject we need to be able to move it around the window. You will of course do this in the Update method. I will show you the code for the Update method and then explain it.

protected override void Update(GameTime gameTime)
{
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
        this.Exit();
    keyboardState = Keyboard.GetState();
    motion = Vector2.Zero;

    if (keyboardState.IsKeyDown(Keys.Down))
    {
        motion.Y++;
    }
    if (keyboardState.IsKeyDown(Keys.Up))
    {
        motion.Y--;
    }
    if (keyboardState.IsKeyDown(Keys.Right))
    {
        motion.X++;
    }
    if (keyboardState.IsKeyDown(Keys.Left))
    {
        motion.X--;
    }
    gameObject.Update(motion);
    base.Update(gameTime);
}



What this code does is get the current state of the keyboard using Keyboard.GetState. It then sets the motion vector to be Vector2.Zero, (0, 0). It checks to see if the Down key is currently being pressed. If it is you increase the Y value of the motion vector to 1. The reason you increase is that on the window the Y coordinate increases as you travel from the top of the window to the bottom. It then checks to see if the Up key is currently being pressed. If it is it will decrease the Y value of the motion vector. You may see one of the problems with using the keyboard to control the movement of objects. If both the up and down keys are pressed at the same time they will cancel each other out. The same is true for the left and right keys. There are ways around this but that is out of the scope of this tutorial. Similarly to check for movement to the right you check for the right key and increase the X value of the vector because X increases as you travel from left to right. If the left key is being pressed you decrease the X value. After you have checked which direction the player wants to move the sprite you call the Update method of the GameObject class passing in the motion vector.

All that is left is to draw the GameObject and that will be done in the Draw method. Rendering in 2D takes place between calls to the Begin and End methods of the SpriteBatch class. I call the Begin method of the SpriteBatch class passing in SpriteBlendMode.AlphaBlend which means that I want to draw with transparency. I then call the Draw method of GameObject class passing in the SpriteBatch object and then call the End method of the SpriteBatch class. This is the code for the Draw method.

protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.CornflowerBlue);

    spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
    gameObject.Draw(spriteBatch);
    spriteBatch.End();

    base.Draw(gameTime);
}



There is just one more thing that I want to add to this tutorial. At the moment you can move the sprite off the edges of the window. To stop that I will make a quick change to the Update method of the GameObject class. This is the new code for the Update method of the GameObject class.

public void Update(Vector2 motion)
{
    if (motion != Vector2.Zero)
    {
        motion.Normalize();
        position += motion * speed;
        if (position.X < 0)
            position.X = 0;
        if (position.Y < 0)
            position.Y = 0;
        if (position.X + texture.Width > windowWidth)
            position.X = windowWidth - texture.Width;
        if (position.Y + texture.Height > windowHeight)
            position.Y = windowHeight - texture.Height;
    }
}



What I do is check to see if the X value of the position vector is less than 0. If it is less than 0 it is actually outside of window because the coordinates of the upper left corner of the window are 0. If it is less than 0 I cancel the movement by setting it to 0. I do something similar for the Y value of the position vector. If it is less than 0 I set it to 0. The right and bottom edges are just a bit more complicated. The reason is that you need to remember that the sprite is draw relative to the upper left corner of the sprite. So to check if it goes off the right edge of the window you compare the X value of its position plus its width to the width of the window. If it is you cancel the movement by setting the X value of its position to the width of the window minus the width of the object. The same is true for the Y value except instead of using width you use height. To keep it from going off the bottom edge of the window you check the Y value of the position plus the height of the object to the height of the window. If it is greater than the height of the window you cancel the movement by setting the Y value of the position to be the height of the window minus the height of the object.

Is This A Good Question/Topic? 0
  • +

Page 1 of 1