Intent
- Decouple the progression of game time from user input and processor speed.
- batch mode programs — once the work was done, the program stopped.
Motivation
Interview with a CPU
Event loops
A world out of time
- The common terms for one crank of the game loop are “tick” and “frame”.
- this loop isn’t blocking on input.how fast does it spin?
- we measure how quickly the game loop cycles in terms of real time, we get the game’s “frames per second”
-
two factors determine the frame rate
- how much work it has to do each frame. Complex physics, a bunch of game objects, and lots of graphic detail
- the speed of the underlying platform
Seconds per second
- In early video games, that second factor was fixed.
- Older games were carefully coded to do just enough work each frame so that the game ran at the speed the developers wanted. But if you tried to play that same game on a faster or slower machine, then the game itself would speed up or slow down
- this is why old PCs used to have “turbo” buttons. New PCs were faster and couldn’t play old games because the games would run too fast. Turning the turbo button off would slow the machine down and make old games playable.
- our games must intelligently adapt to a variety of devices
- it runs the game at a consistent speed despite differences in the underlying hardware.
The Pattern
- A game loop runs continuously during gameplay
- Each turn of the loop, it processes user input without blocking, updates the game state, and renders the game
- It tracks the passage of time to control the rate of gameplay
When to Use It
- If you’re using a game engine, you won’t write it yourself, but it’s still there
- With libraries, you own the main game loop and call into the library. An engine owns the loop and calls into your code.
- Animation and music keep running even when the game is “waiting” for you to take your turn.
- Take care with this code, and be mindful of its efficiency
Keep in Mind
- a program spends 90% of its time in 10% of the code
- Your game loop will be firmly in that 10%
You may need to coordinate with the platform’s event loop
- If you’re building your game on top of an OS or platform that has a graphic UI and an event loop built in, then you have two application loops in play. They’ll need to play nice together.
- if you’re writing a game against the venerable Windows API, your main() can just have a game loop. Inside, you can call PeekMessage() to handle and dispatch events from the OS. Unlike GetMessage(), PeekMessage() doesn’t block waiting for user input, so your game loop will keep cranking.
- If you’re targeting a web browser, the event loop is deeply built into browser’s execution model. You’ll call something like requestAnimationFrame() and it will call back into your code to keep the game running.
Sample Code
- the code for a game loop is actually pretty straightforward.
- The game loop drives AI, rendering, and other game systems, but those aren’t the point of the pattern itself
Run, run as fast as you can
Take a little nap
One small step, one giant step
Play catch up
- One part of the engine that usually isn’t affected by a variable time step is rendering. Since the rendering engine captures an instant in time, it doesn’t care how much time advanced since the last one.
- This is more or less true. Things like motion blur can be affected by time step, but if they’re a bit off, the player doesn’t usually notice.
-
We’ll update the game using a fixed time step because that makes everything simpler and more stable for physics and AI.
- But we’ll allow flexibility in when we render in order to free up some processor time.
-
A certain amount of real time has elapsed since the last turn of the game loop.
- This is how much game time we need to simulate for the game’s “now” to catch up with the player’s.
- We do that using a series of fixed time steps.
double previous = getCurrentTime();
double lag = 0.0;
while (true)
{
double current = getCurrentTime();
double elapsed = current - previous;
previous = current;
lag += elapsed;
processInput();
while (lag >= MS_PER_UPDATE)
{
update();
lag -= MS_PER_UPDATE;
}
render();
}
- At the beginning of each frame, we update lag based on how much real time passed.
- This measures how far the game’s clock is behind compared to the real world.
- We then have an inner loop to update the game, one fixed step at a time, until it’s caught up.

-
MS_PER_UPDATE is just the granularity we use to update the game.
- The shorter this step is, the more processing time it takes to catch up to real time.
- The longer it is, the choppier the gameplay is.
- But be careful not to make it too short. You need to make sure the time step is greater than the time it takes to process an update(), even on the slowest hardware. Otherwise, your game simply can’t catch up
-
we’ve yanked rendering out of the update loop.
- That frees up a bunch of CPU time
- The end result is the game simulates at a constant rate using safe fixed time steps across a range of hardware.
-
It’s just that the player’s visible window into the game gets choppier on a slower machine.
- you can safeguard this by having the inner update loop bail after a maximum number of iterations. The game will slow down then, but that’s better than locking up completely.
-
備注
- 渲染通常不受變長時間步長的影響,因為渲染并不關心自從上一個時間到現在驅動了多長時間。所以我們可以使用固定的時間步長來更新游戲,因為這樣使得物理和AI更簡單穩定。而我們渲染時,可以更靈活用來釋放一些處理器時間(從后面章節的更新和渲染的時間軸來看,渲染的頻率比更新的頻率要低,則間接釋放了一些處理器時間;It updates with a fixed time step, but it can drop rendering frames(可丟棄渲染幀) if it needs to to catch up to the player's clock.)
- 在每幀開始,我們根據真實時間過去了多少來更新lag,它描述了游戲的時鐘與現實世界落后多少。然后在內循環,更新游戲,固定步長,直到趕上
- MS_PER_\UPDATE只是我們用來更新游戲的粒度(步長).這一步長約小,則趕上需要的處理時間越多(while循環次數越多).這一步越長,則越起伏(長時間不更新).這個值要確保大于處理update的時間
Stuck in the middle
- We update the game at a fixed time step, but we render at arbitrary points in time.
- from the user’s perspective, the game will often display at a point in time between two updates.

- imagine a bullet is flying across the screen. On the first update, it’s on the left side. The second update moves it to the right side. The game is rendered at a point in time between those two updates, so the user expects to see that bullet in the center of the screen. With our current implementation, it will still be on the left side.
-
When we go to render, we’ll pass that in:
- render(lag / MS_PER_UPDATE);
- We divide by MS_\PER_UPDATE here to normalize the value. The value passed to render() will vary from 0 (right at the previous frame) to just under 1.0 (right at the next frame), regardless of the update time step. This way, the renderer doesn’t have to worry about the frame rate. It just deals in values from 0 to 1.
- The renderer knows each game object and its current velocity. Say that bullet is 20 pixels from the left side of the screen and is moving right 400 pixels per frame. If we are halfway between frames, then we’ll end up passing 0.5 to render(). So it draws the bullet half a frame ahead, at 220 pixels.
- Of course, it may turn out that that extrapolation is wrong. When we calculate the next frame, we may discover the bullet hit an obstacle or slowed down or something.
- We rendered its position interpolated between where it was on the last frame and where we think it will be on the next frame. But we don’t know that until we’ve actually done the full update with physics and AI.
- So the extrapolation is a bit of a guess and sometimes ends up wrong. Fortunately, though, those kinds of corrections usually aren’t noticeable. At least, they’re less noticeable than the stuttering you get if you don’t extrapolate at all.
-
備注
- 我們用固定的時間步長更新游戲,但在任意點渲染,從用戶的角度看,游戲通常會在兩次更新之間的時間點顯示
- 想象一個子彈飛過屏幕。在第一次更新時,它在左側。第二個更新將其移動到右側。游戲在這兩個更新之間的時間點呈現,因此用戶期望在屏幕的中心看到子彈。在我們當前的實現中,它仍然在左側
- 但實際上從實現中可以看到,當lag小于更新步長的時候,會跳出更新循環,我們可以直接傳遞:render(lag / MSPERUPDATE);這個值是在0-1之間。如在之前的例子中,子彈在屏幕左側20像素處,然后正每幀400像素向有移動,然后傳入0.5(兩次更新中間),則繪20 + 400 * 0.5 = 220像素,平穩運動
- 但有些時候,這個推論是錯誤的,因為計算下一幀時,子彈可能碰到了障礙物或者減速等,所以需要直到完成了物理和AI的完成更新后才知道。但是幸運的時候,這些修正通常不明顯,至少比不修正前的運動鋸齒好
Design Decisions
Do you own the game loop, or does the platform?
-
使用平臺的事件循環
- It’s simple
- It plays nice with the platform
- You lose control over timing
-
使用引擎的循環
- You don’t have to write it
- You don’t get to write it
-
自己寫
- Total control.
- You have to interface with the platform.
如何管理功耗
- 一個游戲運行的再漂亮,但是30分鐘后手機發燙,體驗很糟糕
- 盡可能少用cpu,sleep
-
運行速度盡可能快
- 游戲體驗好,但是如果玩家在筆記本上,他們的膝蓋會很溫暖
-
限制幀率
- 將設置幀速率的上限(通常為30或60 FPS)。如果游戲循環在該時間段之前完成處理,則其將僅休眠
如何控制游戲速度?
-
游戲循環的兩個關鍵部分
- non-blocking user input
-
adapting to the passage of time
-
Fixed time step with no synchronization(即第一個樣例)
-
Fixed time step with synchronization(sleep樣例)
- 還是很簡單
- 對電量友好,尤其是對于移動游戲.通過簡單地睡眠幾毫秒,而不是試圖在每個tick中做更多的處理
- 游戲不會太快
- 游戲可能會很慢,因為如果一幀中更新和渲染需要時間過長,則播放速度會下降.(因為沒有將更新與渲染分離)
-
Variable time step(可變時間步長)
- 大多數游戲開發人員反對,因為這不是一個好主意
- It adapts to playing both too slowly and too fast
- It makes gameplay non-deterministic and unstable. 這是真正的問題,尤其是物理和網絡會更加困難在這種可變步長的情況下
-
Fixed update time step, variable rendering(最后一個樣例)
- It adapts to playing both too slowly and too fast
- It’s more complex
引用
posted on 2017-02-27 22:07
landon 閱讀(1735)
評論(0) 編輯 收藏 所屬分類:
GameServer