Monogame smooth Tile to tile movement

So basically what I want to achieve is a ‘smooth’ movement from tile to tile.

My map has 32x32 px tiles and my player is also 32x32. I’d like for the movement not to be instant, and also blocks other simultaneous keypresses, like in the old gameboy Pokemon games you know…

I cannot figure out how to limit the speed so it waits until the player has reached the destination tile

Thats what i already have so far:

Player class:

   public Player()
   {
      state = State.Idle;
      destination = Sprite.Hitbox;
   }

   ...

   public void UpdatePosition(GameTime gameTime, Input input)
   {
    switch (state)
    {
        case State.Idle:
            if (input.IsKeyDown(Keys.Up) || input.IsKeyDown(Keys.Z))
            {
                Sprite.Direction = Direction.UP;
                Sprite.AnimationTimer += (int)gameTime.ElapsedGameTime.TotalMilliseconds;
                if (Sprite.Hitbox.Y > 0)
                {
                    destination.Y -= 32;
                    state = State.MovingUp;
                }
            }
            else if (input.IsKeyDown(Keys.Down) || input.IsKeyDown(Keys.S))
            {
                Sprite.Direction = Direction.DOWN;
                Sprite.AnimationTimer += (int)gameTime.ElapsedGameTime.TotalMilliseconds;
                if (Sprite.Hitbox.Y < (Globals.SCREEN_HEIGHT - Sprite.Hitbox.Height))
                {
                    Sprite.AnimationTimer += (int)gameTime.ElapsedGameTime.TotalMilliseconds;
                    destination.Y += 32;
                    state = State.MovingDown;                            
                }
            }
            else if (input.IsKeyDown(Keys.Left) || input.IsKeyDown(Keys.Q))
            {
                Sprite.Direction = Direction.LEFT;
                Sprite.AnimationTimer += (int)gameTime.ElapsedGameTime.TotalMilliseconds;
                if (Sprite.Hitbox.X > 0)
                {
                    destination.X -= 32;
                    state = State.MovingLeft;                        
                }
            }
            else if (input.IsKeyDown(Keys.Right) || input.IsKeyDown(Keys.D))
            {
                Sprite.Direction = Direction.RIGHT;
                Sprite.AnimationTimer += (int)gameTime.ElapsedGameTime.TotalMilliseconds;
                if (Sprite.Hitbox.X < (Globals.SCREEN_WIDTH - Sprite.Hitbox.Width))
                {
                    destination.X += 32;
                    state = State.MovingRight;                         
                }
            }
            break;

        case State.MovingUp:
            if (Sprite.Hitbox.Y - (Globals.Speed * gameTime.ElapsedGameTime.TotalMilliseconds) < destination.Y)
            {      
                Sprite._hitbox.Y = destination.Y;
                state = State.Idle;
            }
            else
                Sprite._hitbox.Y -= (int)(Globals.Speed * gameTime.ElapsedGameTime.TotalMilliseconds);
            break;

        case State.MovingDown:
            if (Sprite.Hitbox.Y + (Globals.Speed * gameTime.ElapsedGameTime.TotalMilliseconds) > destination.Y)
            {
                Sprite._hitbox.Y = destination.Y;
                state = State.Idle;
            }
            else
                Sprite._hitbox.Y += (int)(Globals.Speed * gameTime.ElapsedGameTime.TotalMilliseconds);
            break;

        case State.MovingLeft:
            if (Sprite.Hitbox.X - (int)(Globals.Speed * gameTime.ElapsedGameTime.TotalMilliseconds) < destination.X)
            {
                Sprite._hitbox.X = destination.X;
                state = State.Idle;
            }
            else
                Sprite._hitbox.X -= (int)(Globals.Speed * gameTime.ElapsedGameTime.TotalMilliseconds);
            break;

        case State.MovingRight:
            if (Sprite.Hitbox.X + (int)(Globals.Speed * gameTime.ElapsedGameTime.TotalMilliseconds) > destination.X)
            {
                Sprite._hitbox.X = destination.X;
                state = State.Idle;
            }
            else
                Sprite._hitbox.X += (int)(Globals.Speed * gameTime.ElapsedGameTime.TotalMilliseconds);
            break;
    }
}

   public void Update(GameTime gameTime, Input input)
   {
      UpdatePosition(gameTime, input);
      base.Update(gameTime);
   }

It looks ok to me at first glance, can you tell us what is actually happening, the states Moving* all seem to move the sprite depending on the globals.speed value every frame.

I take it your also drawing out every frame using the sprite.hitbox location.

The Input class - is that buffering the keypresses? are you clearing the keypress if state is NOT idle?

Well the sprite sort of “jumps” very quickly to the next tile.
My input class is very simple, just to gather the keydown and up events and also keypressed:

public class Input
{
    private KeyboardState _oldState, _currentState;

    public Input(KeyboardState oldState, KeyboardState currentState)
    {
        _oldState = oldState;
        _currentState = currentState;
    }

    public bool IsKeyDown(Keys key)
    {
        return _currentState.IsKeyDown(key);
    }

    public bool IsKeyUp(Keys key)
    {
        return _currentState.IsKeyUp(key);
    }

    public bool IsKeyPressed(Keys key)
    {
        return _oldState.IsKeyUp(key) && _currentState.IsKeyDown(key);
    }
}

OK so i came up with a good behaviour by changing my speed from 4 to 0.15
seems weird…

I was just about to suggest changing the speed - remember that each draw is 60 frames a second - so if you want it to move 32 pixels and you want it to take 5 seconds to do that, then you need to set the speed so it moves 6.4 pixels a second - so that would then evaluate to 0.10 speed, to move 32 pixels over 5 seconds.

32 / 5 = 6.4
6.4 / 60 = 0.106666

Hope that helps.

I don’t like having number like that for speed…
can i do this:
Globals.Speed * (gameTime.ElapsedGameTime.TotalMilliseconds/60)

so i can use a normal speed like 4 or 8?

I don’t see why not - as long as you know what it all means. but yes, looks like you need to calculate the end result.

Put a “const speedAdjustment = 60” at the top of the method, then use that in the calculations.

You could also use gameTime.ElapsedGametime.TotalSeconds. This way you can use higher speed values without the need of a speedAdjustment

You are using integers for your movement positions. You are basically causing yourself rounding errors. what happens if you run at 60fps and you want to move 1 pixels in that time you must move …016 pixels. if you move 60 pixels per second and you turn off is fixed time step and start running at 500+ frames a second what happens ?

e.g.
Sprite._hitbox.X -= (int) ( .016)
this evaluates to…
Sprite._hitbox.X -= 0;

Define a additional Vector2 hitbox variable at the class level to accumulate the floating point values then cast that to your original hitbox.

Vector2 floatingHitbox = Vector2.Zero;

All the code such as this has to be altered like so…

case State.MovingUp:
            if (Sprite.Hitbox.Y < destination.Y)
            {     
                // actual floating point position
                Sprite._floating_hitbox.Y = (float)(destination.Y);
                // drawable position 
                Sprite._hitbox.Y = destination.Y;
                state = State.Idle;
            }
            else
                // actual floating point position
                Sprite._floating_hitbox.Y -= (float)(Globals.Speed * gameTime.ElapsedGameTime.TotalMilliseconds);
                // drawable position
                Sprite._hitbox.Y = (int)(Sprite._floating_hitbox.Y);
            break;

I typically name my positions as world or drawable so one is for simple output in 2d.
So a player class will typically hold two variables like something like so.

        class Player
        {
            Vector2 worldPosition = Vector2.Zero;
            Vector2 worldSize = Vector2.Zero;
            Rectangle drawablePosition = new Rectangle();
            // if i were you here i might actually just make a property like so
            public Vector2 Position
            {
                get { return worldPosition; }
                set
                {
                    worldPosition = value;
                    drawablePosition.X = (int)(worldPosition.X);
                    drawablePosition.Y = (int)(worldPosition.Y);
                }
            }
        }