Gráficos
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.
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:
- 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
- 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
- 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:
imagealign_InlineUp
El veloz murciélago hindú comía feliz cardillo
################
# #
# (Img_mono) #
# #
y kiwi. ################ La cigüeña tocaba el
saxofón detrás del palenque de paja.
|
imagealign_InlineDown
El veloz murciélago hindú comía feliz cardillo
y kiwi. ################ La cigüeña tocaba el
# #
# (Img_mono) #
# #
################
saxofón detrás del palenque de paja.
|
imagealign_InlineCenter
El veloz murciélago hindú comía feliz cardillo
################
# #
y kiwi. # (Img_mono) # La cigüeña tocaba el
# #
################
saxofón detrás del palenque de paja.
|
<
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:
imagealign_MarginLeft
################ El veloz murciélago
# # hindú comía feliz
# (Img_mono) # cardillo y kiwi. La
# # cigüeña tocaba el
################ saxofón detrás del
palenque de paja. El veloz murciélago
hindú comíafeliz cardillo y kiwi. La
|
imagealign_MarginRight
El veloz murciélago ################
hindú comía feliz # #
cardillo y kiwi. La # (Img_mono) #
cigüeña tocaba el # #
saxofón detrás del ################
palenque de paja. El veloz murciélago
hindú comía feliz cardillo y kiwi. La
|
- 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.
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:
- El nombre de la ventana en la que se va a colocar el gráfico
(
gg_mapawin
, por ejemplo).
- La imagen que se va a colocar (pero mira la explicación que va
después de la lista de argumentos).
- 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ú.
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.
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.
Puedes hacer más que colocar imágenes PNG y JPEG en tus ventanas
gráficas; puedes dibujar las tuyas propias. El principal comando para
hacer esto es glk_window_fill_rect()
, que necesita seis argumentos:
- El nombre de la ventana en la que quieres dibujar
- El color del que quieres que sea el rectángulo. Éste está
codificado como un número hexadecimal en la forma
siguiente. Primero, escribe un signo dólar (que indica que lo que va
después es un número hexadecimal). Después, ecribe un número
hexadecimal de dos dígitos, desde
00
a FF
, que representa la
cantidad de rojo que forma parte del color. Después viene un número
exadecimal de dos dígitos que representa la cantidad de verde, y
después un número hexadecimal de dos dígitos que representa la
cantidad de azul. Así, $000000
sería el negro, $FFFFFF
sería
blanco, $FF0000
sería un rojo intenso, $FFC000
sería un bonito
dorado, $C0C0FF
sería un azul bebé, etc... - La coordenada X de la esquina superior izquierda del rectángulo.
- La coordenada Y de la esquina superior izquierda del rectángulo.
- El ancho del rectángulo, si quieres poner un solo pixel o una
línea vertical, éste debe ser 1.
- El alto del rectángulo. Si quieres dibujar un solo pixel o una
línea horizontal, éste debe ser 1.
Y esto prácticamente es todo. Otro truco que puedes hacer es llenar
toda una ventana con un color sólido, usando
glk_window_set_background_color()
(que requiere dos argumentos: la
ventana en cuestión y el color que quieres darle al fondo, codificado
como se ha explicado antes) y a continuación hacer un
glk_window_clear()
(que requiere un argumento, la ventana a
borrar). Pero si quieres dibujar cosas más complejas que puntos,
líneas y rectángulos, tendrás que, al menos de momento, construirlas
tu mismo a base de puntos, líneas y rectángulos. En la mayoría de los
casos será mucha mejor decisión usar un programa gráfico para crear
una imagen PNG o JPEG.