|
So how long is the lag? It depends on how long a frame is (where a "frame" here is a complete iteration of the main loop). It takes up to three frames for the user's input to be translated into visual feedback.
So if we are running at 30fps, then the lag is 3/30th or one-tenth of a second. If we are running at 60fps, then the lag will be 3/60th or 1/20th of a second.
This calculation illustrates a common misconception about the difference between 60fps and 30fps games. Since the difference between these two frame rates is just 1/60th of a second, people assume that the difference in responsiveness will also be 1/60th.
But in fact, going from 60 to 30 does not just add a vsync to your lag, it acts as a multiplier, doubling the length of the process pipeline that's responsible for lag. In our ideal example in Figure 1, it adds 3/60ths of a second, not 1/60th. If the event pipeline is longer, which it quite possibly can be, it can add even more.
Figure 1 actually illustrates the best possible sequence of events. The button press event is translated into visual feedback via the shortest path possible, which we can clearly see in the sequence of events.
As a programmer, being familiar with the order in which things happen is a vital part of understanding why things act the way they do in the game. It's quite easy to introduce additional frames of lag (meaning an extra 1/60th or 1/30th of a second delay), by not paying careful attention to the order in which events occur.
As a simple example, consider what would happen if we switched the order of the Logic() and Rendering() calls in our main loop. Look at Frame 2 of Figure 1: here the GPU logic (rendering) happens after CPU logic, so input at the start of Frame 2 will affect the CPU logic and hence the GPU logic in the same frame.
However if GPU logic is performed first, then the input will not have an effect on GPU logic until the next frame, hence introducing an extra frame of lag. While this is a novice mistake, programmers need to make absolutely sure it's not happening.
Extra frames of lag can be introduced in a subtler manner as a result of the order of operations within the game logic. In our example, we are firing a gun.
Now perhaps our engine is set up to increment the position of the objects in the world using a physics engine, and handle events that are raised due to this update (such as collision events). In this situation, the sequence of input or logic looks like Listing 2.
Listing 2: Physics Update Is Followed By Event Handling
void Logic() {
HandleInput();
UpdatePhysics();
HandleEvents();
}
Event handling via messages is a very nice way of decoupling systems and a programmer might decide to use it for the player control events. To fire a gun, the HandleInput() function will fire an event telling the gun to fire.
The HandleEvents() function will take this event and cause the gun to actually fire. But because the physics update has already happened for this frame, the effect on the world state will not be incorporated until the next frame, hence introducing an extra frame of lag.
More Lag Causes
Lower level action ordering can draw out the lag even more. Consider a jump, for example. The feedback is the character actually moving. To make something move in a game, you can either set the velocity directly or apply a force to it, such as acceleration or, more likely, a momentary impulse.
There's a problem in this scenario if your physics engine updates positions before the velocity change is applied, a common condition in many introductory game programming tutorials.
Although the velocity of the jumping object is updated in the same frame as the one where the input event is handled, the object will not actually begin to move until the next time around the loop-on the next game frame-and so it introduces an additional frame of lag.
Remember, these are cumulative problems that can be difficult to discern in isolation, but the combined effect can make your game controls turn to mush.
Suppose you had made all three mistakes listed above: you do rendering before logic, you handle logic events after advancing the physics state, and you update position before velocity. That's three additional full iteration of the main loop, in addition to the three you already have built in-six frames of lag between the player pressing a button and seeing the result on screen.
At 60 frames per second that's 1/10th of a second, which is bad enough, but if the game is running at 30fps, the lag is doubled to an unbearable 1/5th of a second, or 200 milliseconds.
|
Say you want to fire an event upon the player touching the volume of a trigger, if the updates are too slow, the player will be well within the even trigger before its fired. This could lead to level designers feeling that there is something "untimed" about their design.
Now on the flip side, if this issue is not fixed early on in the production, then it will mean that the events in the levels, especially for a highly scripted game would have most likely been tuned with this bug; SO changing it or fixing it would break the pacing of your levels...
Another time to look for this problem is when engineers are in their "optimization" phase, one of the first things that gets tweaked is the collision system....and in a cube somewhere a level designer is pulling his hair out!
Articles like this (and thinking a lot about responsiveness) is, as stated, hugely important - player feedback is one of my hang-ups and can remove all feeling from a game if the delay is significant (a few fractions of a second even).
Thanks.
This is one area where old systems have an advantage. I guess the days of 1-frame reaction times, like with the good old NES, are over.
Another place I've found input lag is in the network layer. Where possible you want to simulate player actions on the client, so when you turn left you don't have to wait for your input to travel up to the server, be applied to the game state, travel back to the client, then wait for render. Strangely enough I even found this once in a single player game. Input events were being batched in a network queue, pumped to localhost, received, and then interpreted all on the same machine!
I done my best to squeeze the maximum performance of my mouse-reading and drawing func, and still it lagged several frames behind, when I turned on triple buffering, the lag was reduced, but if I turned on the system cursor I could still see that lag is still present...
fortunally I gave up :P since my code was already at the state of the mininum lag (ie: the optimum code that he described in the start of the article)
Maybe you guys don't even know what I am talking about, but hopefully some of you do. I think this is one of the biggest causes for me of that feeling of unresponsiveness.
http://cowboyprogramming.com/2008/04/23/running-in-circles/
However, having a concept of the "present" is important for handling lag that is unavoidable. In GH (and in most games), the game needs to be aware that the player is not seeing what it thinks what the current state is, but rather is seeing a few frames back. Games should allow the player a degree of slop in their synchronization to account for this. I touched on this in my "Pushing Buttons" article:
http://cowboyprogramming.com/2007/01/02/pushhing-buttons/
I've discussed the issue further here :
http://www.codermind.com/answers/How-do-I-reduce-mouse-lag-in-my-3D-application.
html
The irreducible part is : the latency of the mouse driver, the refresh rate (if you had really good control about refresh rate then it's possible to do better but that's usually something from the past like Amiga programming, not PC), and the LCD latency (LCD add latency too. They could add on average a whole frame of delay compared to an equivalent CRT and I'm not talking about remanence : see http://www.behardware.com/articles/632-1/lcds-images-delayed-compared-to-crts-ye
s.html ).