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í: