// game loop setInterval(() => { update(); render(); }, 1000 / 60);
setInterval
vs requestAnimationFrame
update()
requestAnimationFrame(() => { angle++; render(); ... });
let last = performance.now(); requestAnimationFrame(() => { let now = performance.now(), dt = now - last; angle += dt * 60 / 1000; last = now; render(); ... });
let dt = 0, step = 1 / 60, last = performance.now(); requestAnimationFrame(() => { let now = performance.now(); dt += (now - last) / 1000; while(dt > step) { dt -= step; angle++; } last = now; render(dt); ... });
update()
update()
update()
зависит итоговый результат симуляции
let last = performance.now(), step = 1 / 60, dt = 0, now; let frame = () => { now = performance.now(); dt = dt + Math.min(1, (now - last) / 1000); while(dt > step) { dt = dt - step; update(step); } last = now; render(dt); requestAnimationFrame(frame); } requestAnimationFrame(frame);
update()
и render()
:let lerp = (start, finish, time) => { return start + (finish - start) * time; };
let last = performance.now(), fps = 60, slomo = 1, // slow motion multiplier step = 1 / fps, slowStep = slomo * step, dt = 0, now; let frame = () => { now = performance.now(); dt = dt + Math.min(1, (now - last) / 1000); while(dt > slowStep) { dt = dt - slowStep; update(step); } last = now; render(dt / slomo * fps); requestAnimationFrame(frame); } requestAnimationFrame(frame);
Преждевременная оптимизация - зло!
let inputState = { UP: false, DOWN: false, LEFT: false, RIGHT: false, ROTATE: false }; // ... let update = (step) => { if (inputState.LEFT) posX--; if (inputState.RIGHT) posX++; if (inputState.UP) posY--; if (inputState.DOWN) posY++; if (inputState.ROTATE) angle++; };
update()
update()
и render()
class GameScene { constructor(game) { this.game = game; this.angle = 0; this.posX = game.canvas.width / 2; this.posY = game.canvas.height / 2; } update(dt) { if (this.game.keys['87']) this.posY--; // W if (this.game.keys['83']) this.posY++; // S if (this.game.keys['65']) this.posX--; // A if (this.game.keys['68']) this.posX++; // D if (this.game.keys['32']) this.angle++; // SPACE if (this.game.keys['27']) this.game.setScene(MenuScene); // Back to menu } render(dt, ctx, canvas) { ... ctx.fillStyle = '#0d0'; ctx.fillRect(posX, posY, rectSize, rectSize); } }
class Game { constructor() { this.setScene(IntroScene); this.initInput(); this.startLoop(); } initInput() { this.keys = {}; document.addEventListener('keydown', e => { this.keys[e.which] = true; }); document.addEventListener('keyup', e => { this.keys[e.which] = false; }); } setScene(Scene) { this.activeScene = new Scene(this); } update(dt) { this.activeScene.update(dt); } render(dt) { this.activeScene.render(dt, this.ctx, this.canvas); } }
let context = new AudioContext(); fetch('sounds/music.mp3').then(response => { response.arrayBuffer().then(arrayBuffer => { context.decodeAudioData(arrayBuffer, buffer => { let source = context.createBufferSource(); source.buffer = buffer; source.connect(context.destination); source.start(0); }); }); });
<audio>
→ WebAudio