Dibujando tus propias imágenesGráficosGráficosMostrando gráficos PNG y JPEG

Mostrando gráficos PNG y JPEG

Si quieres que tu juego muestre un gráfico, lo primero tienes que preparar algunas cosas. El nombre del fichero que contiene la imágen debe ester listado en el fichero de recursos Blorb asociado con tu programa (ver blorb.html) y haberle dado un nombre para referirte a él desde el juego. Glk soporta dos formatos de imágen: PNG y JPEG. JPEG es un formato con pérdida, utiliza un algoritmo de compresión que reduce considerablemente el tamaño del fichero, a cambio de perder un poco de fidelidad a la imagen original (a menudo la pérdida de fidelidad no es detectable por el ojo). PNG es un formato sin pérdida que tiene soporte para pixels transparentes, en una forma muy similar al GIF, solo que sin los potenciales problemas legales que podrían derivarse de usar GIF (pues el algoritmo de compresión que usa GIF está patentado). JPEG se considera mejor para fotos, PNG para cosas como arte dibujado "a mano" (con un programa de dibujo), imágenes de texto, y similares. Si tu imagen no está en ninguno de esos formatos, tendrás que usar un programa gráfico para convertirlo antes de poder usarlo en tu juego Inform Glulx. Lo siguiente, tienes que decidir dónde mostrarás la imagen. El trozo pantalla que ocupa el intérprete Glulx puede ser dividido en ventanas (ver Ventanas Glk, y tienes que decidir cuál de ellas será el destino de esta imagen concreta. Lo que hay que hacer a partir de aquí, depende del tipo de ventana al que pertenezca la ventana destino.

Gráficos en una ventana de buffer de texto

Bien, has decidido que quieres meter una imagen directamente en medio del texto de tu aventura conversacional. Esto es, realmente, bastante sencillo: simplemente llama a glk_image_draw() con cuatro argumentos:
  1. El nombre de la ventana a la que vas a enviar la imagen. A menos que estés haciendo cosas realmente inusuales con tu disposición de ventanas, el nombre de esta ventana será gg_mainwin
  2. El nombre de la imagen a mostrar, tal como la has nombrado en tu fichero de recursos Blorb. Si la imagen es de un mono, por ejemplo, podrías llamarla Img_Mono
  3. La forma en la que quieres que el texto fluya alrededor de la imagen. Aquí tienes cinco posibilidades. Las tres primeras son para el caso de que quieras que tu imagen sea colocada como si simplemente fuera otra palabra más en la línea de texto que aparecerá. Quizás la mejor forma de mostrar cada opción sea con algunos diagramas: Naturalmente, si la imágen es la única cosa que va a aparecer en ese párrafo línea puedes elegir cualquiera de los tres, pues todos saldrán igual. Las otras dos opciones sólo funcionarán si la imagen es la primera cosa que sale en la línea (es decir, si va después de un carácter ^ en una cadena, o después de una llamada a new_line;) Cualquier texto que vaya después de la imagen, será plegado alrededor de la imagen en una de estas dos formas:
  4. El cuarto argumento vale 0. Este argumento sólo tiene algún significado si la imagen va a ser dibujada en una ventana gráfica; sin embargo, no podemos omitirlo, debido a que, a diferencia de las funciones Inform normales, las funciones de Infglk no pueden recibir menos parámetros de los que están esperando.
Así que supongamos que queremos que esta imagen del mono aparezca cuando el jugador decide llevar al protagonista al zoo y > EXAMINAR MONO. Deberíamos programar el mono como sigue:

Object  mono "el mono Bubu"
 with   nombre 'mono' 'bubu',
        descripcion [;
            print "El mono tiene esta pinta:^";
            glk_image_draw(gg_mainwin, Img_mono,
                           imagealign_InlineUp, 0);
            print "^"; rtrue;
        ],
 has    animado propio;

Por supuesto, siempre cabe la posibilidad de que el jugador esté usando un intérprete que no soporte gráficos, en cuyo caso la imagen no podrá cargarse y el jugador sólo verá una línea en blanco después de la frase "esta pinta:". Para saber cómo habérselas con esta posibilidad, ver la sección sobre test de capacidades.

Imágenes en una ventana gráfica

Al igual que en las ventanas de buffer de texto, la función para colocar imágenes en una ventana gráfica es glk_image_draw(), pero en esta ocasión sus cuatro argumentos son los siguientes:
  1. El nombre de la ventana en la que se va a colocar el gráfico (gg_mapawin, por ejemplo).
  2. La imagen que se va a colocar (pero mira la explicación que va después de la lista de argumentos).
  3. y 4. Las coordenadas en las que quieres que se sitúe la esquina superior izquierda de la imagen. El tercer argumento indica cuántos pixels hay desde el borde izquierdo de la ventana, y el cuarto cuántos hay desde el borde superior de la ventana, así que si quieres que la imagen salga pegada a estos bordes, pon 0 para ambos. (¿Por qué querrías usar coordenadas distintas de 0,0? Quizás quieras antes poner una imagen de un borde decorativo, y cargar pequeñas imágenes en ese borde, quizas en 10,l0.) No te preocupes si la imagen sobrepasa el margen derecho o inferior de la ventana gráfica - cualquier exceso será recortado.
La parte complicada es el segundo argumento. Cuando pones imágenes en una ventana de tipo buffer de texto, esta imagen pasa a ser simplemente un elemento más del flujo que el programa envía a la ventana, de modo que cuando una operación undo o una restauración de un juego salvado cambia la localización del protagonista dentro del juego, la librería manejará estas imágenes automáticamente. No ocurre lo mismo cuando estás poniendo las imágenes en otras ventanas. Veamos un ejemplo. Supongamos que tienes un juego con una ventana gráfica de tamaño fijo, donde muestras un gráfico de la localidad en la que se halla el jugador. Y digamos que has decidido hacer esto insertando directamente la imagen que quieres mostrar cuando el jugador se mueve a esa habitación. Así, si la cocina está al sur del comedor, podrías tener un trozo de código en el objeto comedor con un aspecto como este:

   al_s [;
       if (gg_picwin) { ! comprobación de que existe la ventana
          glk_image_draw(gg_picwin, Img_Cocina, 0, 0);
       }
       JugadorA(Kitchen);
   ],

Esto, realmente, causará que la imagen de la cocina aparezca en la ventana correcta cuando el jugador entra en la cocina. Pero ¿y si el jugador después pone undo o carga una partida salvada en la cual estaba en el comedor? En la ventana principal (de texto), se verá retornar al comedor, ¡pero la ventana gráfica aún estará mostrando una imagen de la cocina! La librería no actualiza las ventanas gráficas automáticamente, tienes que hacerlo tú.

Manejo de ventanas gráficas

La librería biplataforma proporciona unos cuantos "puntos de entrada" para hacer un poco más sencillo el manejo de las ventanas. Un punto de entrada es una especie de rutina opcional, que los programadores pueden escribir en su código fuente. Los programadores de librerías, de vez en cuando encontrarán un punto en el código de la librería donde sospechan que los programadores de juegos querrían añadir código personalizado, o quizás no. Entonces el programador de la librería hace una rutina "tonta" (vacía) que no hace nada, y la llama desde ese punto. Entonces, el programador de juegos puede escribir una rutina con el mismo nombre, y cuando la librería llega al punto en cuestión, ejecutará el código de la rutina que ha proporcionado el programador del juego, que hace algo, en lugar de la que venía en la librería que no hacía nada. O el programador de juegos puede no escribir esa rutina, y entonces la librería ejecutará la rutina "tonta" que no hace nada y proseguirá. De modo que, si en tu juego no vas a usar ventanas especiales (como ventanas gráficas), o canales de sonido, o referencias a ficheros externos, no necesitarás preocuparte por los puntos de entrada que se van a explicar ahora. La librería se ocupará de gg_mainwin y gg_statuswin por ti, así como de cualquier fichero de "partida guardada" que puedas generar. Pero si creas tus propios objetos glk, como una ventana gráfica, es tu responsabilidad atenderla. He aquí cómo. Lo primero, tienes que crear una rutina para el punto de entrada que es llamado tras cada reinicio, restauración de partida guardada y cada undo. Esta es IdentifyGlkObject(). La razón por la que debes tener una rutina IdentifyGlkObject() si creas tu propia ventana gráfica es porque, después de un reinicio, una restauración de partida o un undo, las variables globales que manejan esas ventanas pueden tener valores incorrectos, y las cosas pueden ponerse feas si no les devuelves los valores correctos. IdentifyGlkObject() en realidad es llamada tres veces, y recibe un parámetro llamado fase que indica qué llamada es la que se ha producido (toma los valores 0, 1 y 2). En cada una de esas llamadas, la misión de IdentifyGlkObject() es diferente. En la fase 0, tu misión es poner a cero todas las variables que uses para manejar objetos Glk. En la fase 1, tienes que restaurar los valores correctos a todas las variables que uses para manejar ventanas, flujos de datos y referencias a ficheros. Finalmente, en la fase 2 debes restaurar los valores correctos de los restantes objetos Glk que hayas creado (canales de sonido, por ejemplo). En la fase 2 es también donde debes actualizar tus ventanas para que muestren las imágenes correctas, y los canales de sonido para que toquen la música adecuada, etc. Así que, continuando con el ejemplo de la sección pevia, digamos que tenemos una ventana gráfica, gg_picwin, en la que mostramos la imagen de la localidad donde está el protagonista. Pero a diferencia de lo que hicimos en la sección previa, no dibujaremos las imágenes directamente en esa ventana, con llamadas como glk_image_draw(gg_picwin, Img_Cocina, 0, 0). En vez de eso, tendremos una variable global llamada imagen_actual, y una rutina como la siguiente:

[ RedibujarVentanaGrafica ;
   glk_image_draw(gg_picwin, imagen_actual, 0, 0);
];  

Ahora, la rutina para mover al jugador desde el comedor a la cocina hacia el sur, tendría este aspecto:

  al_s [;
     imagen_actual=Img_Cocina;
     RedibujarVentanaGrafica();
     JugadorA(cocina);
  ],  

Y por último, tendremos nuestro punto de entrada IdentifyGlkObject():

[ IdentifyGlkObject fase tipo ref rock;
   if (fase == 0) { ! Poner cero en todos nuestros objetos glk
      gg_picwin = 0;
      return;
   }       
   if (fase == 1) { ! Reiniciar correctamente las variables glk
      switch (tipo) {
         0: ! es una ventana
            switch (rock) {
               GG_PICWIN_ROCK: gg_picwin = ref;
            }
         1: ! es un flujo
            ! pero no hay flujos en este ejemplo
         2: ! es una referencia a fichero
            ! pero no hay ficheros en este ejemplo
      }
      return;
   }       
   if (fase == 2) { ! Actualizar nuestras ventanas
      RedibujarVentanaGrafica();
   }
];

¿Qué es todo eso? Primero, cogemos la variable global en la cual guardábamos la ventana gráfica que habíamos creado antes (pero que ahora solo contiene basura, debido al reinicio) y la ponemos a 0 - esto es la fase 0. Después, la librería encuentra este objeto Glk creado y no sabe lo que es, así que llama de nuevo al punto de entrada para que determine lo que era. Como parámetros le pasa el identificador de este objeto (en ref) y el "valor roca" del objeto en rock (el cual no se pierde durante el reset). En nuestra rutina decimos "Si el valor roca de este objeto es igual al valor roca que habíamos puesto a nuestra ventana gráfica, entonces ¡tiene que ser nuestra ventana gráfica!" Por tanto, actualizamos el valor de nuestra variable con el valor que recibimos en ref. Esto fue la fase 1. Finalmente en la fase 2 hacemos un redibujo: ahora que ya sabemos a dónde enviar la imagen que queremos mostrar, podemos mostrarla. La ventana gráfica ahora se comportará de forma correcta ante restauraciones de partidas grabadas y "undos" y similares: si el jugador mueve al protagonista a la cocina, y después pone UNDO, no sólo el protagonista volverá al comedor, sino que la ventana gráfica reemplazará además la imagen de la cocina con la imagen del comedor. ¿Y qué hay de los eventos externos que pueden afectar al juego? Por ejemplo el jugador puede cambiar de tamaño la ventana de Glulxe, o cambiar la resolución del monitor - ¿cómo podemos manejar estas cosas? Respuesta: la librería tiene un bucle en el que comprueba constantemente estas cosas, y este bucle también contiene un punto de entrada llamado HandleGlkEvent(). HandleGlkEvent() recibe dos argumentos: "ev" es un array que contiene información sobre lo que acaba de ocurrir (¿un cambio de tamaño? ¿una pulsación del ratón? ¿el final de un efecto de sonido?), y "contexto" que puede valer 0 si el evento ocurrió mientras la librería esperaba una entrada (como cuando espera por un comando normal, o en un prompt como el de la rutina SiONo() - en cualquier momento en que el juego está esperando a que el jugador pulse la tecla Intro para interpretar la entrada) o 1 si el evento ocurrió durante una espera de carácter (como en los menús y similares, en los que el juego espera por cualquier tecla). Para el ejemplo que estamos tratando, todo lo que necesitamos es esta breve rutina:

[ HandleGlkEvent ev contexto;
   contexto = 0; ! Esta linea solo está para evitar un warning
   switch (ev-->0) {
      evtype_Redraw, evtype_Arrange:
         RedibujarVentanaGrafica();
   }
];  

Este código, esencialmente se resume en "Si ocurre algo que implique que hay que redibujar la ventana gráfica, pues adelante, redibujala usando las instrucciones que hemos escrito antes." Y esto debería ser todo lo que necesitas para hacer tus ventanas gráficas lo bastante robustas como para manjear cualquier contingencia que se les venga encima.

Solución de problemas con los gráficos

Pregunta: ¡Estoy bastante seguro de haberlo hecho todo bien, pero mi juego se niega a mostrar este JPEG! No se cuelga, y pasa el test de gestalt_Graphics, pero no sale el gráfico. ¿Qué pasa? Respuesta: El problema puede estar en el propio fichero JPEG. Realmente, JPEG es un nombre que se da una variedad de formatos de imagen diferentes, y la implementación de Glk que estás usando puede estar equipada sólo para decodificar agunos de ellos. Volver a guardar el JPEG con otros settings, o con otro programa de tratamiento de gráficos, puede arreglar la cosa. Si aún no funciona, puede que tengas que usar PNG en su lugar.
Dibujando tus propias imágenesGráficosGráficosMostrando gráficos PNG y JPEG