sábado, 31 de diciembre de 2011

Colisiones y sonido

Ver artículo anterior de esta serie
Puff, poco tiempo para estas cosas y con el peque ya dándole a la psp y a la ps3. Llego tarde.
Dejo aquí un ejemplillo sobre cómo montar las colisiones (muy burdamente pero suficientes si se ajustasen mejor los rectángulos) y sobre cómo incluir los sonidos. Algo curioso es que si un sonido esta en uso hay que esperar a que termine antes de que se puede volver a utilizar. Para resolver esto hay que tener en el código distintas variables apuntando al mismo sonido. En el caso de las flechas tengo dos variables así que si uno esta sonando se utiliza el otro. Si se dispara muy rápido serian necesarias más variables.
Para probar esto lo mejor es ir a la segunda pantalla y dedicarse a cazar al pájaro. Una vez que se le ha dado con una flecha, cambia el estado del sprite para que este caía hasta donde corresponda en función de la pantalla, al llegar al suelo se convierte en "premio" y cuando el soldado colisiona con el premio se lo lleva.
El ejemplo:

jueves, 3 de noviembre de 2011

Un arco sin caza que cazar :(

Ver el artículo anterior de esta serie.
El ejemplo:

Esto me ha llevado un rato. De nuevo si lo hubiese pensado antes no me habría liado con la parte de apuntar con el arco. ¡Sir Arthur siempre ha tirado los cuchillos “pa lante”!.
Ahora podemos disparar pero nada de tiros parabólicos, hazte a la idea de que la flecha cae después de que sale de la pantalla o de que tienes una fuerza increíble.
Aunque he revisado el código se me ha guarreado un poco. La trayectoria de las flechas es, cómo no, y=mx+b, donde m es la pendiente de la recta, m=tangente del ángulo, y b es el punto de corte con el eje y.
En función de la escala del prota (si te vas a la tercera pantalla es más pequeño) y en función del ancho del sprite o de hacia dónde apunta hay que echar una serie de cálculos en los que no me he parado demasiado. He probado utilizando la intuición y me veo, cuando intente tratar las colisiones con objetos (siguiente paso), revisando todo el código y parándome a pensar, lápiz en mano, las trayectorias y demás. El hecho de que el eje “y” esté invertido en el canvas y de que yo no tenga muy claro hacia dónde van los ángulos complica las cosas y más si añadimos que he modificado el mapa de los sprites para que sólo aparezcan los que miran hacia la izquierda y debo, entonces, girarlos para tratar el caso de los que miran a la derecha:


Por cierto, las flechas van por el canvas intermedio y, por ejemplo, en el dibujo de arriba, si no pongo algo en el primer plano van por delante del desfiladero, ¡un efecto a lo Michael Escher!

miércoles, 26 de octubre de 2011

Subir escaleras molara pero disparar un arco muchísimo más.

Ver el artículo siguiente de esta serie.
Ver el artículo anterior de esta serie.
Primera prueba para el disparo con el arco (Sí ya sé que estas son horas para darle al Batman Arkham City pero el deber es el deber y, además, el peque se ha puesto a darle al Braid y aunque es “mu” complicado para cuando esté esto ya se es capaz de pasarse el Halo sin problemas).
La prueba es muy sencilla, una pequeña animación para ver cómo se va a mover el arco a la hora de apuntar. El mapa del “sprite” es el siguiente:
 
Primero dibujamos el brazo de atrás, girando el contexto el ángulo adecuado, luego pintamos el cuerpo del muñeco (casi a lo Johnny cogió su fusil) y, finalmente, encima de lo dibujado, volvemos a girar el contexto y pintamos el otro brazo y el arco.

El increíble hombre menguante – I.

Ver artículo siguiente de esta serie.
Ver artículo anterior de esta serie.
Lo del “I” en el título es porque después de ver el ejemplo siguiente está claro que tiene que haber un “II” para continuar con este tema y corregirlo:


Este es el primer intento para ir integrando cosas. Principalmente el cambio de pantallas y el cambio en la escala (dos “sprites”, pequeño y grande).
Poco a poco voy definiendo variables globales que se van utilizando en la mayoría de los scripts. Las más destacadas son un el “sprite”, personaje pequeño, el grande y la pantalla actual en la que tenemos definidos los puntos iniciales, finales, el recorrido posible, de dónde venimos y a donde vamos.
Uno de los cambios más destacados es “el bucle principal del juego” que se intenta ejecutar a razón de 30 frames por segundo:

this.actualizarJuego = function() {
        this.sprite.actualizarEstado();
        //
        var siguientePantalla = pantalla.hayQueCambiarDePantalla(this.sprite.posX, this.sprite.posY);
        if (siguientePantalla != 0) {
            var numPantalla = pantalla.numero;
            pantalla = getPantalla(siguientePantalla);
            if (pantalla.escalaReducida)
                this.sprite = spriteEscalaReducida;
            else
                this.sprite = spriteEscalaNormal;
            if (numPantalla < pantalla.numero)   
                this.sprite.setPosicion(pantalla.getPosicionInicial());
            else
                this.sprite.setPosicion(pantalla.getPosicionFinal());
        }
        //                       
        this.drawFrame();
}

Aquí:
  • Actualizamos el estado del sprite (si hay una tecla pulsada se actualiza la posición en función de ella si es que se puede).  
  • Comprobamos si hay que cambiar de pantalla y si es así colocamos el sprite (pequeño o grande, dependiendo de la pantalla) en su posición inicial o final si venimos de la anterior o la siguiente, respectivamente.
  • Dibujamos todo.
El otro cambio destacable es la inclusión de una función “cargarImagenesPantalla” en el script:
Esta función es la encargada de cambiar las imágenes en función de la pantalla a mostrar. Primero pondrá una ventanita de “cargando …”  y cuando se han cargado las imágenes del fondo y del primer plano (si existe ésta) las mostrará. Aquí tenemos dos problemas que creo que son sencillos de resolver:
  • Cuando la pantalla se está cargando si seguimos pulsando las teclas el muñeco se sigue moviendo y cuando se muestran el muñeco no está al principio. Esto se corrige no aceptando la pulsación de las teclas si estamos cargando.
  • La carga de las pantallas va lenta. Esto se debe corregir cargando previamente las imágenes tanto de la pantalla anterior como de la siguiente al llegar a una determinada. El navegador debería “cachear” (guardar temporalmente) la imagen y al necesitarla ya la deberíamos tener disponible en local (directorio temporal del disco duro).

martes, 25 de octubre de 2011

Controlando al personajillo II.

A un árbol me subí
donde manzanas había.
Si manzanas no comí
y manzanas no dejé...
¿cuántas manzanas había?.

El ejemplo:

Mi idea inicial era que el personaje consiguiese, en un momento determinado del juego, una cuerda que podría lanzar para poder subir y bajar. He programado el lanzamiento de la cuerda pero al ir a integrarlo con el movimiento del muñeco he visto que se complicaba mucho. ¿Cuándo recoger la cuerda? ¿Difícil acertar para que se enganche? ¿Hacia dónde está mirando el muñeco?. En definitiva, ¿qué pasa, si como en la mayoría de los juegos, dejo las cuerdas ya colocadas?. Así que descartado el lanzamiento y cuerdas colocadas. ¿Qué pasa con las cuerdas? ¿Por qué los juegos utilizan escaleras? Si engancho las cuerdas en los troncos de arriba el muñeco al subir quedará en el aire, subiendo sin cuerda, ya que no tiene sentido que las cuerdas sobresalgan por encima de los troncos. Para resolver esto nada más sencillo que poner escaleras en lugar de cuerdas ya que las escaleras sí sobresalen. En muchos juegos las escaleras se ven de frente. Después de programar esta parte está claro que esa solución es muchísimo más sencilla ya que no hay que tener en cuenta hacia qué lado mira el personaje y no hay que tener más que una animación con el muñeco de espaldas. En mi caso he dibujado la animación de subir y bajar en la escalera de la derecha (con el personaje mirando hacia la izquierda). Así las manos coinciden con la escalera. Al reflejar el personaje para que suba por el otro lado las manos ya no quedan en la escalera, si no que vuelan. Debería retocar las animaciones, cosa que no hubiera sucedido con la escalera de frente. Además si hubiésemos descartado la poca profundidad que tiene mi escenario también nos hubiésemos evitado el tener que estar mirando qué queda delante y qué queda detrás. Necesitamos poner un plano en el frente para ocultar las manos cuando se sube por la escalera detrás de los troncos.
Para manejar por donde se mueve el personaje y para definir la pantalla he creado el javascript pantalla.js. Se crean segmentos por donde se puede mover el personaje, fuera de ellos no se permite el movimiento (hay que llegar al final del segmento para poder cambiar de dirección, quizás sea necesaria una pequeña ayuda aquí). Además los segmentos verticales se marcan para saber si el personaje debe mirar  hacia la derecha o hacia la izquierda. Esta definición es automática y sólo hay que proporcionar los puntos que unen los segmentos de izquierda a derecha. En el caso de esta pantalla, el recorrido se define como:
Pantalla([0,255,180,255,180,54,546,54,546,255,736,255]); 

El recorrido a realizar es el mostrado en negro pero teniendo en cuenta la posición y el tamaño de las imágenes del sprite y que se su posición es la esquina superior izquierda, nos queda el recorrido mostrado en rojo.
Además dentro de la Pantalla hemos metido un par de métodos que nos indicarán si se llega al final del recorrido (siguiente pantalla) o si se va al inicio (pantalla anterior).
He ampliado el script “listeners” para tener en cuenta las teclas para subir y bajar. Aunque por el momento las teclas son las flechas tendré que cambiarlas ya que se utilizan para mover el scroll y si éste existe es un problema (ahora lo eliminado impidiendo en el html que aparezca).
El código de sprite.js también lo he modificado adaptándolo a los nuevos movimientos y “generalizándolo” para sprites que se mueven con el teclado en todas direcciones (¡sólo uno!).
¿El tamaño “de la cámara”? No tengo muy claro qué tamaño del personaje elegir. En las pantallas donde va haber conversación con otros personajes lo ideal es lo mostrado en los primeros ejemplos, de cerca, pero para otras pantallas, como la primera de este ejemplo, da mucho más juego poner “la cámara” más alejada. Esto nos obliga a meter más imágenes con distintos tamaños o a escalarlas en el canvas. Otra opción podría ser elegir un tamaño intermedio pero, a mí, el cambio de escala me resulta más atractivo de cara al peque.

martes, 18 de octubre de 2011

Apache POI en Apache Tapestry.

Apache POI (http://poi.apache.org/) es un proyecto de Apache que nos permite trabajar, desde Java, con los documentos “office” de Microsoft. Tiene varias “sub-apis”, por un lado las que trabajan con los formatos antiguos de office (97-2007) y por otro las que trabajan con los nuevos formatos basados en xml. En este caso vamos a ver un ejemplo, muy por encima, de generación de un documento Excell utilizando el formato antiguo y cómo mostrarlo con Tapestry. El paquete a utilizar es org.apache.poi.hssf que tiene como descripción “Horrible SpreadSheet Format API's for reading/writting Excel files using pure Java”.
Empezamos viendo cómo generar el documento siguiente:

Dentro de nuestra página en Tapestry colocamos un par de enlaces para la generación de los documentos Excell:
El código, dentro del archivo “.tml”, para generar los enlaces:

<t:actionlink t:id="obtenerSabanaEnExcel" style="margin-right:10px;">
      <img src="${context:imagenes/excel.png}"/>
      Sábana
</t:actionlink>
<t:actionlink t:id="excelParaMoodle" style="margin-right:10px;">
      <img src="${context:imagenes/excelparamoodle.png}"/>
      Excel para Actas en Moodle
</t:actionlink>

El código, dentro de la clase Java asociada con la página, que se ejecuta al pulsar sobre obtenerSabanaEnExcell (onActionFrom):

StreamResponse onActionFromObtenerSabanaEnExcel() throws Exception {
      getAlumnos();
      SabanaAsignatura sabanaAsignatura = new SabanaAsignatura(getCodigoAsignatura(), getSistema(),                         alumnos, parteExamenDAO, resultadoExamenDAO, getUsuario().getCursoAcademico());
      GeneradorSabanaExcelCompleta generador = new
                  GeneradorSabanaExcelCompleta(getNombreAsignatura(), sabanaAsignatura);
      InputStream is = generador.generarSabana(getSistema());
      return new ExcelStreamResponse(is, "Resultados");           
}

Donde lo único que hacemos es cargar los datos para la generación del Excell, generarlo y devolver un objeto del tipo ExcelStreamResponse con el InputStream y que se llamará “Resultados.xls” en el cliente.
La clase ExcelStreamResponse es utilizada por Tapestry para hacer saber al navegador el tipo de contenido (en este caso un documento Excell de Microsoft) que le va a ser servido:

public class ExcelStreamResponse implements StreamResponse {
    private InputStream is;
    private String filename="default";
    public ExcelStreamResponse(InputStream is, String... args) {
        this.is = is;
        if (args != null) {
            this.filename = args[0];
        }
    }
    public String getContentType() {
        return "application/vnd.ms-excel";
    }
    public InputStream getStream() throws IOException {
        return is;
    }
    public void prepareResponse(Response arg0) {
        arg0.setHeader("Content-Disposition", "attachment; filename=" + filename + ".xls");
    }
}

Dentro de la clase GeneradorSabanaExcelCompleta se crea la hoja (u hojas) Excell.

      HSSFWorkbook libro = new HSSFWorkbook();
      hoja = libro.createSheet(nombreAsignatura);

Se crean las filas:

      HSSFRow fila0 = hoja.createRow(0);
      fila0.setHeightInPoints(19f);

Se crean los estilos:

      Font fuenteBlanca;
      fuenteBlanca = libro.createFont();
      fuenteBlanca.setColor(IndexedColors.WHITE.getIndex());
      //
     CellStyle estiloGris = libro.createCellStyle();
     estiloGris.setAlignment(CellStyle.ALIGN_CENTER);
     estiloGris.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
     estiloGris.setFillPattern(CellStyle.SOLID_FOREGROUND);
     estiloGris.setFillForegroundColor(IndexedColors.GREY_40_PERCENT.getIndex());
     estiloGris.setFont(fuenteBlanca);

Y se crean y se rellenan las celdas con los distintos estilos:

      HSSFCell celdaAsignatura = fila0.createCell(0);
      celdaAsignatura.setCellValue(nombreAsignatura);
      celdaAsignatura.setCellStyle(estiloGrisOscuro);

Finalmente, devolvemos el InputStream:

      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      libro.write(baos);
      baos.close();
      return new ByteArrayInputStream(baos.toByteArray());

Utilización de plantillas.

Apache POI también nos permite utilizar plantillas (vale un xls sin rellenar) con lo que nuestro código se reduce bastante. Un ejemplo completo:

public static InputStream generarControlDeAsistencia(File plantilla, String cursoAcademico, String curso, int                                                                               grupo, List<String> alumnos) throws Exception {       
        FileInputStream fis = new FileInputStream(plantilla);
        HSSFWorkbook libro = new HSSFWorkbook(fis);
        HSSFSheet hoja = libro.getSheetAt(0);
        HSSFRow fila = hoja.getRow(1);
        HSSFCell celda = fila.getCell(5);
        celda.setCellValue(cursoAcademico);
        celda = fila.getCell(8);
        celda.setCellValue(curso);
        celda = fila.getCell(11);
        celda.setCellValue(grupo);
        int totalFilas = Math.min(alumnos.size(), 36);
        for (int i=0; i<totalFilas; i++) {
            fila = hoja.getRow(7 + i);
            celda = fila.getCell(1);
            celda.setCellValue(alumnos.get(i));
        }
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        libro.write(baos);
        baos.close();
        return new ByteArrayInputStream(baos.toByteArray());
}

Controlando al personajillo.

Ver artículo siguiente de esta serie.
Ver artículo anterior de esta serie.
Antes de nada el enlace al ejemplo (parece que funciona en Firefox, Opera, Safari y Chrome. Con IE 9 no he probado):


He retocado un poco el html (habrá que ir haciendo  hueco para la publicidad) y he incluido un javascript para controlar el personaje tanto con teclas como con un par de botones. El javascript es muy sencillo. Nos quedamos con la dirección del último botón presionado o la última tecla presionada. Si se deja de pulsar la tecla o el botón el personaje se detendrá y se colocará de frente. La parte destacada y que parece funcionar en todos los navegadores (el documento html debe tener el foco) es:

document.onkeydown = function(e){
    e = e?e:window.event;
    switch (e.keyCode){
        case 37:
                teclaPulsada = IZQUIERDA;
            break;
        case 39:
                teclaPulsada = DERECHA;
            break;
    }
}

Donde al presionar cualquier tecla se ejecuta la función y donde se utiliza el operador ternario (que aborrezco) y que se debería sustituir por:

if (e == null)
    e = window.event;

ya que en el caso del IE, parece ser, el evento que se produce llega a nulo.

Aquí dejo la imagen utilizada (teniendo en cuenta el tamaño de los ojos estoy haciendo trampa al darle la vuelta a las imágenes). Según el peque el personaje debería poder agacharse por si alguien le lanza algo (un día le enseñaré a Sir Arthur y el Ghost and Goblins) pero es que (para ganarnos a las madres) el juego debe ser poco violento (aunque para ganarnos al peque eso funcione peor) y a lo sumo le dejaré (en esta primera versión) cazar pajarracos con el arco (en la siguiente debería caer hasta el apuntador). Todavia me queda hacerle subir y bajar por una cuerda (y, algo que de momento abandono, el que el personaje porte, visualmente, el arco, la cuerda, las flechas y el carcaj).

Un pajarraco.

Ver artículo siguiente de esta serie.
Ver artículo anterior de esta serie.
Al final lo del mar, aunque al peque no le disgustó, va a ser que no. Cambiamos por el pajarraco siguiente:
La animación aquí:

jueves, 13 de octubre de 2011

Un buen tutorial de Pixel Art

Hace tiempo estuve buscando algún buen tutorial sobre pixel art. He leído y he puesto varios en práctica. El mejor, para mí, por lo sencillo y claro que resulta es uno que debe llevar bastante tiempo colgado en la red. El tutorial de Derek Yu que podéis ver en:
http://www.derekyu.com/?page_id=229
Allí te muestra cómo sonseguir el siguiente pixel art paso a paso:
En mi caso el resultado ha sido este (más de lo que me esperaba):
y

Validar un usuario contra un directorio activo de Windows Server 2003 (¿o 2008?).

Cuando Sun era Sun no paraba de encontrar cien mil códigos útiles y respuestas a casi todos mis problemas con Java en sus foros. Hoy en día, reconvertidos a foros de Oracle, no soy capaz de encontrar ni una sola respuesta a ni una sola de mis preguntas. No sé qué habrán hecho pero tela con la compañía de la base de datos, "en casa de herrero ...".
Antes de eso, por suerte, debí conseguir el siguiente código para validar un usuario, desde Java, contra un Directorio Activo de un Windows Server 2003. Realmente en mi entorno no sé si es un 2003 o un 2008 pero funciona perfectamente. Dejo aquí la versión del código que utilizo en mi Index.java (Acceso seguro a nuestra aplicación con Tapestry y Tomcat 6. Perfilado de páginas.) y debajo la explicación que ofrecía el usuario:

private boolean validarEnElLdap(String usuario, String clave) throws Exception {
            boolean autenticadoEnElLDap = true;
            Hashtable<String, String> env = new Hashtable<String, String>();
            env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
            env.put(Context.SECURITY_AUTHENTICATION, "simple");
            env.put(Context.PROVIDER_URL, "ldap://servidor_ldap:puerto_ldap");
            Control[] connCtls = new Control[] {new FastBindConnectionControl()};
            env.put(Context.SECURITY_PRINCIPAL, usuario);
            env.put(Context.SECURITY_CREDENTIALS, clave);     
            try {
                LdapContext ctx = new InitialLdapContext(env, connCtls);
                ctx.close();
            }
            catch(AuthenticationException ignorada) {   
                autenticadoEnElLDap = false;
            }
            return autenticadoEnElLDap;
        }
               
        class FastBindConnectionControl implements Control {
            public byte[] getEncodedValue() {
                    return null;
            }
              public String getID() {
                return "1.2.840.113556.1.4.1781";
            }
             public boolean isCritical() {
                return true;
         }
}

Aquí, la explicación, del usuario de los foros de Sun "adler_steven", el  11 de Abril del 2006.
Many developers attempt to use LDAP Directories as an authentication service. While LDAP is a directory protocol primarily designed to search, add, delete and modify entries stored in the directory, implicit in the protocol is the ability to authenticate LDAP connections using a variety of authentication mechanisms.
For example Active Directory supports simple (clear text), HTTP-DIGEST, X.509 Client Certificates and Kerberos (via GSS-API). For details of these mechanisms, refer to other postings in this forum; JNDI, Active Directory and Authentication (parts 1 - 4)
When using a LDAP Directory as a simple authentication service, the typical approach is to gather a user's credentials (username & password) and verify these against the username and password values stored in the directory.
For obvious security reasons Active Directory does not permit read operations against the Windows password attribute( unicodePassword), thereby preventing an attacker from retrieving the password and attempting to crack the password offline. Therefore the only way to verify a user's credentials, is to actually perform a LDAP bind.
Ordinarily when Active Directory authenticates a user, it assembles all of the authorization data and builds a Windows security token containing all of the user's security identifiers (group membership, privileges etc.). While this is appropriate for authenticating user's into a Windows network, it may incur additional performance overhead and may not be appropriate for many Intranet or Extranet application scenarios, where all that is required is a simple verification of a user's name & password.
In order to support this simple scenario, Windows Server 2003 introduced a LDAP Connection Control that does not incur the overhead of assembling all of the user's Windows authorization information during the LDAP bind operation. This control is described at
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ldap/ldap/ldap_server_fast_bind_oid.asp
To use the Active Directory LDAP Fast Bind Control in Java & JNDI, simply request the control during the connection request. In this example of a server side of the application, the LdapContext is initialized with the connection control and subsequent authentication checks are performed by invoking the Context.reconnect method.

Más animaciones.

Ver artículo anterior de esta serie.
Ver artículo siguiente de esta serie.
Un par de animaciones que ya querrían los de Naugthy Dog para el Uncharted 3:
http://bajoeltejadodezic.googlecode.com/svn/trunk/ejemplo4/mar3.html
http://bajoeltejadodezic.googlecode.com/svn/trunk/ejemplo4/mar5.html
Bueno, realmente no, pero tampoco es que les haya dedicado mucho tiempo. Vale, como diria mi papi, son toda una castaña. Y sí, es más que probable que el mar se convierta en un precipicio si se sigue comportando así.
Dentro del juego estará bien meter alguna animación simplemente para que resulte más atractivo, un pájaro que vuela, una hoja que cae. De momento, utilizaré esta del mar para ver cómo pegar este tipo de animaciones con el resto del juego.

Acceso seguro a nuestra aplicación con Tapestry y Tomcat 6. Perfilado de páginas.

Al acceder a nuestra aplicación se deben proporcionar el usuario y la clave. Queremos que la clave del usuario viaje por la red encriptada de forma que si alguien tuviese acceso a ella no pueda utilizarla. Para ello crearemos un certificado, que instalaremos en nuestro servidor, que permitirá que el navegador y el servidor se comuniquen por un canal seguro. Este certificado, al ser creado por nosotros, no garantiza a los usuarios que seamos quienes decimos ser. Para ello es necesario que el certificado lo emita una entidad certificadora y hay que pagar por él. Obviamente si el usuario se conecta a una dirección, dominio, que él conoce como de confianza no debería necesitar nada más.

Configuración del puerto seguro en Tomcat 6.
Primero tenemos que instalar un certificado autofirmado en tomcat. Para ello, siguiendo con las instrucciones de Apache (http://tomcat.apache.org/tomcat-6.0-doc/ssl-howto.html), creamos un almacén de claves desde Java, con la herramienta keytool (que encontraremos dentro del directorio bin de la distribución de Java):
keytool -genkey -alias tomcat -keyalg RSA
Al no indicar el archivo de salida se generara el archivo en el directorio del usuario (en el caso de Windows XP, por ejemplo, “C:\Documents and Settings\[usuario que accede al equipo]). Allí veremos el archivo “.keystore”.
Como indicaba antes, puesto que este certificado esta creado por nosotros, al acceder a nuestra aplicación el navegador informara al usuario de que no puede garantizar que somos quienes decimos ser con una especie de mensaje de error. Si, nuestro usuario, confía en nosotros (debería) puede instalar nuestro certificado en el navegador y no tener que ver de nuevo este tipo de mensajes.
Es recomendable, aunque no necesario, que al crear el certificado utilicemos el nombre de nuestro dominio como nombre del certificado. Si no, como sucede en el mensaje anterior, el navegador nos indicará que el certificado tampoco coincide con el sitio (“El certificado sólo es válido para nombreyapellidos”).
A continuación debemos configurar el puerto seguro en Tomcat. Vamos a la carpeta de instalación de Tomcat y editamos el archivo server.xml del directorio conf. Encontraremos, y si no lo crearemos, un conector comentado para el puerto 8443 (o para el protocolo https en otro puerto). Introducimos la localización del almacén de claves y la clave:

<Connector port="8443" protocol="HTTP/1.1" maxThreads="200" scheme="https" secure="true" SSLEnabled="true" keystoreFile="C:/Documents and Settings/[usuario]/.keystore" keystorePass="clavealmacen" clientAuth="false" sslProtocol="TLS" />

Página de logín.
En mi caso el formulario para acceder a la aplicación no pertenece a ella si no que está ubicado en una página de la web. Para probar es suficiente con la página siguiente:

<html>
<form method="post" action="https://localhost:8443/UEM/Index">
        usuario <input type="text" id="idUsuario" name="idUsuario"/><br/>
        clave <input type="password" id="clave" name="clave"/>
        <input type="submit"/>
    </form>
</html>

En el atributo action del formulario indicamos el protocolo seguro (https), el puerto seguro que vamos a utilizar (8443), el contexto web de nuestra aplicación (UEM) y la página que recibirá nuestra petición en Tapestry (Index).

Index en Tapestry.
Tapestry requiere para cada una de sus páginas un archivo “.tml”, la página en sí, y una clase Java asociada a ella.
Nuestro Index no visualiza nada. En el caso de que el login sea correcto redirigiremos al usuario a la primera página de nuestra aplicación y en caso contrario le devolveremos a la página de login para que lo intente de nuevo.
El código de Index.tml es, simplemente:

<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
</html>

La parte del código de “Index.java” que nos interesa es la siguiente:   

@Inject
private RequestGlobals requestGlobals;
   
@SessionState(create=false)
private Usuario usuario;
       
Object onActivate() throws Exception {
        HttpServletRequest request = requestGlobals.getHTTPServletRequest();
        Usuario usuarioAutenticado = validarUsuario(request.getParameter("idUsuario"), request.getParameter("clave"));
            if (usuarioAutenticado != null) {
                usuario = usuarioAutenticado;
                HttpSession sesion = request.getSession(false);
                String idSesion = sesion.getId();
                String url =  requestGlobals.getHTTPServletResponse().encodeURL("http://localhost/ /Presentacion;jsessionid=" + idSesion);
                return new URL(url);
            }
            else
                return new URL("http://localhost/UEM/login.html");       
}
Tapestry buscará el método onActivate y lo ejecutará. Le hemos pedido, a través de la anotación @Inject, que nos proporcione acceso a la petición que hemos recibido, objeto HttpServletRequest (que Tapestry nos proporciona a través del servicio requestGlobals), y que nos proporcione un objeto de la sesión http (HttpSesion) del tipo Usuario. El usuario no está creado y le pedimos a Tapestry que no lo cree (create=false). Si el usuario y la clave no son correctos devolvemos al usuario a la página de acceso para que vuelva a introducir su usuario y su clave. Si el usuario y la clave son correctos creamos el objeto usuario y redirigimos al usuario a la primera página de nuestra aplicación (siempre es bueno sobreescribir la url con el identificador de sesión por si el usuario no tiene activadas las cooquies en su navegador). Simplemente con crear el objeto usuario (asignarlo al usuarioAutenticado del código) Tapestry lo almacena en la sesión (HttpSesion). Si sólo creamos este usuario en la página de acceso de nuestra aplicación podremos definir una clase “PaginaBase” con un código como el  siguiente:

@SessionState(create=false)
    private Usuario usuario;
Object onActivate()    throws Exception {
        if (usuario == null) return FinDeSesion.class;
        // Continuamos con la página actual
        return null;
    }

Si hacemos extender al resto de páginas de nuestra aplicación de ésta conseguiremos que automáticamente se valide si el usuario se logó o no en la aplicación cuando intente acceder a cada una de ellas. Si el usuario es nulo no se habrá logado o habrá perdido la sesión (por tiempo de inactividad) por lo que deberá volver a logarse. En caso contrario, al retornar null, Tapestry continuara con la ejecución de la página actual, sabiendo que el usuario esta validado.

Acceso a las páginas en función del perfil del usuario.
Una forma de proteger nuestras páginas en función del perfil del usuario es extender de la PaginaBase y sobreescribir o definir un nuevo método onActivate. Podemos, por ejemplo, crear  la PaginaBaseProfesor que deberán extender todas aquellas páginas a las que se puede acceder sólo si se es “Profesor” sobreescribiendo el método del siguiente modo:

@Override
    protected Object onActivate() throws Exception {
        Object pagina = super.onActivate();
        if (pagina != null)
            return pagina;
        Usuario usuario = getUsuario();
        if (!usuario.isProfesor() && !usuario.isAdministrador())
            return enviarMensaje("MSJ_NO_ACCESO");
        // Continuamos con la página
        return null;
    }

Si, además, a una página determinada se puede acceder sólo si se es docente de una determinada asignatura (parámetro que vendrá en la Request) podremos crear un nuevo onActivate en esa página que extienda de la PaginaBaseProfesor:

public Object onActivate(String codigoExamen, String codigoAsignatura) throws Exception {
        this.examen = examen;
        setCodigoAsignatura(codigoAsignatura);
        return validarDocencia();
}

Tapestry primero llamara al onActivate de la página padre, que a su vez debería llamar al de su padre, de tal modo que se verificará primero si el usuario está logado, a continuación si el usuario es un profesor o un administrador y, finalmente si el profesor es docente de esa asignatura.

martes, 11 de octubre de 2011

Una de bocadillos.

Ver artículo anterior de esta serie
Ver artículo siguiente de esta serie
El ejemplo en movimiento:
http://bajoeltejadodezic.googlecode.com/svn/trunk/bocadillos/bocadillos.html
El javascript del ejemplo:
http://bajoeltejadodezic.googlecode.com/svn/trunk/bocadillos/bocadillos.js
A lo largo del juego el protagonista tendrá contacto con otros personajes y hablará con ellos. La idea es que el dialogo se muestre a base de "bocadillos" y que exista un narrador, "ejem" (carraspeos), que los vaya leyendo.
En este ejemplo he creado un objeto “AnimaciónBocadillos” para que realice la animación y para que construya los bocadillos en función del texto que se le pase (posiblemente, aquí deberé pasar también la ubicación del archivo de audio para que se reproduzca cuando se visualiza el bocadillo).
Empecé creando una función que esperaba que fuese recursiva pero la llamada utilizando setTimeout() a una misma función pasando parámetros parece complicada (si no imposible) desde este “dichoso” lenguaje.
Con el código siguiente se crea y se inicia la animación:

var bocadillos = [
    [565,270, "Hola,;¿Estas buscando algo?", "#fff"],
    [420,280, "Sí,;busco a mis amigos.;¿Los has visto?", "#fff"],
    [565,270, "No.;¿Quieres que te ayude?", "#fff"],
    [420,280, "¡Sí!", "yellow"],
    [565,270, "Consigue lo que aparece en esta lista;y te diré donde están tus amigos.", "#fff"]
            ];
new AnimacionBocadillos(bocadillos).progresa();

En la variable bocadillos incluimos cada uno de los bocadillos de la animación, indicando el punto (x,y) donde se anclara el bocadillo, el texto que se mostrará (cada línea separada por “;”) y el color del relleno.
Al llamar al método progresa del objeto AnimacionBocadillos se iterará sobre el array, se limpiara el canvas y se irán mostrando los bocadillos.
Dentro de la función “muestraBocata” se mide el tamaño de la línea más ancha del texto, se descompone en líneas con split(“;”), y se mide el ancho con el método measureText(lineas[i]).width.
Con la altura de la fuente lo tenemos más complicado ya que no parece que exista un método estándar para obtenerla (las fuentes en la web son un quebradero de cabeza especial). En nuestro caso, como la fuente la elegimos nosotros, para calcular el alto de la línea, utilizaremos el método infalible “a capón”.
Un método curioso (¿qué remedio?), encontrado en la web, para calcular el alto de una fuente:
http://stackoverflow.com/questions/1134586/how-can-you-find-the-height-of-text-on-an-html-canvas
function measureTextHeight(ctx, left, top, width, height) {
    // Draw the text in the specified area
    ctx.save();
    ctx.translate(left, top + Math.round(height * 0.8));
    ctx.mozDrawText('gM'); // This seems like tall text...  Doesn't it?
    ctx.restore();
    // Get the pixel data from the canvas
    var data = ctx.getImageData(left, top, width, height).data,
        first = false,
        last = false
        r = height,
        c = 0;
    // Find the last line with a non-white pixel
    while(!last && r) {
        r--;
        for(c = 0; c < width; c++) {
                if(data[r * width * 4 + c * 4 + 3]) {
                        last = r;
                        break;
                }
        }
    }
    // Find the first line with a non-white pixel
    while(r) {
        r--;
        for(c = 0; c < width; c++) {
                if(data[r * width * 4 + c * 4 + 3]) {
                        first = r;
                        break;
                }
        }
        // If we've got it then return the height
        if(first != r) return last - first;
    }
    // We screwed something up...  What do you expect from free code?
    return 0;
}
// Set the font
context.mozTextStyle = '32px Arial';
// Specify a context and a rect that is safe to draw in when calling measureTextHeight
var height = measureTextHeight(context, 0, 0, 50, 50);
console.log(height);

lunes, 10 de octubre de 2011

Mejorando la animación (¡Nadie mueve los brazos tan rápido!).

Ver artículo anterior de esta serie
Ver artículo siguiente de esta serie
Bueno, según mi peque, para correr rápido hay que mover los brazos lo más rápido posible así que si le ves igual se parezca al de la animación anterior.
Esto va a ser bastante sencillo teniendo en cuenta que la animación anterior era toda una patata.
Mejoras a introducir:
  1. Más dibujos para el movimiento:
  2. Dejar de saltar del último componente de la animación al primero. Esto lo conseguimos con el código siguiente:
        this.frame = this.frame + this.incrementoFrame;
        if (this.frame > 4)
           this.incrementoFrame = -1;
        if (this.frame < 1)
           this.incrementoFrame = 1;
  3. Para que los brazos y las piernas vayan más despacio mantenemos cada dibujo de la animación durante tres “frames”. Introducimos la variable “paso” y sólo cambiamos de dibujo cuando paso toma el valor 3.
  4. Al cambiar de dibujo corregimos el desfase que existe entre la posición de los dibujos poniendo, por ejemplo, la posición de referencia de cada uno de los dibujos en el segundo pixel del casco (esto no me suena muy ortodoxo pero creo que debería ser más correcto). De este modo el casco debería avanzar siempre a la misma velocidad.
    El desfase  es de 4, 6, 1, 2, 3, 8 pixeles, respectivamente. Para no hacer que el “muñeco” avance siempre a más velocidad de la debida podemos relativizar el desfase (cambiaríamos la línea roja cuatro pixeles a la derecha) por lo que el desfase será de 0, 2, -3, -2, -1 y 4 pixeles. El código para corregir esto es:
    if (this.paso == 0)
        this.posX = this.posX - desfaseFrameRelativo[this.frame];
  5. Podemos hacer que el sprite se borre a sí mismo en lugar de borrar toda la pantalla. Esto debería aunque es más óptimo no se aprecia en la animación.
La animación con los cambios realizados se puede ver aquí:
http://bajoeltejadodezic.googlecode.com/svn/trunk/ejemplo2/movimiento2.html

Estamos pidiendo al navegador que ejecute nuestro código de dibujo a razón de 30 frames por segundo (1000 milisegundos / 30 frames por segundo = 33 milisegundos):
setInterval(function(){principal.actualizarJuego()}, 33);
Es posible que el hardware del equipo no sea capaz de realizar las llamadas a esa velocidad por lo que en los libros sobre programación de videojuegos introducen el concepto de velocidad y el del tiempo “real” transcurrido entre cada uno de los frames de modo que es posible que el “muñeco” avance un número de pixeles en un paso y otro distinto en el siguiente. Además se consigue también que independientemente de los frames por segundo en los que se ejecute nuestro código el juego se mueva a la misma velocidad en los distintos equipos por lo que siempre tendría la misma dificultad. En mi caso, “juego pa mi peque”, la dificultad, “habilidad”, no es necesaria y el hardware, capaz de ejecutar un navegado, debería ser capaz, también, de mover el juego a los frames marcados por lo que no voy a introducir esto en mi código. No obstante dejo aquí la versión con estos conceptos introducidos:
http://bajoeltejadodezic.googlecode.com/svn/trunk/ejemplo3/movimiento3.html

Ver artículo anterior de esta serie

viernes, 7 de octubre de 2011

Utilizando Gmail y el programador de tareas de Gnome para enviar mensajes, almacenados en MySQL, con JavaMail.

Para ponernos en situación, imagina que tienes una aplicación en la que deseas enviar alguna notificación, por correo electrónico, a uno de los usuarios para informarle de alguna acción realizada por otro usuario.

MySQL.
La tabla, en MySQL, que definimos es la siguiente:

Donde almacenamos el identificador del usuario que ha realizado la acción que da lugar a la notificación, el momento, el identificador del usuario al que debemos enviar el mensaje, el asunto, el texto del mensaje (en mi caso el texto esta formateado como HTML para que quede más bonito de cara al que lo recibe) y un booleano para saber si hemos enviado o no el mensaje.

Preparación del proyecto en Eclipse
En Eclipse (o cualquier otro IDE)  creamos un proyecto Java, añadimos un paquete para nuestras clases y un directorio “lib” para albergar las librerías de JavaMail y del conector Java a MySQL.
Para poder utilizar estas librerías dentro de nuestro código pulsamos con el botón derecho del ratón sobre el proyecto, seleccionamos la opción “Build Path” y la subopción “Configure Build Path …”. Aquí vamos a la pestaña “Libraries” y agregamos los jars que hemos copiado anteriormente dentro del directorio “lib”.

Código Java
Con esto ya podemos empezar a escribir el código Java.
Aquí solo pondré algún trozo del código. Las clases completas las puedes coger de aquí:
http://bajoeltejadodezic.googlecode.com/svn/trunk/notificador/notificador.zip

La clase “Notificacion” carece de explicación. Define la entidad “Notificación” con sus atributos y nos permite que el código importante sea menos farragoso.
La parte importante del código la encontramos en  la clase “Notificador”. Es una clase que se va a ejecutar con un programador de tareas que lanzará nuestro código a las 12:00 de la noche cuando se supone que todos los usuarios están “durmiendo” o al menos han dejado de trabajar con nuestra aplicación y ya todas las notificaciones a enviar están almacenadas en nuestra tabla. Además no queremos enviar 100 correos con 100 notificaciones a la misma persona por lo que leeremos la tabla y agruparemos las notificaciones en función del destinatario para sólo enviarle un correo con todas las notificaciones del día. Una vez que las notificaciones se han enviado marcaremos cada una de ellas en base de datos como enviadas para que no se vuelvan a enviar. Es muy importante saber que si el envío de correo se realiza dentro de una transacción jdbc si esta falla el correo no se enviara por lo que englobaremos el envío dentro de una transacción y así evitaremos reenviar las notificaciones si no somos capaces de marcarlas en nuestra base de datos como enviadas.
La ejecución del código empieza en el método “main” del notificador.
public static void main(String[] args) {
        System.setProperty("mail.mime.charset", "ISO-8859-1");
        Connection conexion = null;
        PreparedStatement pstmt = null;
        try {           
            String urldb = "jdbc:mysql://localhost:3306/nombreBaseDeDatos";;
            String usuariodb = "usuarioDelEsquemaDeNotificaciones";
            String clavedb = "claveDelUsuarioDelEsquemaDeNotificaciones";
            Class.forName("com.mysql.jdbc.Driver");
               conexion = DriverManager.getConnection(urldb, usuariodb, clavedb);
            pstmt = conexion.prepareStatement("select idUsuarioRemitente, idUsuarioDestinatario, fecha, texto, asunto from notificacion where enviado=? order by idUsuarioDestinatario, fecha");
            pstmt.setBoolean(1, false);
            ResultSet rs = pstmt.executeQuery();
            List<Notificacion> notificaciones = new ArrayList<Notificacion>();
            Notificacion notificacion;
            String idUsuarioDestinatario = null, idUsuarioDestinatarioAnterior = null;
            while (rs.next()) {
                notificacion = new Notificacion();
                Clob clob = rs.getClob("texto");
                notificacion.setTexto(clob.getSubString(1, (int)clob.length()));
                notificacion.setAsunto(rs.getString("asunto"));
                notificacion.setFecha(rs.getTimestamp("fecha"));
                notificacion.setIdUsuarioRemitente(rs.getString("idUsuarioRemitente"));
                idUsuarioDestinatario = rs.getString("idUsuarioDestinatario");
                if (idUsuarioDestinatarioAnterior != null && !idUsuarioDestinatario.equals(idUsuarioDestinatarioAnterior)) {
                    enviarNotificaciones(getCorreo(idUsuarioDestinatarioAnterior, conexion), notificaciones, conexion);
                    notificaciones = new ArrayList<Notificacion>();
                    notificaciones.add(notificacion);
                }
                else
                    notificaciones.add(notificacion);
                idUsuarioDestinatarioAnterior = idUsuarioDestinatario;
            }
            enviarNotificaciones(getCorreo(idUsuarioDestinatario, conexion), notificaciones, conexion);           
        }
        catch(Exception e) {
              // Código eliminado, ver el zip.
        }
        finally {
              // Código eliminado, ver el zip.
        }
    }
Lo primero que hacemos es definir la propiedad para el conjunto de caracteres a utilizar por JavaMail con la línea:
System.setProperty("mail.mime.charset", "ISO-8859-1");
Esto no debería ser necesario pero sí es interesante comentarlo. Posiblemente, de no hacerlo, JavaMail utilice UTF-8 a la hora de codificar los caracteres pero, en mi caso, he tenido la mala idea de definir el campo “texto” de mi tabla, en MySQL, siguiendo la codificación por defecto de la tabla, y esta la tengo definida como “latin-1 default collation”. Si no pongo esta línea el resultado es que en mis mensajes los acentos, las eñes, y demás no aparecen correctamente.
A continuación crearemos los parámetros necesarios para crear la conexión con la base de datos, usuario, clave, url de la base de datos, cargaremos el driver jdbc de la misma y creamos un PreparedStatement para poder agruparlas por destinatario (cogiendo solo las no enviadas, enviado=0).
pstmt = conexion.prepareStatement("select idUsuarioRemitente, idUsuarioDestinatario, fecha, texto, asunto from notificacion where enviado=? order by idUsuarioDestinatario, fecha");
Recorremos el resultset  y vamos almacenando en un ArrayList las notificaciones de un mismo usuario. En el momento en el que se cambia de usuario enviamos por correo las notificaciones a ese usuario y reiniciamos el ArrayList para albergar las del siguiente destinatario. En caso de error guardamos una traza para luego poder consultarla en un fichero. Posteriormente, al fin del proceso, cerramos la conexión.

En mi código el archivo de error va a “C:\\notificaciones” (la mala costumbre del Windows). Es curioso ver que Ubuntu me crea el log de este modo:

Dentro del método enviarNotificaciones definimos las propiedades necesarias para que JavaMail encuentre y utilice nuestra cuenta de Gmail (smtp.gmail.com, puerto 587) y para que envíe el correo, sacando la dirección de correo del destinatario de la tabla donde tenemos la dirección de ese usuario. Después de enviar la notificación las marcamos en base de datos como enviadas para no volver a tratarlas en el futuro.
public static void enviarNotificaciones(String correoDestinatario, List<Notificacion> notificaciones, Connection con) throws Exception {
        if (correoDestinatario == null || notificaciones == null || notificaciones.size() == 0) return;
       
        StringBuffer strBuff = new StringBuffer("<html><body>");
        for (Notificacion notificacion: notificaciones) {
            strBuff.append(notificacion.getTexto());
            strBuff.append("<br/>");
            strBuff.append("<br/>");
        }
        strBuff.append("</body></html>");
       
        Properties props = new Properties();
        props.setProperty("mail.smtp.host", "smtp.gmail.com");
        props.setProperty("mail.smtp.starttls.enable", "true");
        props.setProperty("mail.smtp.port", "587");
        props.setProperty("mail.smtp.user", "cuentaDeGmail@gmail.com");
        props.setProperty("mail.smtp.auth", "true");
        Session session = Session.getDefaultInstance(props);
        MimeMessage message = new MimeMessage(session);
        message.setFrom(new InternetAddress("cuentaDeGmail@gmail.com"));
        message.addRecipient(Message.RecipientType.TO, new InternetAddress(correoDestinatario));
        message.setSubject("Asunto de las notificaciones, p.e, Notificacion automáticas Sistema de ...");
       
        message.setContent(strBuff.toString(), "text/html");
        Transport t = session.getTransport("smtp");
        t.connect("cuentaDeGmail@gmail.com", "claveDelUsuarioDeGmail");
        t.sendMessage(message, message.getAllRecipients());
        t.close();

        actualizarNotificaciones(notificaciones, con);
}

Una vez que todo compila podremos arrancar nuestra base de datos y ejecutar el proyecto, como aplicación Java, directamente desde Eclipse para ver que todo funciona correctamente. Aunque desde Eclipse se puede hacer tengo la costumbre, posiblemente mala, de crear, por línea de comandos el jar de mis clases. Pulsando con el botón derecho del ratón sobre el proyecto podemos ver las propiedades del mismo y copiar la ruta del mismo. Vamos a un explorador, metemos la ruta, y copiamos el contenido del directorio “bin” en otra carpeta. Abrimos una línea de comandos, vamos a ese directorio y creamos el “jar” (librería para nuestras clases), notificador.jar:

Definición de la tarea en Ubuntu
Copiaremos los jars de conector MySQL, de JavaMail y de nuestro código en un directorio de Ubuntu.
La versión de Ubuntu sobre la que definí la tarea es la 10.04, utilizando el escritorio de GNome.  Simplemente tenemos que ir a las herramientas del sistema y abrir las tareas programadas (no recuerdo si tuve que instalar algún paquete especial para esto, imagino que no, que es algo que viene por defecto en el sistema).

Crearemos una nueva tarea y definiremos sus propiedades, “Cada día” a las 00:00. El comando a ejecutar será del tipo:
java -classpath /home/david/notificador/notificador.jar:/home/david/notificador/mail.jar:/home/david/notificador/mysql-connector-java-5.1.12-bin.jar bajo.el.tejado.de.zinc.Notificador

En breve espero poner un ejemplo de uso: un formulario de sugerencias que notifica la entrada de alguna sugerencia al administrador del sistema.

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