6
votes

Update: I have uploaded a video showing the stutter here: http://intninety.co.uk/xnastutter.mp4 you may have to look closely in the video if you are not viewing it at 1920x1080, but you'll see that there is a rather distinct stutter when moving every 2 seconds or so, I'd recommend viewing it in Windows Media Player rather than your web browser to ensure the video itself isn't choppy and thus preventing you seeing the actual stutter

I'm recently picking up a project I started a while ago, however I am still struggling to solve the problem I left it at!

At the moment I have a very simple application which just has a single sprite on screen and is moved around using the directional keys. The problem is every two seconds or so, the game stutters and the sprite appears to jump backwards and then back forwards very quickly.

The sprite itself is a 55x33 bitmap, so isn't anything large, and the code in use is as follows. Hopefully this is enough to get the ball rolling on some ideas as to what may be the problem, if a video is required to see exactly how the stuttering looks I can put one together and upload it somewhere if need be.

As you'll see in the code it does compensate for time lost between frames by making the movement greater should it happen, however that drop is happening very consistently time wise, which is leading me to believe I'm doing something wrong somewhere.

I've tried on a few different machines but the problem persists across all of them, if anyone has any ideas or can see where it is I'm messing up it'd be greatly appreciated if you could point it out.

Thanks :)

Constructor of the Game Setting up the Graphics Device Manager

graphics = new GraphicsDeviceManager(this);
graphics.IsFullScreen = true;
graphics.SynchronizeWithVerticalRetrace = false;
graphics.PreferredBackBufferWidth = 1920;
graphics.PreferredBackBufferHeight = 1080;
Content.RootDirectory = "Content";
this.IsFixedTimeStep = false;

Code from the Game's Update Method

KeyboardState keyboard = Keyboard.GetState();
GamePadState gamePad = GamePad.GetState(PlayerIndex.One);

if (keyboard.IsKeyDown(Keys.Escape)) {
    this.Exit();
}

if ((keyboard.IsKeyDown(Keys.Left)) || (gamePad.DPad.Left == ButtonState.Pressed))
{
    this.player.MoveLeft((float)gameTime.ElapsedGameTime.TotalMilliseconds); 
} else if ((keyboard.IsKeyDown(Keys.Right)) || (gamePad.DPad.Right == ButtonState.Pressed))
{
    this.player.MoveRight((float)gameTime.ElapsedGameTime.TotalMilliseconds);
}

if ((keyboard.IsKeyDown(Keys.Up)) || (gamePad.DPad.Up == ButtonState.Pressed))
{
    this.player.MoveUp((float)gameTime.ElapsedGameTime.TotalMilliseconds);
} else if ((keyboard.IsKeyDown(Keys.Down)) || (gamePad.DPad.Down == ButtonState.Pressed))
{
    this.player.MoveDown((float)gameTime.ElapsedGameTime.TotalMilliseconds);
}

base.Update(gameTime);

The "Move" Methods seen in the above Update Method

public void MoveLeft(float moveBy)
{
    this.position.X -= (moveBy * this.velocity.X);
}
public void MoveRight(float moveBy)
{
    this.position.X += (moveBy * this.velocity.X);
}

public void MoveUp(float moveBy)
{
    this.position.Y -= (moveBy * this.velocity.Y);
}

public void MoveDown(float moveBy)
{
    this.position.Y += (moveBy * this.velocity.Y);
}

The Game's Draw Method

GraphicsDevice.Clear(Color.CornflowerBlue);

spriteBatch.Begin();

spriteBatch.Draw(this.player.Texture, this.player.Position, null, Color.White, this.player.Rotation, this.player.Origin, 1.0f, SpriteEffects.None, 0.0f);

spriteBatch.End();

base.Draw(gameTime);

Edit: forgot to mention, the velocity object used in the Move methods is a Vector2

2
Any reason why you are coverting to a float and then taking a float parameter, rather than just taking the int. Assuming velocity is a PointF, then Position (PointF too?) calc will still be float even if moveBy is int (it will be implicitly cast on the fly). The jump is likely to be a bad value creeping in, maybe related to floating point arithmatic? Try putting out the positions and moveby values on each call and and hopefully this will show you where the bad digit is creeping into MoveLeft/Right.Wolf5370
Does this 'jump' look like flashing a second-old frame? Or is it always showing the sprite displaced in a certain direction? It almost sounds like an anomaly in GameTime, though I've never heard of that happening.Kendall Frey
@Wolf5370 - the cast to float is because TotalMilliseconds is a double (milliseconds plus fractional milliseconds) and not an int (ie: just Milliseconds). I've been able to reproduce this even setting (and confirming) a target fixed time step of 1ms (was running ~0.3-0.4ms without fixed time step). So it does not look like a bug with gameTime.J...
graphics.SynchronizeWithVerticalRetrace = true; also does not seem to change the behaviour.J...
I've played with this to death and can't figure it out. What's really odd is that I have another program (one of mine) which, although working with a more developed physics engine, can essentially be reduced to a very similar sequence of calls, and which I cannot cause to exhibit this weird glitch - as much as I cannot cause this piece of code to not exhibit it. I draw to destination Rectangles but that didn't fix this. I use Vector2. math rather than direct operations on floats but that didn't fix it. Changed most everything I could think of...J...

2 Answers

1
votes

I've managed to see it occur once for a split second which has led me to what I think is the problem. Since you are using the raw ElapsedGameTime.TotalMilliseconds value as a factor for your movement, all computer lag that your program experiences will be directly applied to the motion. For example, if your computer (OS) does something else for one twentieth of a second, then the elapsed time value will accumulate to a value of ~50 milliseconds, when it is normally about 0.3 milliseconds. This would cause a frame that has 150 times more motion than a normal frame.

To cause this to happen manually, you can do the following:

// define a frame counter
private int mCounter;

...

protected override void Update(GameTime pGameTime)
{
   // save the elapsed time value
   float time = (float)pGameTime.ElapsedGameTime.TotalMilliseconds;

   ...

   // force a long frame every 2500th frame (change depending on your framerate)
   if (mCounter++ > 2500)
   {
      mCounter = 0;
      time = 75; // about 225 times longer frame
   }

   ...

   // use the time value in your move calls
   if ((keyboard.IsKeyDown(Keys.Left)) || (gamePad.DPad.Left == ButtonState.Pressed))
      mPlayer.MoveLeft(time);

To prevent this from happening, (aside from setting IsFixedTimeStep = true;, which would fix it immediately; but assuming you want IsFixedTimeStep to be false), you should use a time value, as above, but cap it. It's up to you to determine the proportion of elapsed time to motion and to determine what is a good amount of time to allow to pass per frame. Ex:

protected override void Update(GameTime pGameTime)
{
   // save the elapsed time value
   float time = (float)pGameTime.ElapsedGameTime.TotalMilliseconds;
   if (time > 1)
      time = 1;

   ...

   if ((keyboard.IsKeyDown(Keys.Left)) || (gamePad.DPad.Left == ButtonState.Pressed))
      mPlayer.MoveLeft(time);

   ...

While that will correct the issue for your current program, your frames are only at 0.3ms each since there is not a lot happening. Once there is more to your game, more time will be elapsing per frame, and you will want the cap to be much higher than 1ms.

EDIT: To be clear, this is 'downtime' from your CPU/OS. It's not going to go away*, it's just up to you whether to jump ahead a bunch when it happens (which can cause problems if the elapsed time ever makes it to 2000ms, for example), or to cap these spikes and let them cause lag; either way there is going to be a 'hole' in your motion that you can't fill. This really is normal, and it's not as critical as it seems. Once there is more happening in your game, it will become less and less noticeable. It stands out strongly at the moment particularly because there are only two graphics present and nothing else happening.

*(Actually, you might look for other applications and processes that you can close to keep the CPU from being borrowed by some other program, but since you are using a multi-tasking OS, you are never going to be guaranteed to have the CPU to yourself.)

0
votes

Update Again

Just had a thought. Have you ever checked that (float)gameTime.ElapsedGameTime.TotalMilliseconds does not result in Infinity or a negative number? The elapsed time becoming negative would explain your sprite jumping back then forward.

Its the keyboard. You can prove this by changing moving left and right to left mouse and right mouse click. Use the keydown and keyup triggers to set a state instead of iskeydown.

Update:

It appears there are a few things that cause "stuttering" in XNA. I thought for sure your issue was the keyboard problem one. However, I can only assume you've tested your code with the GamePad also and it suffers the same.

There is another problem with IsFixedTimeStep but you already have that set to false. A quick google search suggests there are more than a few people with these types of problems without any clear fix.

There is a long discussion at http://forums.create.msdn.com/forums/t/9934.aspx?PageIndex=6

Other things to look at:

Try restricting your frame rate to 60 fps. There isn't any reason to go above that anyways. One power suggests that a 2d app that does nothing could stall the graphics pipeline with a significantly high throughput.

Check to see if IsSlowRunning is ever called. Garbage collection and Chaos Theory could cause this to be set. I