Realmscape

Human Interaction

12 Aug 2015  -  Jon Hall

Even in the case of a point and click adventure, we don't want to be only using buttons as our primary interaction method with our game logic.

In JavaScript game engines the most common form of interaction is via Event Listeners and Handlers. I'll be primarily focusing on handlers for KeyboardEvents[1] and MouseEvents[2] in this post, although keep in mind there are an assortment of other event types (and you can even create your own!).
An Event Handler in its most simple form, is a function that defines the action to take when a specific event type is raised. It is usually provided with the Element that triggered the event, and/or the raised Event itself as arguments.

Event Handlers can be defined for an individual HTMLElement, Document, or Window and by default the Event then bubbles (or propagates) up through the DOM to the highest level (until cancelled or stopped). This behaviour allows us to, for instance, provide two different responses to mouse clicks on child Elements both of which are then also received and handled by a parent Element.

<BUTTON id="consoleInteract" onclick="javascript:interact(window.prompt('Output to console:'));">Interact</BUTTON>
[...]
<SCRIPT type="text/javascript">
 document.onclick = function(e){ //Add an event handler to the document object
  console.log("CLICK!"); //Output a string to the debugging console, to show the order in which handlers are fired
  addLine('console1',"CLICK!"); //Output to our text interface as well, so the order of events is clear
 }
</SCRIPT>
The debugging console output will contain the output from the button-triggered prompt, then "CLICK!" from the document handler

If we add a document.onclick EventHandler to the code from the end of the previous post, we can then see the event propagates up the DOM from the Button Element to the Document Element, and the handlers are fired in order.

Some of the Event Handlers

Several common event handlers can be found through the GlobalEventHandlers[3] interface. These include:

All of which are useful (and in some cases, only vary slightly different from each other), and some of which we will use in our own customised input handler. Of note, is that the onkeypress handler can vary in implementation between browsers - some fire on all key presses, while others exclude certain keys (such as the arrows).

Most frustratingly, for the use of game input, these JavaScript handlers fire asynchronously and there is no set interval between events. So although they are convenient for capturing a single event, if you want to handle a 'held down' key state (or repetitive onKeyDown events) you'll need to tie it to your engine's tick cycle to ensure predictable repetition (without that link, you'll usually find that the first event is fired right away, then there'll be a delay, and then the event will be fired repeatedly in quick succession - not ideal if your entity movement relies on keys being held down!).
If you've read previous blog posts, you'll already know that the game loop itself should be handling and responding to user input, and these keyboard event handlers should just be updating the engine's user input state.

The Event Object

Instantiated whenever an event is raised, the Event Object[4] defines the core properties of that event such as Event.target (the target the event was originally dispatched from), Event.bubbles (whether the event propagates up the DOM), Event.timestamp (the time of event creation), and Event.type (the name of the event).

We will be focusing on KeyboardEvents[1] and MouseEvents[2] in this post, which are higher level implementations of the Event Object and provide even more properties such as KeyboardEvent.keyCode[5] (a code for the key represented by the event), MouseEvent.button (the button number that was pressed when the event fired), MouseEvent.clientX and MouseEvent.clientY (local cursor coordinates).

The KeyboardEvent.keyCode property returns an implementation dependent number identifying the key represented in the event. These numbers can be easily matched up to physical keys with an internet search for "JavaScript Key Codes", or determined yourself with a KeyboardEvent handler that returns or displays the .keyCode property.

//Demonstrate the frequency of event firing, and the keyCode associated with the event
document.onkeypress = function(e){
 console.log(e); //Output the KeyboardEvent to the debugging console
 addLine('console1',"keyCode: "+e.key); //Output the keyCode to our text interface
}
Code such as this can be used to determine the keyCode value for a given key

The use of numerical keyCodes allows us to create a reconfigurable mapping of individual keys to specific engine actions, which will be demonstrated in the next blog post (it was going to be included in this one, but it became too long!).

The complete code from the previous post, updated with the changes covered in this one, can be found on GitHub, or below.

<html>
 <head>
  <script type="text/javascript">
function addLine(elem,content){
 var c = document.getElementById(elem);
 var line = document.createElement('p');
 line.setAttribute("id","p"+c.children.length);
 line.innerHTML = content;
 c.appendChild(line);
 line.scrollIntoView();
}
function interact(interaction){
 if(interaction != null && interaction.length >= 1){
  console.log(interaction);
  addLine('console1',interaction);
 }
}
  </script>
  <style type="text/css">
p {
 margin:0;
}
  </style>
 </head>
 <body>
  <div id="console1" style="height:80px;width:350px;border:1px solid black;font-family:monospace;"></div>
  <button id="consoleInteract" onclick="javascript:interact(window.prompt('Output to console:'));">Interact</button>
  <script type="text/javascript">
//Prep DOM
var dev = document.getElementById("console1");
dev.style.overflowY = "scroll";
dev.style.maxHeight = dev.style.height || "200px";

//Demonstrate the frequency of event firing, and the keyCode associated with the event
document.onkeypress = function(e){
console.log(e); //Output the KeyboardEvent to the debugging console
addLine('console1',"keyCode: "+e.keyCode); //Output the keyCode to our text interface
}

document.onclick = function(e){ //Add an event handler to the document object
console.log("CLICK!"); //Output a string to the debugging console, to show the order in which handlers are fired
addLine('console1',"CLICK!"); //Output to our text interface as well, so the order of events is clear
}
  </script>
 </body>
</html>
The complete code from the previous post, extended to include keyPress and click handlers (lines 34-43)
Comments, Feedback, and Discussion can be found on our subreddit

 

[1] MDN KeyboardEvent documentation
[2] MDN MouseEvent documentation
[3] MDN GlobalEventHandlers documentation
[4] MDN Event Object documentation
[5] At the time of writing, KeyboardEvent.keyCode is marked as becoming deprecated and KeyboardEvent.key should be used instead where available. Currently KeyboardEvent.key is not yet supported by current versions of the major browsers, so the deprecated KeyboardEvent.keyCode is used throughout this blog.