martes, 21 de agosto de 2012

HTML5 - Eventos en el canvas

Hola a todos!!
En el siguiente tutorial vamos a crear una clase llamada EventManager. Esta clase nos permitirá recoger los eventos del ratón y táctiles generados sobre el canvas.

La idea principal es tener una instancia accesible desde cualquier punto de la aplicación, y que contenga los datos referentes a la entrada del ratón o táctil.
No nos limitaremos a tener definida la clase EventManager, vamos a instanciarla para poder utilizarla desde los listeners de los eventos.
Los listeners serán funciones globales que registraremos en el canvas.

Función constructora de EventManager

function EventManager(){
  this.touchdown = 0;
  this.touchup = 0;

  this.canX;
  this.canY;
  this.canvas;
  this.currentEvents = [mouseDown,

                        mouseXY,
                        touchDown,
                        touchXY,
                        touchUp,
                        mouseUp,
                        touchUp];
}


touchdown: si es 1 indicará que se está presionando el botón del mouse o la pantalla, si es 0 lo contrario.
touchup: si es 1 indicará que acaba de dejarse de presionar el botón del mouse, la duración de este estado haremos que sea de 1 iteración.
canX: es un número entero que contiene la coordenada x donde está situado el mouse o el dedo. (píxeles)
canY: es un número entero que contiene la coordenada y donde está situado el mouse o el dedo. (píxeles)
canvas: contiene una referencia al elemento del dom <canvas>.
currentEvents: es un array que contiene las referencias de las funciones globales que registraremos como listeners de eventos del canvas.

[Para los que no estéis familiarizados con la creación de clases en javascript revisad mi tutorial!]

La instancia que crearemos se llamará EM, la crearemos de la siguiente forma:
var EM = new EventManager();

Creación de los Event Listeners

Los events listeners son funciones que registramos en el canvas para que se disparen al darse determinados eventos. Estas funciones son las que modifican las propiedades de la instancia EM, cambiando las coordenadas del mouse y el estado del click (touchdown, touchup).

function mouseUp() {
  EM.touchup = 1;
  EM.touchdown = 0;
  mouseXY();
}

function touchUp() {
  EM.touchup = 1;
  EM.touchdown = 0;

  touchXY();
}

function mouseDown() {
  EM.touchup = 0;
  EM.touchdown = 1;
  mouseXY();
}

function touchDown() {
  EM.touchup = 0;
  EM.touchdown = 1;
  touchXY();
}

function mouseXY(e) {
  EM.canX = e.pageX - EM.canvas.offsetLeft;
  EM.canY = e.pageY - EM.canvas.offsetTop;
}

function touchXY(e) {
  e.preventDefault();
  EM.canX = e.targetTouches[0].pageX - EM.canvas.offsetLeft;
  EM.canY = e.targetTouches[0].pageY - EM.canvas.offsetTop;
}


Al llamar al constructor de EventManager, almacenamos las referencias a estas funciones en la instancia EM dentro del array EM.currentEvents.

Registrar un event listener en el canvas

Para registrar un event listener en el canvas, emplearemos la función addEventListener.
Esta función requiere 3 parámetros:

canvasElement.addEventListener( DOMString type,  
                                EventListener listener, 
                                boolean useCapture);

type: mediante un String indicamos que tipo de evento será el que dispare la función.
Los siguientes tipos de eventos son equivalentes, unos responden al mouse y a entradas táctiles.
  "mousedown" = "touchstart"
  "mousemove" =  "touchmove"
  "mouseup" = "touchend"

listener: es la función que se ejecutará al dispararse el evento indicado.
useCapture: si es true, el event listener se dispara en la fase de captura del evento, si es false se dispara en la fase de destino y propagación del evento. Yo siempre los empleo como false.

Para eliminar un event listener del canvas, tenemos la función removeEventListener, que admite los mismos parámetros que addEventListener.

Para facilitar el registro y eliminación de event listeners, vamos a añadir dos funciones al prototipo de nuestra clase EventManager.


EventManager.prototype.addEventsListeners = function(){
  this.canvas.addEventListener("mousedown", this.currentEvents[0], true);
  this.canvas.addEventListener("mousemove", this.currentEvents[1], false);
  this.canvas.addEventListener("touchstart", this.currentEvents[2], false);
  this.canvas.addEventListener("touchmove", this.currentEvents[3], true);
  this.canvas.addEventListener("touchend", this.currentEvents[4], false);
  document.body.addEventListener("mouseup", this.currentEvents[5], false);
}

EventManager.prototype.removeEventsListeners = function(){
  this.canvas.removeEventListener("mousedown", this.currentEvents[0], false);
  this.canvas.removeEventListener("mousemove", this.currentEvents[1], false);
  this.canvas.removeEventListener("touchstart", this.currentEvents[2], false);
  this.canvas.removeEventListener("touchmove", this.currentEvents[3], true);
  this.canvas.removeEventListener("touchend", this.currentEvents[4], false);
  document.body.removeEventListener("mouseup", this.currentEvents[5], false);
}


Estas funciones emplean la referencia al canvas actual que gestiona la instancia EM y las referencias a las funciones event listener que hemos almacenado en el array currentEvents.
Gracias a estas funciones podremos activar y desactivar los eventos sobre nuestro canvas de forma sencilla.

Activar eventos:
EM.addEventsListeners();

Desactivar eventos:
EM.removeEventsListeners();

Comprobar si se está pulsando el mouse o si se ha dejado de pulsar

Ahora tenemos una instancia EM, mediante los event listeners se actualizan las propiedades de esta instancia, las coordenadas del mouse y si se está presionando o si se acaba de dejar de presionar.
Ahora necesitamos dos funciones que faciliten al usuario controlar si se han dado eventos o no.

EventManager.prototype.isTouchDown = function(){
return (this.touchdown == 1);
}

EventManager.prototype.isTouchUp = function(){
if(this.touchup == 0) return false;
else{
this.touchup = 0; return true;
}
}


Ambas funciones retornan un valor booleano, es decir, true o false.
El funcionamiento de isTouchDown es muy simple! mientras el ratón se está presionando o mientras se mantiene presionada la pantalla la propiedad touchdown adquiere el valor 1, y isTouchDown() retornará true, de otro modo retornará false.

El funcionamiento de isTouchUp está pensado para que cuando dejemos de hacer click o justo cuando levantemos el dedo de la pantalla, si comprobamos si esto acaba de ocurrir llamando a isTouchUp() esta función indique true, pero si inmediatamente volvemos a comprobarlo, la función nos dirá false. Así conseguimos que únicamente el estado isTouchUp dure una iteración, y no que sea un estado que se alargue durante todo el rato que no estemos presionando el botón del mouse.

El código completo de la clase EventManager y los event listeners

Para tener el código más ordenado aconsejo que tengáis este código en un fichero javascript separado, llamado EventManager.js:


function EventManager(){
  this.touchdown = 0;
  this.touchup = 0;
  this.currentEvents =  

                [mouseDown,mouseXY,touchDown,touchXY,touchUp,mouseUp,touchUp];
  this.canX;
  this.canY;
  this.canvas;
}

EventManager.prototype.addEventsListeners = function(){
  this.canvas.addEventListener("mousedown", this.currentEvents[0], true);
  this.canvas.addEventListener("mousemove", this.currentEvents[1], false);
  this.canvas.addEventListener("touchstart", this.currentEvents[2], false);
  this.canvas.addEventListener("touchmove", this.currentEvents[3], true);
  this.canvas.addEventListener("touchend", this.currentEvents[4], false);
  document.body.addEventListener("mouseup", this.currentEvents[5], false);
}

EventManager.prototype.removeEventsListeners = function(){
  this.canvas.removeEventListener("mousedown", this.currentEvents[0], false);
  this.canvas.removeEventListener("mousemove", this.currentEvents[1], false);
  this.canvas.removeEventListener("touchstart", this.currentEvents[2], false);
  this.canvas.removeEventListener("touchmove", this.currentEvents[3], true);
  this.canvas.removeEventListener("touchend", this.currentEvents[4], false);
  document.body.removeEventListener("mouseup", this.currentEvents[5], false);
}

EventManager.prototype.isTouchDown = function(){
  return (this.touchdown == 1);
}

EventManager.prototype.isTouchUp = function(){
  if(this.touchup == 0) return false;
  else{ this.touchup = 0; return true; }
}

/*************EVENT LISTENERS***************/
function mouseUp() {
  EM.touchup = 1;
  EM.touchdown = 0;
  mouseXY();
}

function touchUp() {
  EM.touchup = 1;
  EM.touchdown = 0;
  touchXY();
}

function mouseDown() {
  EM.touchup = 0;
  EM.touchdown = 1;
  mouseXY();
}

function touchDown() {
  EM.touchup = 0;
  EM.touchdown = 1;
  touchXY();
}

function mouseXY(e) {
  EM.canX = e.pageX - EM.canvas.offsetLeft;
  EM.canY = e.pageY - EM.canvas.offsetTop;
}

function touchXY(e) {
  e.preventDefault();
  EM.canX = e.targetTouches[0].pageX - EM.canvas.offsetLeft;
  EM.canY = e.targetTouches[0].pageY - EM.canvas.offsetTop;
}

var EM = new EventManager();


Fijaros que al final incluyo la creación de la instancia EM!

Ejemplo de utilización

Lo más importante es que durante la fase de inicialización del juego configuremos la instancia correspondiente del elemento canvas y activemos el registro de los event listeners.

function init() {
  can = document.getElementById("c");
  ctx = can.getContext("2d");
  can.width = 400;
  can.height= 350;
  EM.canvas = can;
  EM.addEventsListeners();

  setInterval(gameLoop, 1000/15);
}


Una vez configurado el objeto EM y activados los event listeners, ya podemos en nuestro bucle de juego capturar los eventos:

if(EM.isTouchUp()) {
  alert(EM.canX + " , " + EM.canY);
}


El código anterior, comprueba si se acaba de hacer click sobre el canvas y nos muestra un alert con las coordenadas donde se hizo click.
Del mismo modo haríamos lo mismo para detectar si se está pulsando el canvas, empleando isTouchDown().


Eso es todo! Ha sido un tutorial realmente largo, espero que todo haya quedado claro.
Esta implementación pertenece a la librería que he desarrollado y espero que os sea útil o que os de ideas para gestionar vuestros eventos sobre el canvas.

La clase es susceptible de muchas ampliaciones, como por ejemplo el soporte para pantallas multitouch, o el guardado de una secuencia de coordenadas para implementar caminos o gestos, podéis extenderla y adaptarla a vuestras necesidades!

Un saludete!!

1 comentario: