3
\$\begingroup\$

I have a top-down game that has 8 directions of movement (top/down/left/right/topright/topleft/bottomright/bottomleft). There are sprites for each direction of movement. This is just an example spritesheet:

Example sprite sheet

The problem is when I want to retain the vertical position. When I let go of the buttons on the keyboard, it polls the keys quickly and ends up facing the character in either left/right/up/down position depending on the last key that was released.

I added a delay for polling the keypresses, but I do not like this solution as it creates a noticeable lag.

Can anyone think of logic that would be able to allow my sprite to retain the proper direction when the keys are unpressed? Here is the current code:

if (kbState.IsKeyDown(Keys.Up) && kbState.IsKeyDown(Keys.Left))
    character.ChangeDirection(Direction.UpLeft);
else if (kbState.IsKeyDown(Keys.Up) && kbState.IsKeyDown(Keys.Right))
    character.ChangeDirection(Direction.UpRight);
else if (kbState.IsKeyDown(Keys.Down) && kbState.IsKeyDown(Keys.Left))
    character.ChangeDirection(Direction.DownLeft);
else if (kbState.IsKeyDown(Keys.Down) && kbState.IsKeyDown(Keys.Right))
    character.ChangeDirection(Direction.DownRight);
else  if (kbState.IsKeyDown(Keys.Up))
    character.ChangeDirection(Direction.Up);
else if (kbState.IsKeyDown(Keys.Down))
    character.ChangeDirection(Direction.Down);
else if (kbState.IsKeyDown(Keys.Left))
    character.ChangeDirection(Direction.Left);
else if (kbState.IsKeyDown(Keys.Right))
    character.ChangeDirection(Direction.Right);

Edit

Settled for the following code:

float delay = 0.04f;
float DELAY = 0.04f;
....

//Get movement 
delay -= (float)gameTime.ElapsedGameTime.TotalSeconds;
if (delay < 0)
{
      int vertical = kbState.IsKeyDown(Keys.Up) ? -1 : kbState.IsKeyDown(Keys.Down) ? 1 : 0;
      int horizontal = kbState.IsKeyDown(Keys.Left) ? -1 : kbState.IsKeyDown(Keys.Right) ? 1 : 0;
      movement = new Vector2(horizontal, vertical);
      delay = DELAY;
}

character.Update(gameTime, movement);

Seems I have to use a delay in order to get this to work. Thanks for the feedback guys.

\$\endgroup\$
4
  • \$\begingroup\$ Ok so your problem is that when you want to move your character in a diagonal direction you need to press 2 buttons i.e the up and left button to go up and left diagonally? However, when you stop pressing the buttons because you aren't a perfectly timed computer you let go of one button first causing your sprite to take on the direction of the direction you last took your finger off? Is this your problem? \$\endgroup\$
    – Savlon
    Feb 9, 2013 at 21:31
  • \$\begingroup\$ Yes, exactly. Sorry if I was not clear. \$\endgroup\$
    – jgallant
    Feb 9, 2013 at 21:38
  • \$\begingroup\$ It seems like a lot of effort for something that the player probably won't even notice. \$\endgroup\$ Feb 10, 2013 at 4:06
  • \$\begingroup\$ The player will notice. Consider if you want to stop in a diagonal position, and fire off projectiles. \$\endgroup\$
    – jgallant
    Feb 10, 2013 at 12:38

3 Answers 3

2
\$\begingroup\$

This is something I just worked on because I had similar issue in the past and the question made me curious to revisit it.

One thing to start with is to create your own utility class for handling Input that you can use on all future projects as the default methods with XNA are clumsy. You want methods as @Hackworth mentioned to detect when a key has been released/pressed/down that is easier to manage.

Within my Update Method for the character:

 var velocity = Vector2.Zero;

    // Notice that velocity persists through these keypresses and
    // keys are polled individually
 if (InputHandler.KeyDown(Keys.Up) && !InputHandler.KeyDown(Keys.Down))
     velocity = new Vector2(velocity.X, -1);
 if (InputHandler.KeyDown(Keys.Down) && !InputHandler.KeyDown(Keys.Up))
     velocity = new Vector2(velocity.X, 1);
 if (InputHandler.KeyDown(Keys.Right) && !InputHandler.KeyDown(Keys.Left))
     velocity = new Vector2(1, velocity.Y);
 if (InputHandler.KeyDown(Keys.Left) && !InputHandler.KeyDown(Keys.Right))
     velocity = new Vector2(-1, velocity.Y);

    // now that each part of the Vector2 is set to 0/1/-1 we can set an enum
    // Representing this Vector2's direction.
    SetDirection(velocity);

    // Now, within that tiny amount of time we might have released a key
    // which causes our character to annoy us with his orientation.
    // So, let's override with a more desired orientation.

    // Up_Right
    if (InputHandler.KeyDown(Keys.Up) && InputHandler.KeyReleased(Keys.Right))
        DrawDirection = Directions.UP_RIGHT;
    if (InputHandler.KeyDown(Keys.Right) && InputHandler.KeyReleased(Keys.Up))
        DrawDirection = Directions.UP_RIGHT;
    // Up_Left
    if (InputHandler.KeyDown(Keys.Up) && InputHandler.KeyReleased(Keys.Left))
        DrawDirection = Directions.UP_LEFT;
    if (InputHandler.KeyDown(Keys.Left) && InputHandler.KeyReleased(Keys.Up))
        DrawDirection = Directions.UP_LEFT;
    // Down_Right
    if (InputHandler.KeyDown(Keys.Down) && InputHandler.KeyReleased(Keys.Right))
        DrawDirection = Directions.DOWN_RIGHT;
    if (InputHandler.KeyDown(Keys.Right) && InputHandler.KeyReleased(Keys.Down))
        DrawDirection = Directions.DOWN_RIGHT;
    // Down_Left
    if (InputHandler.KeyDown(Keys.Down) && InputHandler.KeyReleased(Keys.Left))
        DrawDirection = Directions.DOWN_LEFT;
    if (InputHandler.KeyDown(Keys.Left) && InputHandler.KeyReleased(Keys.Down))
        DrawDirection = Directions.DOWN_LEFT;

    private void SetDirection(Vector2 direction)
    {
             if (direction.X == 0 && direction.Y == -1)
                DrawDirection = Directions.UP;
            else if (direction.X == 0 && direction.Y == 1)
                DrawDirection = Directions.DOWN;
            else if (direction.X == -1 && direction.Y == 0)
                DrawDirection = Directions.LEFT;
            else if (direction.X == 1 && direction.Y == 0)
                DrawDirection = Directions.RIGHT;
            else if (direction.X == -1 && direction.Y == -1)
                DrawDirection = Directions.UP_LEFT;
            else if (direction.X == -1 && direction.Y == 1)
                DrawDirection = Directions.DOWN_LEFT;
            else if (direction.X == 1 && direction.Y == -1)
                DrawDirection = Directions.UP_RIGHT;
            else if (direction.X == 1 && direction.Y == 1)
                DrawDirection = Directions.DOWN_RIGHT;
    }
\$\endgroup\$
2
  • \$\begingroup\$ This at first seemed to be a valid solution, but again, caused problems in the case where you are holding down two buttons (Up and Right) and then releasing one of the buttons. You need to stop pressing all buttons in order to stop going in the diagonal direction. I managed to get it to work using this solution, and adding a counter that counts up how many times it detects if only a single key is pressed, and then adjusting the direction if the threshold is met. \$\endgroup\$
    – jgallant
    Feb 10, 2013 at 15:46
  • \$\begingroup\$ Yes, I didn't think the player would willing press those keys together on purpose since that seems kinda silly. I updated the code to reflect this without a counter. \$\endgroup\$
    – Dialock
    Feb 10, 2013 at 16:27
2
\$\begingroup\$

What you need is to store the previous frame's keyboard state. Then determine which button has just been pressed compared to the previous state, and only if a button goes from released to pressed can it change the direction of your character. That way, you don't need to fiddle with artificial delays or anything.

Basically you want to implement the equivalent of the KeyDown event that you may know from a Forms or WPF UI application.

You could use this method:

    private bool keyWentDown(Keys key)
    {
        return kbState.IsKeyDown(key) && prevKbState.IsKeyUp(key);
    }

Basically you can re-use your code as it is. However, you don't execute it every frame, but only when your character direction actually needs updating, i.e. when any of the direction keys has been pressed.

So the complete code could be:

        if (keyWentDown(Keys.Up) 
            || keyWentDown(Keys.Down) 
            || keyWentDown(Keys.Left) 
            || keyWentDown(Keys.Right))
        {
            //your code
        }
\$\endgroup\$
3
  • \$\begingroup\$ I tried this and it works for retaining the vertical direction when the keys are unpressed. However, it creates a new problem. When holding down Up/Right, then releasing only one of the buttons, it does not change directions. Tried writing some conditions to change the direction, and it comes back to the original problem. \$\endgroup\$
    – jgallant
    Feb 9, 2013 at 22:22
  • 2
    \$\begingroup\$ @Jon Then you have 2 conflicting design goals: Making your input responsive (no artificial lag) but also relaxed enough so the diagonal direction can be retained. In that case, I don't see another way other than having an artificial lag between accepting inputs, e.g. run your direction update code only every 2nd, or 3rd, or 4th, etc. frame. You could also implement the keyWentUp method, and delay the update for n frames after such either event fires for either direction key, rather than statically every n frames. You will have to find the best balance by experimentation. \$\endgroup\$
    – Hackworth
    Feb 9, 2013 at 22:31
  • \$\begingroup\$ I'll accept this answer by tomorrow if no one provides a valid solution. Thanks for the feedback my good man. \$\endgroup\$
    – jgallant
    Feb 9, 2013 at 23:08
0
\$\begingroup\$

Compare the last player position (differing from the current position) with the current position and don't take the arrow keys in account at all. I.e. only store the last position when actually a movement happens.

Assuming (y-axis pointing towards bottom):

if (x < lastX) {
    if (y < lastY) {
        // top left
    } else if (y == lastY) {
        // left
    } else { // y > lastY
        // bottom left
    }
} else if (x == lastX) {
    if (y < lastY) {
        // top
    } else if (y == lastY) {
        // Not allowed
    } else { // y > lastY
        // bottom
    }
} else { // x > lastX
    if (y < lastY) {
        // top right
    } else if (y == lastY) {
        // right
    } else { // y > lastY
        // bottom right
    }
}
\$\endgroup\$
1
  • \$\begingroup\$ Same problem occurs. Since the position is updated based on which button was unpressed last, this solution does not work. Interesting take on the problem though. \$\endgroup\$
    – jgallant
    Feb 9, 2013 at 22:45

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .