4
\$\begingroup\$

(I'm using XNA and C#) Currently, my game (a shooter) runs flawlessly with 60 FPS (which I developed around). However, if the framerate is changed, there are two major problems:

  • Gunshot sound effects are slower
  • Jumping gets messed up

Here's how I play gunshot sounds:

update(gametime)
{
    if(leftMouseButton.down)
    {
        enqueueBulletForSend();
        playGunShot();
    }
}

Now, obviously, the frequency of playGunShot depends on the framerate. I can easily fix the issue if the FPS is higher than 60 FPS by capping the shooting rate of the gun, but what if the FPS is less than 60? At first I thought to just loop and play more gunshots per frame, but I found that this can cause audio clipping or make the bullets fire in "clumps."

Now for the second issue: Here's how jumping works in my game:

if(jumpKey.Down && canJump)
{
    velocity.Y += 0.224f;
}

// ... (other code) ...
if(!onGround)
    velocity.Y += GRAVITY_ACCELERATION * elapsedSeconds;

position += velocity;

The issue here is that at < 60 FPS, the "intermediate" velocity is lost and therefore the character jumps lower. At > 60 FPS, the game adds more "intermediate" velocities, and therefore the character jumps higher.

For example, at 60 FPS, the following occurs:

  1. Velocity increased to 0.224
  2. Not on ground, so velocity decreased by X
  3. Position increased by (0.224 - X) <-- this is the "intermediate" velocity

At 30 FPS, the following occurs:

  1. Velocity increased to 0.224
  2. Not on ground, so velocity decreased by 2X
  3. Position increased by (0.224 - 2X) <-- the "intermediate" velocity was lost

All help is appreciated!

\$\endgroup\$

3 Answers 3

2
\$\begingroup\$

Regarding the jumping problem:

You need to incorporate the time since the last frame into your calculation when you apply velocity to position. Assuming you have perfected things for 60fps:

//Time values in milliseconds, 1000/60fps = 16.6667
position += velocity * ((thisFrameTime - lastFrameTime) / 16.6667);

You will need to post more information about the sound issue. How are you playing your sounds?

\$\endgroup\$
6
  • \$\begingroup\$ That only partially solved the issue -- the jump is much closer to the jump at 60 FPS now, but at 120 FPS, the player is able to jump ~2x the height that a player at 60 FPS can jump. As for sound, I'm using XNA with XACT, and so the sound is played asynchronously by the framework. I'm simply telling the framework to play a sound effect each time a bullet is fired. \$\endgroup\$
    – untitled
    Commented Nov 23, 2012 at 6:58
  • \$\begingroup\$ Introducing a Math.Max(1.0, ...) call to the velocity modifier will fix that. \$\endgroup\$
    – Jim
    Commented Nov 23, 2012 at 8:07
  • \$\begingroup\$ Instead of having the gun sound play because the mouse is pressed, I think you should rethink how the weapon is handled. Right now it appears that you can fire one bullet per frame. Instead, try looking at it as pressing the mouse starts firing and releasing it stops firing. It could start/stop a thread to keep the timing independent of your framerate. \$\endgroup\$
    – Jim
    Commented Nov 23, 2012 at 8:12
  • \$\begingroup\$ If I use Math.Max, my velocity ends up getting capped, which doesn't really solve any issues. It also makes the movement not a parabola anymore, making the transition jarring. \$\endgroup\$
    – untitled
    Commented Nov 23, 2012 at 22:05
  • \$\begingroup\$ The velocity modifier, not the whole velocity. Like this: position += velocity * Math.Max(1.0, ((thisFrameTime - lastFrameTime) / 16.6667)); \$\endgroup\$
    – Jim
    Commented Nov 23, 2012 at 23:59
1
\$\begingroup\$

I don't know about XNA specific but as I understand your scenario, you have two options for the jumping problem (that will probably appear again in other situations later, ie misdetected collisions that allow wall trespassing): 1- You must measure the delta time between frames and multiply each acceleration/velocity vectors using that delta as factor. 2- Fix your timesteps. You do this by not allow other than a fixed, previously choosen, delta. Depending on the time spend rendering each frame, you will call your logic update loop one time or more for each screen update, but each iteration is guaranteed to represent the same delta. Maybe this link can help you: http://gafferongames.com/game-physics/fix-your-timestep/ (pay attention to key concepts: "your renderer produces time, and your simulation consumes it", and "spiral of death" that is important when resuming from a pause for example. Ignore C++ specifics and concentrate in the concepts).

I like 2, because it simplifies logic planning and assumptions made for physic simulation as it lets you use constant values and the game will still adapt to different machines and produce the same results. In the worst case, the game will lag (ie: in old hardware), but still things like wall trespassing won't occur.

For the audio lag problem: you said all is ok if frame rate is 60 FPS. Well, if you use semi fixed timesteps the update logic loop will be called enough times to maintain a constant 60 updates per second, only will lag in very old hardware or when something else is sucking system resources. Also note that you can have 60 screen refreshes and 120 logic loop updates, for shooters I think is not bad solution.

If with "lag" you did mean latency (sounds delay): assuming your playSound function does not block, that's ok, but the code that look for the audio resource to play should block and run in the same thread that your game logic, game logic must stop until the sound resource is found (if you have some kind of audio resource manager for example). Otherwise you will notice that in some situations the sounds are not correctly synchronized with what happening in the rendered scene.

\$\endgroup\$
6
  • 1
    \$\begingroup\$ XNA already have fixedtimestep feature included in framework. all you need is to switch a flag. see msdn.microsoft.com/en-us/library/…. \$\endgroup\$
    – tigrou
    Commented Nov 23, 2012 at 11:51
  • \$\begingroup\$ I want to be able to support any framerate. Am I correct in understanding that you want my physics loop to update with the timestep, but have the render method draw at whatever FPS? That would defeat the purpose of running the game at a higher FPS though. \$\endgroup\$
    – untitled
    Commented Nov 23, 2012 at 22:11
  • \$\begingroup\$ You should fully read that link. With that setting Update() and Draw() will not be called the same number of times if the graphics card is slow. \$\endgroup\$
    – Jim
    Commented Nov 25, 2012 at 5:47
  • \$\begingroup\$ With the method I proposing if the graphic card is slow you get frame skipping automatically. CPU and RAM must be fast enough for kipping 60 logic updates (or whatever you decided), if the graphic card is the bottleneck then you get for example 30 draws and 60 updates, or 15 draws and 60 updates. You only get less than 60 updates if the machine cannot keep up with the simulation requirements. \$\endgroup\$ Commented Nov 25, 2012 at 13:27
  • \$\begingroup\$ @ThomasBradsworth Yes, I want to make draws independently of updates. Think about scenarios where the graphic card cannot keep up with your expectations. Why lag all the game when the rest of the hardware is enough to update your logic 60 or even 120 times per second? If you implement it correctly, where the graphic card is fast enough you get 1:1 draws/updates, where not the system falls back automatically to 1:2 or 1:4. \$\endgroup\$ Commented Nov 25, 2012 at 13:36
1
\$\begingroup\$

Have you tried decoupling the render loop from the update loop? I don't have any experience with XNA/C# though, so I'm not sure how complicated that might be for you.

\$\endgroup\$
1
  • \$\begingroup\$ Clarifications should be added to the question as a comment. \$\endgroup\$ Commented Nov 24, 2012 at 1:41

You must log in to answer this question.

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