-
Notifications
You must be signed in to change notification settings - Fork 22.7k
chore: run Prettier on games #19943
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
chore: run Prettier on games #19943
Changes from all commits
ca1db7a
45bf133
3e59fef
c744033
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"bracketSameLine": true | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ tags: | |
- Main Loop | ||
- requestAnimationFrame | ||
--- | ||
|
||
{{GamesSidebar}} | ||
|
||
This article looks at the anatomy and workflow of the average video game from a technical point of view, in terms of how the main loop should run. It helps beginners to modern game development understand what is required when building a game and how web standards like JavaScript lend themselves as tools. Experienced game programmers who are new to web development could also benefit, too. | ||
|
@@ -55,13 +56,14 @@ But do not immediately assume animations require frame-by-frame control. Simple | |
|
||
There are two obvious issues with our previous main loop: `main()` pollutes the `{{ domxref("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 easy (direct) access to it, you could create it as an Immediately-Invoked Function Expression (IIFE). | ||
|
||
<!-- prettier-ignore-start --> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Curious: When do you add these? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. MMmh. When I reviewed this PR, this morning, it happened that GitHub hid some previously-changed files . I would really like to avoid these as much as possible (read maybe 3-4 places on the whole of MDN), but this can be fixed in a follow-up PR. (The way games/ is doing examples is unusual for MDN). |
||
```js | ||
/* | ||
* Starting with the semicolon is in case whatever line of code above this example | ||
* relied on automatic semicolon insertion (ASI). The browser could accidentally | ||
* think this whole example continues from the previous line. The leading semicolon | ||
* marks the beginning of our new line if the previous one was not empty or terminated. | ||
*/ | ||
* Starting with the semicolon is in case whatever line of code above this example | ||
* relied on automatic semicolon insertion (ASI). The browser could accidentally | ||
* think this whole example continues from the previous line. The leading semicolon | ||
* marks the beginning of our new line if the previous one was not empty or terminated. | ||
*/ | ||
|
||
;(() => { | ||
function main() { | ||
|
@@ -73,22 +75,24 @@ There are two obvious issues with our previous main loop: `main()` pollutes the | |
main(); // Start the cycle | ||
})(); | ||
``` | ||
<!-- prettier-ignore-end --> | ||
|
||
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 `{{ domxref("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: | ||
|
||
<!-- prettier-ignore-start --> | ||
```js | ||
/* | ||
* Starting with the semicolon is in case whatever line of code above this example | ||
* relied on automatic semicolon insertion (ASI). The browser could accidentally | ||
* think this whole example continues from the previous line. The leading semicolon | ||
* marks the beginning of our new line if the previous one was not empty or terminated. | ||
* | ||
* Let us also assume that MyGame is previously defined. | ||
*/ | ||
* Starting with the semicolon is in case whatever line of code above this example | ||
* relied on automatic semicolon insertion (ASI). The browser could accidentally | ||
* think this whole example continues from the previous line. The leading semicolon | ||
* marks the beginning of our new line if the previous one was not empty or terminated. | ||
* | ||
* Let us also assume that MyGame is previously defined. | ||
*/ | ||
|
||
;(() => { | ||
function main() { | ||
|
@@ -100,6 +104,7 @@ For the second issue, stopping the main loop, you will need to cancel the call t | |
main(); // Start the cycle | ||
})(); | ||
``` | ||
<!-- prettier-ignore-end --> | ||
|
||
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. | ||
|
||
|
@@ -134,15 +139,16 @@ const tNow = window.performance.now(); | |
|
||
Back to the topic of the main loop. You will often want to know when your main function was invoked. Because this is common, `window.requestAnimationFrame()` always provides a `DOMHighResTimeStamp` to callbacks as an argument when they are executed. This leads to another enhancement to our previous main loops. | ||
|
||
<!-- prettier-ignore-start --> | ||
```js | ||
/* | ||
* Starting with the semicolon is in case whatever line of code above this example | ||
* relied on automatic semicolon insertion (ASI). The browser could accidentally | ||
* think this whole example continues from the previous line. The leading semicolon | ||
* marks the beginning of our new line if the previous one was not empty or terminated. | ||
* | ||
* Let us also assume that MyGame is previously defined. | ||
*/ | ||
* Starting with the semicolon is in case whatever line of code above this example | ||
* relied on automatic semicolon insertion (ASI). The browser could accidentally | ||
* think this whole example continues from the previous line. The leading semicolon | ||
* marks the beginning of our new line if the previous one was not empty or terminated. | ||
* | ||
* Let us also assume that MyGame is previously defined. | ||
*/ | ||
|
||
;(() => { | ||
function main(tFrame) { | ||
|
@@ -155,6 +161,7 @@ Back to the topic of the main loop. You will often want to know when your main f | |
main(); // Start the cycle | ||
})(); | ||
``` | ||
<!-- prettier-ignore-end --> | ||
|
||
Several other optimizations are possible and it really depends on what your game attempts to accomplish. Your game genre will obviously make a difference but it could even be more subtle than that. You could draw every pixel individually on a canvas or you could layer DOM elements (including multiple WebGL canvases with transparent backgrounds if you want) into a complex hierarchy. Each of these paths will lead to different opportunities and constraints. | ||
|
||
|
@@ -168,15 +175,16 @@ You will need to make hard decisions about your main loop: how to simulate the a | |
|
||
If your game can hit the maximum refresh rate of any hardware you support then your job is fairly easy. You can update, render, and then do nothing until VSync. | ||
|
||
<!-- prettier-ignore-start --> | ||
```js | ||
/* | ||
* Starting with the semicolon is in case whatever line of code above this example | ||
* relied on automatic semicolon insertion (ASI). The browser could accidentally | ||
* think this whole example continues from the previous line. The leading semicolon | ||
* marks the beginning of our new line if the previous one was not empty or terminated. | ||
* | ||
* Let us also assume that MyGame is previously defined. | ||
*/ | ||
* Starting with the semicolon is in case whatever line of code above this example | ||
* relied on automatic semicolon insertion (ASI). The browser could accidentally | ||
* think this whole example continues from the previous line. The leading semicolon | ||
* marks the beginning of our new line if the previous one was not empty or terminated. | ||
* | ||
* Let us also assume that MyGame is previously defined. | ||
*/ | ||
|
||
;(() => { | ||
function main(tFrame) { | ||
|
@@ -189,6 +197,7 @@ If your game can hit the maximum refresh rate of any hardware you support then y | |
main(); // Start the cycle | ||
})(); | ||
``` | ||
<!-- prettier-ignore-end --> | ||
|
||
If the maximum refresh rate cannot be reached, quality settings could be adjusted to stay under your time budget. The most famous example of this concept is the game from id Software, RAGE. This game removed control from the user in order to keep its calculation time at roughly 16ms (or roughly 60fps). If computation took too long then rendered resolution would decrease, textures and other assets would fail to load or draw, and so forth. This (non-web) case study made a few assumptions and tradeoffs: | ||
|
||
|
@@ -229,34 +238,35 @@ A separate update and draw method could look like the following example. For the | |
|
||
> **Warning:** This example, specifically, is in need of technical review. | ||
|
||
<!-- prettier-ignore-start --> | ||
```js | ||
/* | ||
* Starting with the semicolon is in case whatever line of code above this example | ||
* relied on automatic semicolon insertion (ASI). The browser could accidentally | ||
* think this whole example continues from the previous line. The leading semicolon | ||
* marks the beginning of our new line if the previous one was not empty or terminated. | ||
* | ||
* Let us also assume that MyGame is previously defined. | ||
* | ||
* MyGame.lastRender keeps track of the last provided requestAnimationFrame timestamp. | ||
* MyGame.lastTick keeps track of the last update time. Always increments by tickLength. | ||
* MyGame.tickLength is how frequently the game state updates. It is 20 Hz (50ms) here. | ||
* | ||
* timeSinceTick is the time between requestAnimationFrame callback and last update. | ||
* numTicks is how many updates should have happened between these two rendered frames. | ||
* | ||
* render() is passed tFrame because it is assumed that the render method will calculate | ||
* how long it has been since the most recently passed update tick for | ||
* extrapolation (purely cosmetic for fast devices). It draws the scene. | ||
* | ||
* update() calculates the game state as of a given point in time. It should always | ||
* increment by tickLength. It is the authority for game state. It is passed | ||
* the DOMHighResTimeStamp for the time it represents (which, again, is always | ||
* last update + MyGame.tickLength unless a pause feature is added, etc.) | ||
* | ||
* setInitialState() Performs whatever tasks are leftover before the main loop must run. | ||
* It is just a generic example function that you might have added. | ||
*/ | ||
* Starting with the semicolon is in case whatever line of code above this example | ||
* relied on automatic semicolon insertion (ASI). The browser could accidentally | ||
* think this whole example continues from the previous line. The leading semicolon | ||
* marks the beginning of our new line if the previous one was not empty or terminated. | ||
* | ||
* Let us also assume that MyGame is previously defined. | ||
* | ||
* MyGame.lastRender keeps track of the last provided requestAnimationFrame timestamp. | ||
* MyGame.lastTick keeps track of the last update time. Always increments by tickLength. | ||
* MyGame.tickLength is how frequently the game state updates. It is 20 Hz (50ms) here. | ||
* | ||
* timeSinceTick is the time between requestAnimationFrame callback and last update. | ||
* numTicks is how many updates should have happened between these two rendered frames. | ||
* | ||
* render() is passed tFrame because it is assumed that the render method will calculate | ||
* how long it has been since the most recently passed update tick for | ||
* extrapolation (purely cosmetic for fast devices). It draws the scene. | ||
* | ||
* update() calculates the game state as of a given point in time. It should always | ||
* increment by tickLength. It is the authority for game state. It is passed | ||
* the DOMHighResTimeStamp for the time it represents (which, again, is always | ||
* last update + MyGame.tickLength unless a pause feature is added, etc.) | ||
* | ||
* setInitialState() Performs whatever tasks are leftover before the main loop must run. | ||
* It is just a generic example function that you might have added. | ||
*/ | ||
|
||
;(() => { | ||
function main(tFrame) { | ||
|
@@ -293,6 +303,7 @@ A separate update and draw method could look like the following example. For the | |
main(performance.now()); // Start the cycle | ||
})(); | ||
``` | ||
<!-- prettier-ignore-end --> | ||
|
||
Another alternative is to do certain things less often. If a portion of your update loop is difficult to compute but insensitive to time, you might consider scaling back its frequency and, ideally, spreading it out into chunks throughout that lengthened period. An implicit example of this was found over at The Artillery Blog for Artillery Games, where they [adjust their rate of garbage generation](https://web.archive.org/web/20161021030645/http://blog.artillery.com/2012/10/browser-garbage-collection-and-framerate.html) to optimize garbage collection. Obviously, cleaning up resources is not time sensitive (especially if tidying is more disruptive than the garbage itself). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have a general comment: I find lines like this hard to read where you have a paragraph on one line, essentially. If there are changes to a word in a sentence in the middle, the entire line is affected in a diff. Prettier can format these using proseWrap in prettier config: {
"proseWrap": "always",
} An alternative is to write one sentence per line, but this is a manual effort. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't have a consensus on this:
I think we are far from a consensus. More, I'm not sure we can find a solution that we agree on, and that is enforceable easily to the long tail of editors. To be fair, I don't think it is a coincidence that Prettier does nothing about this by default. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My two cents: we're taking the initiative to remove the ambiguity and discussions about code style in example code using Prettier, we could do the same for the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is a discussion worth having once we have set up Prettier and/or Markdownlint. "Stück für Stück" 🤣 . There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Sounds good to me 😂 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we certainly had a strong consensus on this: #6936 . There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I agree with this though, we should run Prettier on Markdown. When we converted from HTML, we ran it through Prettier as well. (Apparently I'm wrong about this. I don't know whether there are things markdownlint does that Prettier does not.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have a strong preference for not wrapping at all—or at least not wrap by column width. I configured my editor to soft wrap and I encourage everyone to do so (you can also do that on GitHub). If you wrap by column width, and you add one word on the first line, you would possibly trigger tens of lines of diff due to the reflow. I'm okay with soft breaking by sentences (in that I won't call it out or change it if others do that), but IMO it makes reading the content from the source a little "bumpy", like reading poetry or one of those poorly structured Twitter threads. |
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ tags: | |
- Games | ||
- Web | ||
--- | ||
|
||
{{GamesSidebar}} | ||
|
||
This page lists a number of impressive web technology demos for you to get inspiration from, and generally have fun with. A testament to what can be done with JavaScript, WebGL, and related technologies. The first two sections list playable games, while the second is a catch-all area to list demos that aren't necessarily interactive/games. | ||
|
@@ -52,10 +53,8 @@ This page lists a number of impressive web technology demos for you to get inspi | |
|
||
- [A Wizard's Lizard](http://www.wizardslizard.com/) | ||
- : Top down Zelda-esque exploration/RPG. | ||
|
||
**[Bullet Force](https://www.crazygames.com/game/bullet-force-multiplayer)** | ||
3D multiplayer first-person shooter. | ||
|
||
- [Bullet Force](https://www.crazygames.com/game/bullet-force-multiplayer) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was the manual intervention |
||
- : 3D multiplayer first-person shooter. | ||
- [Elliot Quest](https://elliotquest.com/) | ||
- : 8-bit graphic retro adventure game. | ||
- [RPG MO](https://data.mo.ee/index2.html) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ tags: | |
- HTML | ||
- Mobile | ||
--- | ||
|
||
{{GamesSidebar}} | ||
|
||
## Advantages | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just as with
.eslintrc.js
, I think it would be better to go with.prettierrc.js
(usingmodule.exports = { ... }
syntax, as this allows adding comments (while JSON) doesn't allow this.