jueves, 6 de octubre de 2011

La vuelta del javascript y el inicio de un pequeño juego para mis pequeñajos

Ver artículo siguiente de esta serie
Antes de nada, el ejemplo en ejecución lo puedes ver pulsando el siguiente enlace (subido siguiendo estas indicaciones: ¿Cómo subir a Google Code?):

Aunque llevo ya bastante tiempo haciendo aplicaciones web lo cierto es que tengo un poco abandonado el mundo del javascript. Últimamente sólo me acerco a él salvo para escribir alguna que otra validación o para responder a algún evento dentro de un formulario. Ni siquiera lo he tenido muy presente con la llegada de AJAX,  y de las librerías JQuery, Propotype, etc. Digamos que en las aplicaciones en las que yo me he movido, aplicaciones de gestión, apenas si tenían cabida ya que los criterios de accesibilidad imponen el funcionamiento sin el javascript activado y las espectacularidades dinámicas, modificando el DOM de las páginas, no son bien recibidas (si al día debes rellenar 50 largos formularios en tu aplicación de gestión no es muy agradable ver como entre uno y otro se suceden maravillas muy vistosas).
Por otro lado y habiendo pasado por muchos lenguajes de programación, desde NATURAL hasta Java, siempre he pensado que JavaScript (o el antiguo JScript), eran lenguajes como de juguete, enormemente difíciles de depurar y frustrantes por las distintas “implementaciones” de cada uno de los navegadores.
Ahora, con la llegada del HTML 5, parece ser que se va a convertir en el lenguaje predominante, ocupando, posiblemente, el lugar de Flash (Sirverlight, Flex, Java Swing,  JavaFx, etc.) por lo que nos va a tocar ponernos las pilas con él e ir trabajando un poco con el nuevo elemento canvas que, entre otras cosas, nos promete un estándar “para unirlos a todos” .
¿Por dónde empezar? Una buena opción, en mi caso, es intentar desarrollar un jueguecillo didáctico para mis peques y ver hasta donde llego.

La parte gráfica.
Aquí lo tengo complicado ya que no soy nada ducho con esto. He seguido algún tutorial de pixel art en algún momento pero es muy, muy trabajoso y hay que tener madera. Viendo la estética “flash” que predomina últimamente he probado con el dibujo vectorial y va a ser por ahí por donde pienso tirar.
Primero: un dibujo a mano escaneado que llevamos a Adobe Illustrator.
 Segundo: Mi primer dibujo con Illustrator. Vamos dibujando con la herramienta pluma y rellenamos de color (en youtube hay muchos tutoriales para ver cómo hacer esto).
 Tercero: A dibujar. Aunque es muy trabajoso, teniendo en cuenta que no me considero nada artista, creo que el resultado, con esta herramienta, es bastante decente.

El html para la primera animación (y cómo).
He revisado varios libros sobre juegos con javascript. Parece ser que aquí no hay ninguna necesidad de implementar un segundo buffer para crear la imagen fuera de la pantalla y luego volcarla sobre ella una vez que tengamos todo dibujado (de este modo se evita que se vea cómo se van dibujando las cosas, parpadeos y cosas así). Esto es así porque ya se encargan los navegadores de volcar la imagen sólo cuando todo esta dibujado. No obstante siempre hay lugar a la optimización y algo que parece ser bastante interesante en poner el fondo en un canvas con un z-index inferior, el movimiento del personaje en un canvas para él, con un z-index intermedio, y otro, con el z-index más grande, para un plano todavía más cercano a nosotros. De este modo evitamos el tener que dibujar continuamente, en nuestro código, esas imágenes.

<!DOCTYPE html>
<html>
    <head>
        <title>Prueba</title>
        <meta charset="utf-8">
        <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
        <script type="text/javascript" src="movimiento4.js"></script>
    </head>
    <body>
        <canvas id="canvasBack" width="800" height="400" style="position:absolute;left:10px;top:10px;z-index:1;background-image:url('imagenes/fondo.png');">
            Su navegador no puede ejecutar este contenido. ¡Actualicelo!.
        </canvas>
        <canvas id="canvasMov" width="800" height="400" style="position:absolute;left:10px;top:10px;z-index:10;">
            Su navegador no puede ejecutar este contenido. ¡Actualicelo!.
        </canvas>
        <canvas id="canvasForegr" width="800" height="400" style="position:absolute;left:10px;top:10px;z-index:20;background-image:url('imagenes/foreground.png');">
            Su navegador no puede ejecutar este contenido. ¡Actualicelo!.
        </canvas>
    </body>
</html>

El Javascript.
Lo primero aquí es hacerse con alguna herramienta para la edición. Por el momento estoy probando con Aptana Studio 3 (montado sobre eclipse que, según mi opinión, no tiene quien le “eclipse”. NetBeans se le queda lejos).
Para las pruebas me he descargado una de las últimas versiones de Firefox (tengo Opera, Chrome, y Safari instalados pero IE 9 ¡NO!, de momento). ¿Por qué Firefox? Pues simplemente por su consola y por el plugin FireBug que nos permite depurar  ese endemoniado javascript. Una pantallazo con ambos abiertos y con un punto de ruptura activado (menú desarrollador web):
Como he comentado antes no recuerdo haber escrito algo orientado a objetos utilizando javascript. En este caso si el proyecto se hace grande conviene intentar programar en condiciones. Siendo Java lo que más he utilizado debo decir que escribir el pequeño código siguiente ha sido, para mí, un pequeño sufrimiento. ¿Cuáles son las razones? Primero, ¿cómo escribo las clases? “Googleando” por ahí veras que existe la opción de declarar e instanciar los objetos al mismo tiempo (método declarativo), que existe la posibilidad de utilizar “new”, que existe la posibilidad de utilizar el prototipado para extender las clases, que incluso existen librerías, como prototipe.js, para hacer esto y muchas otras opciones complejas para, simplemente, definir una clase tonta, definir métodos, aplicar herencia, y proteger tus atributos (esto último lo descarto totalmente con este lenguaje). Segundo, el uso de “this”. Como digo, viniendo de java, el “this” de javascript carece de sentido para mí y más cuando ves que lo tienes que poner en todos los sitios y hacer cosas como declarar “var that = this” para no perder el objeto al que quieres referenciar. Parece que el interprete javascript no sabe nunca donde está y nos toca recordárselo. Tercero, la declaración al vuelo de las variables por parte de este lenguaje. ¿De verdad sería tan difícil producir un error cuando el intérprete encuentra una variable no declarada en lugar de crearla? ¡¿para qué?¡. Cuarto, la función setInterval, aunque le pase el objeto al que tiene que llamar setInterval(principal.drawFrame(), 60) no funciona y requiere hacer setInterval(function() {principal.drawFrame()}, 60); para lo que no tengo palabras.
Aquí va el código, sufrido (y mira que es corto), para la animación:

function Sprite(img_file, anchoFrame, altoFrame, posXInicial, posYInicial) {
    var that = this;
    this.imagen = new Image();
    this.imagen.onload = function(){
        that.is_ready = true;
    }
    this.imagen.src = img_file;
    this.posXInicial = posXInicial;
    this.posX = posXInicial;
    this.posY = posYInicial;
    this.alto = altoFrame;
    this.ancho = anchoFrame;
    this.frame = 0;       
    this.drawImage = function(contexto) {
        if(this.is_ready){
            contexto.drawImage(this.imagen, this.frame * this.ancho, 0, this.ancho, this.alto, this.posX, this.posY, this.ancho, this.alto);
            this.frame = (this.frame + 1)%3;               
            this.posX = this.posX - 6;
            if (this.posX < 0)
                this.posX = this.posXInicial;                   
        }
    }   
}

function Principal(pcanvas) {
    this.canvas = pcanvas;
    this.contexto = pcanvas.getContext("2d");
    this.ancho = pcanvas.width;
    this.alto = pcanvas.height;
    this.sprite = new Sprite("imagenes/prota_paso.png", 64, 118, 800, 270);   
    this.drawFrame = function(){
        this.contexto.clearRect(0,0,this.ancho,this.alto);
        this.sprite.drawImage(this.contexto);
    }
}

window.onload = function() {
    var principal = new Principal(document.getElementById("canvasMov"));
    setInterval(function(){principal.drawFrame()}, 60);
}
 
Como se puede ver el movimiento no es nada suave. He probado utilizando un canvas propio para el sprite y luego dibujándolo sobre el principal tal y como hacen en este ejemplo (demostración). He probado a utilizar un html con un único canvas y dibujando el fondo directamente desde javascript. He probado utilizando fillrect en lugar de clearRect. En todos los casos el resultado es el mismo y estoy seguro de que el problema está en el número de sprites para la animación y en su calidad ya que como se aprecia en el ejemplo enlazado anteriormente las cosas deberían ir suaves sin optimizar demasiado. Estas son las imágenes que utilizo y que me tocará mejorar:


Algunas notas:

Posiblemente, en el javascript, sea más correcto utilizar la función ready de JQuery en lugar de window.onload pero por simplicidad prefiero dejarlo así, además en este caso se espera a la carga de la imagen antes de dibujar.

Podríamos utilizar JQuery, por ejemplo, para acceder al canvas, de este modo:
var canvas = $(“#canvasMov”);
var contexto = this.canvas.get(0).getContext(“2d”);
var ancho = this.canvas.width();
En cambio directamente con javascript:
    var canvas = document.getElementById(“canvasMov”);
    var contexto = this.canvas.getContext(“2d”);
    var ancho = this.canvas.width;
Con JQuery los atributos se convierten en métodos y hay objetos dentro de arrays. No veo aquí el beneficio de JQuery. Imagino que su potencia estará en el caso de realizar llamadas con el XMLHttpRequest o bien cuando se haga un uso exhaustivo del dom de la página aunque esto es algo que tampoco termino de ver muy claro (salvo, por el hecho de que cada navegador se comporte de una manera distinta o haya algún “bug” en alguno de ellos que JQuery nos evite).

Intentaba mejorar mi código con por ejemplo un objeto imagen que me indicase cuando estaba cargada. He probado varias cosas del tipo:
1.
function ImageReady(url) {
this.isready = false;
var that = this;
this.onload = function() {
that.isready = true;
}
this.src = url;
}
ImageReady.prototype = new Image(); // Para extender de Image
var imagen = new ImageReady(“imagenes/prota.png”);

2. Ampliando el prototipo:
Image.prototype.isready=false;
Image.prototype.onload = function() {
this.isready = true;
}
var imagen = new Image();
imagen.src = “imagenes/prota.png”;
El resultado ha sido que no he sido capaz de crear un nuevo objeto genérico que sepa cuándo se ha terminado de cargar. “Googleando”, de nuevo, parece que más de uno se ha dado contra esta misma pared. Este lenguaje se impondrá pero sigo diciendo que no es nada amistoso.


Ver artículo siguiente de esta serie

No hay comentarios:

Publicar un comentario