| 2038 | // Run game loop |
| 2039 | let lastTimestamp = 0; |
| 2040 | function frameHandler(timestamp) { |
| 2041 | let frameTime = timestamp - lastTimestamp; |
| 2042 | lastTimestamp = timestamp; |
| 2043 | |
| 2044 | // always queue another frame |
| 2045 | raf(); |
| 2046 | |
| 2047 | // If game is paused, we'll still track frameTime (above) but all other |
| 2048 | // game logic and drawing can be avoided. |
| 2049 | if (isPaused()) return; |
| 2050 | |
| 2051 | // make sure negative time isn't reported (first frame can be whacky) |
| 2052 | if (frameTime < 0) { |
| 2053 | frameTime = 17; |
| 2054 | } |
| 2055 | // - cap minimum framerate to 15fps[~68ms] (assuming 60fps[~17ms] as 'normal') |
| 2056 | else if (frameTime > 68) { |
| 2057 | frameTime = 68; |
| 2058 | } |
| 2059 | |
| 2060 | const halfW = width / 2; |
| 2061 | const halfH = height / 2; |
| 2062 | |
| 2063 | // Convert pointer position from screen to scene coords. |
| 2064 | pointerScene.x = pointerScreen.x / viewScale - halfW; |
| 2065 | pointerScene.y = pointerScreen.y / viewScale - halfH; |
| 2066 | |
| 2067 | const lag = frameTime / 16.6667; |
| 2068 | const simTime = gameSpeed * frameTime; |
| 2069 | const simSpeed = gameSpeed * lag; |
| 2070 | tick(width, height, simTime, simSpeed, lag); |
| 2071 | |
| 2072 | // Auto clear canvas |
| 2073 | ctx.clearRect(0, 0, canvas.width, canvas.height); |
| 2074 | // Auto scale drawing for high res displays, and incorporate `viewScale`. |
| 2075 | // Also shift canvas so (0, 0) is the middle of the screen. |
| 2076 | // This just works with 3D perspective projection. |
| 2077 | const drawScale = dpr * viewScale; |
| 2078 | ctx.scale(drawScale, drawScale); |
| 2079 | ctx.translate(halfW, halfH); |
| 2080 | draw(ctx, width, height, viewScale); |
| 2081 | ctx.setTransform(1, 0, 0, 1, 0, 0); |
| 2082 | } |
| 2083 | const raf = () => requestAnimationFrame(frameHandler); |
| 2084 | // Start loop |
| 2085 | raf(); |