Arena Shooter tutorial

Step 1: Game loop and keyboard

Every game contains a program loop. Gaming frameworks hide it and provide only event handlers called in the loop, but here we create one in the Render object. We could use requestAnimationFrame, but we work with slower framerate (30 FPS) which is enough for this kind of games and saves some power. Any object can register its method in the game loop by calling Render.register to be called every frame. We also need a Render.remove to remove a reference from game loop. This is needed for objects that are created dynamically and released after some time (bullets in our game). Without this, after the bullet is removed from the DOM, its registered method reference would still hang in Render object causing a memory leak. Read more about JS memory management.

Next, we need Keys object to fire immediately and continuously while key is pressed (keypress event has a delay after press, optimized to type text). Go ahead and try to push any combination of keys. It reads everything but Fn key, which is hardware driven. If you have gaming keyboard, it should handle any kombination of 6 keys. On my notebook keyboard, it can't read D if I push W and S. Here we need two arrows and space pushed together for good gaming experience, so I can't assign them to these particular keys. In the Keys object, we define reserved Set to prevent them to set the browser the default action. Here we reserve arrows Up, Down and Space (keycodes 38, 40, 32) to prevent them to scroll the page. Non-reserved keys (like F5) works as usual.

Both these objects are passive, they are independent on the rest of the game and don't enforce any interface, so they can be reused anywhere.

Try to push some keys to find out your keyboard functionality, be sure you understand the code behind and proceed to step 2.

Keys = new function() { this.down = []; this.reserved = new Set; addEventListener("keydown", e => ( this.reserved.has(e.keyCode) && e.preventDefault(), this.down[e.keyCode]=1) ); addEventListener("keyup", e => this.down[e.keyCode]=0); }; Render = new function() { var handlers = new Set; this.register = fn => handlers.add(fn); this.remove = fn => handlers.delete(fn); setInterval(_=> handlers.forEach(h => h()), 33); }; Render.register( _=> (Arena.innerHTML="") || Keys.down.forEach( (v,k) => v && (Arena.innerHTML+= ' '+k) ) ); [32, 38, 40].forEach(key => Keys.reserved.add(key));