kb/data/developer.mozilla.org/en-US/docs/Games/Anatomy-1.md

7.9 KiB

title chunk source category tags date_saved instance
Anatomy of a video game - Game development | MDN 2/6 https://developer.mozilla.org/en-US/docs/Games/Anatomy reference web, html, css, javascript, documentation 2026-05-05T05:20:51.278426+00:00 kb-cron

Present, accept, interpret, calculate, repeat

The goal of every video game is to present the user(s) with a situation, accept their input, interpret those signals into actions, and calculate a new situation resulting from those acts. Games are constantly looping through these stages, over and over, until some end condition occurs (such as winning, losing, or exiting to go to bed). Not surprisingly, this pattern corresponds to how a game engine is programmed. The specifics depend on the game. Some games drive this cycle by user input. Imagine that you are developing a "find the differences between these two similar pictures" -type game. These games present two images to the user; they accept their click (or touch); they interpret the input as a success, failure, pause, menu interaction, etc.; finally, they calculate an updated scene resulting from that input. The game loop is advanced by the user's input and sleeps until they provide it. This is more of a turn-based approach that doesn't demand a constant update every frame, only when the player reacts. Other games demand control over each of the smallest possible individual timeslices. The same principles as above apply with a slight twist: each frame of animation progresses the cycle and any change in user input is caught at the first available turn. This once-per-frame model is implemented in something called a main loop. If your game loops based on time then this will be its authority that your simulations will adhere to. But it might not need per-frame control. Your game loop might be similar to the find the differences example and base itself on input events. It might require both input and simulated time. It might even loop based on something else entirely. Modern JavaScript — as described in the next sections — thankfully makes it less difficult to develop an efficient, execute-once-per-frame main loop. Of course, your game will only be as optimized as you make it. If something looks like it should be attached to a more infrequent event then it is often a good idea to break it out of the main loop (but not always). ## Building a main loop in JavaScript JavaScript works best with events and callback functions. Modern browsers strive to call methods right as they are needed and idle (or do their other tasks) in the gaps. It is an excellent idea to attach your code to the moments that are appropriate for them. Think about whether your function really needs to be called on a strict interval of time, every frame, or only after something else happens. Being more specific with the browser about when your function needs to be called allows the browser to optimize when it is called. Also, it will probably make your job easier. Some code needs to be run frame-by-frame so why attach that function to anything other than the browser's redraw schedule? On the Web, window.requestAnimationFrame() will be the foundation of most well-programmed per-frame main loops. A callback function must be passed in to it when it is called. That callback function will be executed at a suitable time before the next repaint. Here is an example of a simple main loop: Note: In each of the main() methods discussed here, we schedule a new requestAnimationFrame before performing our loop contents. That is not by accident and it is considered best practice. Calling the next requestAnimationFrame early ensures the browser receives it on time to plan accordingly even if your current frame misses its VSync window. The above chunk of code has two statements. The first statement creates a function as a global variable called main(). This function does some work and also tells the browser to call itself next frame with window.requestAnimationFrame(). The second statement calls the main() function, defined in the first statement. Because main() is called once in the second statement and every call of it places itself in the queue of things to do next frame, main() is synchronized to your frame rate. Of course this loop is not perfect. Before we discuss ways to change it, let us discuss what it already does well. Timing the main loop to when the browser paints to the display allows you to run your loop as frequently as the browser wants to paint. You are given control over each frame of animation. It is also very simple because main() is the only function getting looped. A First-Person Shooter (or a similar game) presents a new scene once every frame. You cannot really get more smooth and responsive than that. But do not immediately assume animations require frame-by-frame control. Simple animations can be easily performed, even GPU-accelerated, with CSS animations and other tools included in the browser. There are a lot of them and they will make your life easier. ## Building a better main loop in JavaScript There are two obvious issues with our previous main loop: main() pollutes the window object (where all global variables are stored) and the example code did not leave us with a way to stop the loop unless the whole tab is closed or refreshed. For the first issue, if you want the main loop to just run and you do not need direct access to it, you could create it as an Immediately-Invoked Function Expression (IIFE). When the browser comes across this IIFE, it will define your main loop and immediately queue it for the next frame. It will not be attached to any object and main (or main() for methods) will be a valid unused name in the rest of the application, free to be defined as something else. Note: In practice, it is more common to prevent the next requestAnimationFrame() with an if-statement, rather than calling cancelAnimationFrame(). For the second issue, stopping the main loop, you will need to cancel the call to main() with window.cancelAnimationFrame(). You will need to pass cancelAnimationFrame() the ID token given by requestAnimationFrame() when it was last called. Let us assume that your game's functions and variables are built on a namespace that you called MyGame. Expanding our last example, the main loop would now look like: We now have a variable declared in our MyGame namespace, which we call stopMain, that contains the ID returned from our main loop's most recent call to requestAnimationFrame(). At any point, we can stop the main loop by telling the browser to cancel the request that corresponds to our token. The key to programming a main loop, in JavaScript, is to attach it to whatever event should be driving your action and pay attention to how the different systems involved interplay. You may have multiple components driven by multiple different types of events. This feels like unnecessary complexity but it might just be good optimization (not necessarily, of course). The problem is that you are not programming a typical main loop. In JavaScript, you are using the browser's main loop and you are trying to do so effectively. ## Building a more optimized main loop in JavaScript Ultimately, in JavaScript, the browser is running its own main loop and your code exists in some of its stages.