Otros trucos
Las ventanas de tipo texto y las de tipo rejilla tienen la interesante
capacidad de reconocer cuándo se pulsa un ratón sobre ellas, y de
retornar las coordenadas de la letra o pixel que se ha seleccionado.
Para sacar partido de esta capacidad, primero debes alertar al
programa de que tiene que estar alerta para detectar las pulsaciones
de ratón en la ventana o ventanas que te interesen. Por ejemplo,
digamos que tienes una ventana gráfica, gg_ventana_brujula
, en la
que has puesto una imagen de una rosa de los vientos de ocho puntas.
Quieres que el jugador pueda clicar sobre cualquiera de las puntas y
que eso se convierta en un comando para ir en la dirección apropiada.
Lo primero, necesitas añadir la siguiente línea a la rutina
Inicializar()
:
glk_request_mouse_event(gg_ventana_brujula);
|
El juego estará alerta para detectar una pulsación, y sólo una, del
ratón en la ventana que tiene la rosa de los vientos. Si no ocurre
ninguna, no pasa nada; el programa aceptará igualmente la entrada
habitual por el teclado. Pero si ocurre una, se disparará la rutina
HandleGlkEvent()
.
HandleGlkEvent()
ya ha aparecido en secciones previas (mira
"Imágenes en una ventana gráfica", por ejemplo, o en "Música y
sonido") pero este es un caso especialmente claro de cómo funciona la
rutina. Como siempre, la rutina HandleGlkEvent()
recibe los
parámetros ev
y contexto
; esta vez ev
, que es un array, traerá
bastante información. Cuando el programa detecta una pulsación de
ratón, rellena el array con la siguiente información:
ev-->0
Esto recibe la constante evtype_MouseInput
, para
indicar que lo que ha ocurrido ha sido una pulsación del ratón (y
no, por ejemplo, un redimensionado de la ventana).
ev-->1
toma el valor de la ventana que ha sido "pinchada"
(en este ejemplo, tomaría el valor gg_ventana_brujula
)
ev-->2
la coordenada X del pixel en que han pinchado (si la
ventana fuera de texto, rejilla, aquí tendríamos la coordenada X del
carácter sobre el que se ha pinchado)
ev-->3
la coordenada Y.
Tu rutina HandleGlkEvent
, entonces, tendría el siguiente aspecto (al
menos en parte):
[ HandleGlkEvent ev contexto;
switch (ev-->0) {
evtype_MouseInput:
glk_request_mouse_event(gg_ventana_brujula);
! Aqui pondriamos codigo para analizar los resultados y
! generar instrucciones nuevas. Por ejemplo, podriamos
! hacer que si ev-->2 esta entre 50 y 100, mientras
! ev-->3 esta entre 0 y 50, entonces se habria pinchado
! en la flecha norte de la brujula, y entonces
! lanzariamos el comando para ir al norte
}
];
|
Obsérvese que, una vez que el juego ha detectado una pulsación del
ratón, dejará de detectar más pulsaciones de ratón en esa ventana, a
menos que le indiquemos que espere por otra. De ahí la llamada a a
glk_request_mouse_event()
que aparece en el código anterior.
Ahora, debido a que el juego está esperando a que el jugador complete
su línea de comandos, no podemos simplemente lanzar un comando interno
en el juego del tipo <<Ir Obj_n>>
. En vez de eso, tendremos que
hacer lo siguiente: Primero, vete hasta la línea en que comienza la
rutina HandleGlkEvent
y declara tres nuevas variables locales:
abortres
, comandonuevo
y longcomando
. Después, tras aceptar el
click de ratón, cancelaremos la entrada de línea del teclado,
poniendo:
glk_cancel_line_event(gg_mainwin, 0);
|
Ahora, imagina un comando que, tecleado por el jugador, haría lo que
quieres que ocurra. En este ejemplo, el comando podría ser "ir
norte", o "norte". Haz que éste sea el nuevo comando en la forma
siguiente:
nuevocomando = "ir norte";
|
Y después copia el siguiente código tal cual:
longcomando = PrintAnyToArray(abortres+WORDSIZE,
INPUT_BUFFER_LEN-WORDSIZE, comandonuevo);
abortres-->0 = longcomando;
|
Lo que hace lo anterior es completar el proceso para decirle al
computador que en este caso particular, pinchar el ratón en ese área
concreta de la ventana gráfica, equivale exactamente a teclear "ir
norte" y pulsar Intro. Ahora, puedes hacer que ese texto aparezca
tras el prompt, para que el jugador vea lo que ha ocurrido, o
simplemente hacer algo como:
glk_set_style(style_Input);
print "(click de ratón)";
glk_set_style(style_Normal);
new_line;
|
Qué método es el mejor depende de los gustos personales, y del caso
particular que tengamos entre manos. En cualquier caso, la última
línea de estos bloques de código ha de ser return 2;
, para indicar
que este turno ha finalizado. Et voilà - ya has manejado la entrada
por ratón.
Hiperenlaces son imágenes o trozos de texto que, cuando el usuario
pincha con el ratón sobre ellos, enviarán un mensaje a
HandleGlkEvent()
, indicando que han sido pinchados, lo que te
permite ejecutar código especial si quieres. Por ejemplo, podrías
insertar notas al pie "clickables", que muestren su texto si el
jugado pincha en ellas. O permitir a los jugadores que se muevan
simplemente pinchando sobre los nombres de las salidas en las
descripciones de las localidades... hay infinitas posibilidades. Los
hiperenlaces se preparan de forma muy similar a los eventos de ratón
para ventanas gráficas o de rejilla. Lo primero que hay que hacer es
alertar al programa de que debe estar atento a las pulsaciones de
ratón sobre un hiperenlace:
glk_request_hyperlink_event(gg_mainwin);
|
(Cambia el nombre de la ventana en la que aparecerán los hiperenlaces,
si no es gg_mainwin
. Si quieres enlaces en muchas ventanas,
tendrás que repetir una línea como la anterior para cada una de
ellas.) Ahora el programa está listo para responder a un enlace, sólo
tienes que construir los enlaces propiamente dichos. Esto se hace con
la llamada a glk_set_hyperlink()
. Esta función recibe un parámetro,
que debe ser un número distinto de cero que identifique de forma única
a tu enlace. Probablemente querrás crear constantes para referirte a
los números que hayas elegido para los enlaces. En tu código, algo
como ENLACE_IR_NORTE
es bastante más claro que algo como, por
ejemplo, 67
. Cualquier texto o imagen que se muestre en pantalla
después de la llamada a glk_set_hyperlink()
será parte del enlace,
hasta que ejecutes una llamada a glk_set_hyperlink(0)
. Si dos
enlaces van seguidos, puedes omitir esta llamada entre ambos. Tan
pronto como comienza un enlace nuevo, se da por terminado el anterior.
Ahora tus enlaces ya están listos, y todo lo que necesitas es
programar HandleGlkEvent()
para que responda a ellos cuando el
jugador pinche en uno. Por ejemplo, podría ser así:
[ HandleGlkEvent ev contexto abortres comandonuevo longcomando;
switch (ev-->0) {
evtype_Hyperlink:
glk_request_hyperlink_event(gg_mainwin);
! Pon codigo aqui analogo al que vimos para manejar
! el raton. La unica diferencia es que ahora ev-->1
! contiene la ventana, y ev-->2 el numero del link
! sobre el que el jugador ha pinchado
}
];
|
Y al igual que en la sección previa, recuerda que tras responder a un
click sobre un hiperenlace, el programa no responderá a otro, a menos
que explícitamente le digas que lo haga.
Aunque las aventuras, generalmente, están basadas en turnos, es
posible -y en realidad bastante sencillo, una vez que te has
familiarizado con la entrada por ratón y por hiperenlaces- incorprar
eventos de tiempo real en tu juego. Esto puede estar bien para, por
ejemplo, mover a los personajes, que entren y salgan de la localidad
mientras el jugador está pensando qué comando dar después, o para
implementar una bomba que el jugador realmente deba desactivar en unos
segundos. También pueden usarse en conjunción con gráficos para crear
animaciones.
El comando para lanzar un evento de tiempo real es
glk_request_timer_events()
, con un número entre los paréntesis, que
representa el número de milisegundos que transcurrirán entre los
sucesivos eventos de tiempo real. Es decir, cuando el programa llega a
una línea que dice glk_request_timer_events(1000)
, se ejecutará el
código apropiado de HandleGlkEvent()
cada 1000 milisegundos, o sea,
cada segundo. Detectar este tipo de evento es muy similar a los del
ratón o los hiperenlaces, símplemente equipa a tu rutina
HandleGlkEvent()
con algo como lo siguiente:
[ HandleGlkEvent ev contexto;
switch (ev-->0) {
evtype_Timer:
! Aqui pones el codigo a ejecutar cada cierto tiempo
}
];
|
La principal diferencia entre estos eventos y los de raton o de
hiperenlace, es que no necesitas solicitar de nuevo un evento de
tiempo real cada vez que has consumido uno: el programa
seguirá generandolos automáticamente hasta que le digas explícitamente
que ya no genere más, con la línea glk_request_timer_events(0);
Así que, ¿cómo harías una animación? La clave es sacar partido del
hecho de que, si enumeras cada cuadro de la animación de forma
correlativa en tu fichero de recursos Blorb, las constantes que se les
asignarán serán números secuenciales. Así que si tienes una animación
con siete cuadros, cada cuadro podría recibir los números 3, 4, 5, 6,
7, 8 y 9, por ejemplo. Pero no necesitas saber qué números son, basta
que crees un contador global para los cuadros y le asignes como valor
inicial el del primer cuadro:
cuenta_cuadros = Primer_Cuadro;
|
Después, pon un código como el siguiente en la rutina HandleGlkEvent()
:
switch (ev-->0) {
evtype_Timer:
if (cuenta_cuadros > Ultimo_Cuadro) glk_request_timer_events(0);
else {
glk_image_draw(gg_ventana_anim, cuenta_cuadros, 0, 0);
cuenta_cuadros++;
}
}
|
Este código mostrará primero el primer cuadro, después el segundo,
después el tercero, etc. hasta llegar al séptimo, y después parará. Es
cosa tuya asegurarte de que esto ocurra a una velocidad razonable -
intenta diferente número de milisegundos desde tu
glk_request_timer_events()
y mira cuál queda mejor.
Ahora, si lo único que quieres es una pausa hasta que el jugador pulse
una tecla, la cosa es mucho más sencilla. Hay un comando llamado
KeyCharPrimitive()
que espera por una tecla. Si pones en tu código
la línea x = KeyCharPrimitive();
el juego esperará a que el jugador
pulse una tecla, y después pondrá el valor de la tecla pulsada en la
variable x
. Un interesante efecto secundario es que puedes usar
KeyCharPrimitive()
a solas para causar una pausa en la salida, y
esperar hasta que el jugador pulse una tecla antes de continuar. Por
ejemplo, supón que quieres hacer una pausa antes de una revelación
dramática:
print "~He considerado todas las pruebas~, dice el detective,
~y todo apunta en una dirección. El asesino fue...";
KeyCharPrimitive();
print " ¡yo! Lo siento, no volverá a ocurrir.~";
|
La mayoría de los sistemas de programación de aventuras tienen
previsto algún tipo de entrada/salida a fichero, aunque sólo sea para
crear y leer partidas guardadas en disco. Inform Glulx permite al
programador crear y leer ficheros con cualquier tipo de contenido. Por
ejemplo, puedes escribir una serie de juegos con el mismo
protagonista, y transferir información sobre el personaje de un juego
al siguiente. Esto puede hacerse si el primer juego crea un fichero
que contiene la información pertinente, y el segundo lo lee. He aquí
cómo.
Para crear un fichero, lo primero de todo, debes crear un objeto
conocido como fileref
, de modo que el programa tenga una forma de
referirse al fichero. Esto se puede hacer de dos formas. Si quieres
que el nombre del fichero sea fijo, siempre el mismo, elegido por el
programa, el comando es glk_fileref_create_by_name;
si prefieres que
sea el jugador el que elija el nombre del fichero, usarás
glk_fileref_create_by_prompt
. Ambos necesitan tres parámetros.
El primer parámetro indica qué tipo de fichero queremos
crear. Consiste en dos constantes, unidas por un signo más. La primera
constante será fileusage_Data
(otras posibilidades se usan sólo para
ficheros con partidas grabadas y similares). La segunda constante será
o bien fileusage_BinaryMode
o fileusage_TextMode
; el modo Binary se
usa para ficheros que sólo serán leídos desde otros juegos Glulx (como
en el ejemplo de antes que almacenaba datos del personaje), mientras
que el modo Text es para ficheros que puedan ser leidos por el jugador
(como una lista de cosas divertidas para probar en el juego, un
certificado de haber finalizado, o lo que se te ocurra).
El tercer parámetro (veremos el segundo en un momento) es la ROCA del
fichero que vamos a crear. Las rocas para filerefs son análogas a las
de las ventanas o los canales de sonido. Por supuesto, si estás
creando un fichero simplemente para escribir información en él y
cerrarlo a continuación, no necesitarás una roca, ya que las rocas son
para seguir la pista a los objetos Glk entre diferentes sesiones de
juego, y no va a ocurrir un cambio de sesión a mitad de la escritura
del fichero. Así que en este caso puedes poner un 0 para este
parámetro. Si necesitas mantener el fichero abierto durante varios
turnos, en cambio, necesitarás crear una roca para él.
El segundo parámetro depende del método de creación del fichero. Si lo
has creado en la forma en la que el programador especifica el nombre
del fichero, aquí sería donde iría ese nombre. Sin embargo, hay dos
complicaciones. En primer lugar, querrás que el nombre de fichero
funcione en diferentes ordenadores y plataformas, así que lo más
fiable es que te limites a nombres con ocho caracteres o menos (ya que
algunas plataformas no pueden manejar nombres más largos). En segundo
lugar, no puedes simplemente poner el nombre del fichero en este
punto, sino que tienes que envolverlo en una llamada especial:
ConvertAnyToCString()
. Por ejemplo, para crear un fichero llamado
INFO
en el que poder escribir después, habría que poner:
fref = glk_fileref_create_by_name(fileusage_Data+fileusage_BinaryMode,
ConvertAnyToCString("INFO"), 0);
|
Si vas a dejar que el jugador elija el nombre del fichero, el segundo
parámetro contiene información sobre el tipo de fichero que se va a
crear. Ya que se usa el mismo comando para crear ficheros de lectura o
de escritura, aqui hay cuatro opciones:
filemode_Read
: El fichero tiene que existir ya previamente. Al
jugador se le pedirá que elija uno de los ficheros ya existentes
para ese uso concreto.
filemode_Write
: El fichero no debe existir; si el jugador
elige un fichero ya existente, se le avisará de que los contenidos
serán reemplazados.
filemode_ReadWrite
: El fichero puede existir o no, si ya
existe se avisará al jugador de que será modificado.
filemode_WriteAppend
: El comportamiento es el mismo que el
anterior, pero debe usarse para añadir información al fichero en el
caso de que éste ya exista.
En este caso podríamos terminar con una llamada como:
fref = glk_fileref_create_by_prompt(fileusage_Data+fileusage_BinaryMode,
filemode_Write, 0);
|
Una vez que has obtenido el fileref
, puedes abrir el fichero:
str = glk_stream_open_file(fref, filemode_Write, 0);
|
El segundo parámetro es uno de los filemode
s que hemos listado
antes. Si has pedido el nombre del fichero al jugador, debe ser el
mismo filemode
que usaste antes.
Una vez que el fichero ha sido abierto, podemos deshacernos del
fileref
:
glk_fileref_destroy(fref);
|
Para escribir, hacemos que el stream actual sea ese fichero, y después
imprimimos texto normalmente (como si fuera para la pantalla):
glk_stream_set_current(str);
print "PUNTOS DE EXPERIENCIA: ", jugador.exp, "^";
|
Una vez finalizado, volvemos a la ventana principal, y cerramos el
stream:
glk_set_window(gg_mainwin);
glk_stream_close(str, 0);
|
El segundo parámetro de glk_stream_close()
, al igual que en
glk_window_close()
, sólo tiene interés si quieres conocer cuántos
caracteres han sido escritos o leídos. En cualquier otro caso, usa
cero.
Para leer de un fichero, el proceso es muy similar, solo que usando
filemode_Read
. Las funciones para leer los datos son glk_get_char_stream()
para leer un único carácter, glk_get_buffer_stream()
para leer un
array de bytes, y glk_get_line_stream()
para leer una línea de
bytes, hasta el siguiente carácter de línea nueva.
Mira el paquete Ork
para más detalles. (Ork 1 genera un fichero con
información sobre el jugador; Ork 2 permite al jugador cargar el
personaje que quieran, o empezar de cero.)