Intro a la programación de juegos

Por Nacho Cabanes

Contenido Indice Cambios Enlaces Autor

3. Leyendo del teclado y escribiendo texto.

Contenido de este apartado:


3.1. Pautas generales.

Escribir una frase suele ser sencillo. Tendremos una orden capaz de escribir un cierto texto en unas ciertas coordenadas. A veces no será inmediato escribir algo que no sea un texto puro, como es el caso del resultado de una operación matemática, pero este problema es fácil de solucionar. Lo que suele ser algo más difícil es escribir con tipos de letras espectaculares: a veces (pocas) estaremos limitados al tipo de letra de la BIOS de la tarjeta gráfica, y otras muchas estaremos limtados (menos) a los tipos de letra de nuestro sistema operativo o que incorpora nuestro compilador. Más adelante veremos una forma de crear nuestros propios tipos de letra y usarlos en nuestros juegos, a cambio de peder la comodidad de usar funciones "prefabricadas" como las que veremos en este apartado y usaremos en nuestros pimeros juegos.

Lo de leer el teclado no suele ser difícil: es habitual tener una función que comprueba si se ha pulsado una tecla o no (para no tener que esperar) y otra que nos dice qué tecla se ha pulsado. Incluso es habitual que tengamos definidas ciertas constantes, para no tener que memorizar cual es el número asociado a una cierta tecla, o el código ASCII que devuelve. En el caso de Java, es algo más incómodo que en C o Pascal, pero tampoco es especialmente complicado.

Vamos a verlo...

3.2 Texto y teclado Con C y Allegro. 

A la hora de escribir texto, casi cualquier biblioteca gráfica tendrá disponible una función que nos permita escribir un cierto mensaje en unas ciertas coordenadas. En el caso de Allegro, dicha función es:
  • void textout(BITMAP *bmp, const FONT *f, const char *s, int x, y, int color);


Debería ser fácil de entender: el primer parámetro es dónde escribiremos el texto (en principio, usaremos "screen", la pantalla), con qué fuente (usaremos una llamada "font", que es la fuente hardware del 8x8 puntos habitual de la pantalla de 320x200 puntos). Finalmente indicamos el texto a escribir, las coordenadas x e y, y terminamos con el color.

Además, tenemos dos variantes, por si queremos que el texto quede centrado en torno a esa posición x (la orden anterior indica la posición de comienzo), o que termine en esa posición:
 

  • void textout_centre(BITMAP *bmp, const FONT *f, const char *s, int x, y, color);
  • void textout_right(BITMAP *bmp, const FONT *f, const char *s, int x, y, color);


Si se trata de un texto con parámetros como los que mostramos con "printf", en la mayoría de bibliotecas gráficas no podríamos escribirlo directamente, deberíamos convertirlo primero a texto, con alguna orden como "sprintf". En Allegro sí tenemos la posibilidad de escribirlo directamente con:
 

  • void textprintf(BITMAP *bmp, const FONT *f, int x, y, color, const char *fmt, ...);


Y también tenemos dos órdenes equivalentes pero que ajustan el texto al centro o al lado derecho: 
 

  • void textprintf_centre(BITMAP *bmp, const FONT *f, int x, y, color, const char *fmt, ...);
  • void textprintf_right(BITMAP *bmp, const FONT *f, int x, y, color, const char *fmt, ...);
Un ejemplo del uso de estas órdenes sería:
 
 
/*----------------------------*/
/*  Intro a la programac de   */
/*  juegos, por Nacho Cabanes */
/*                            */
/*    IPJ03.C                 */
/*                            */
/*  Tercer ejemplo: escribir  */
/*  texto en modo gráfico     */
/*                            */
/*  Comprobado con:           */
/*  - Djgpp 2.03 (gcc 3.2)    */
/*    y Allegro 4.02          */
/*----------------------------*/

#include <allegro.h>
#include <conio.h>

main()
{
  allegro_init();

  if (set_gfx_mode(GFX_SAFE,320,200,0,0)!=0){
    set_gfx_mode(GFX_TEXT,0,0,0,0);
    allegro_message(
      "Incapaz de entrar a modo grafico\n%s\n",
      allegro_error);
    return 1;
  }

  textout(screen, font, "Ejemplo de texto", 160,80,
    palette_color[15]);
  textout_centre(screen, font, "Ejemplo de texto", 160,100,
    palette_color[14]);
  textout_right(screen, font, "Ejemplo de texto", 160,120,
    palette_color[13]);
 

  textprintf(screen, font, 100,40, palette_color[12],
    "Dos por dos es %d",2*2);

  getch();
}

END_OF_MAIN();

que tendría como resultado:

Otra orden relacionada con el texto y que puede resultar interesante es:
 

  • int text_mode(int mode);


Si el parámetro "mode" es 0 o positivo, el texto será opaco (se borra el fondo), y el color de fondo del texto es el que indique "mode". Si es negativo, el texto será transparente (en sus huecos se verá el fondo, que es lo habitual en otras bibliotecas gráficas).

Si no se indica nada, se asume que el modo de escritura del texto es 0 (el texto es opaco, con fondo negro). 
 
 

En cuanto a la introducción de texto, en la mayoría de bibliotecas gráficas, no tendremos nada parecido a "scanf" ni a "gets" que nos permita teclear un texto entero, y menos todavía corregirlo con las flechas del cursor, retroceso, etc.

Normalmente deberemos leer letra a letra (con funciones como "getch" o alguna equivalente), y filtrarlo nosotros mismos. Allegro sí tiene un pequeño GUI (Interfaz gráfico de usuario), que nos permitirá abreviar ciertas tareas como la entrada de texto o la pulsación de botones. Pero estas posibilidades las veremos más adelante.

Con Allegro tenemos una función para esperar a que se pulse una tecla y comprobar qué tecla se ha pulsado:
 

  • int readkey();


Se puede utiliza comprobando el código ASCII (ver qué "letra" corresponde):

      if ((readkey() & 0xff) == 'n')  printf("Has pulsado n");

o bien comprobando el código de la tecla (el "scancode")

      if ((readkey() >> 8) == KEY_SPACE)   printf("Has pulsado Espacio");

Esta forma de comprobarlo se debe a que nos devuelve un valor de 2 bytes. El byte bajo contiene el código ASCII (la letra), mientras que el byte alto contiene el código de la tecla.

Si se pulsa Ctrl o Alt a la vez que una cierta tecla, el código (scancode) de la tecla sigue siendo el mismo, pero no es código ASCII. Por ejemplo, mayúsculas + 'a' devolvería 'A', ctrl + 'a' devolvería 1 (y ctrl + 'b' = 2, ctrl + 'c' = 3 y así sucesivamente) y alt + cualquier tecla devuelve 0 (también devuelven 0 las teclas de función F1 a F12 y alguna otra.

Por eso, en los juegos normalmente usaremos el "scancode" de las teclas, que no varía. Eso sí, estos números no son fáciles de recordar, por lo que tenemos unas constantes preparadas, como la KEY_SPACE del ejemplo anterior:

      KEY_A ... KEY_Z,
      KEY_0 ... KEY_9,
      KEY_0_PAD ... KEY_9_PAD,
      KEY_F1 ... KEY_F12,

      KEY_ESC, KEY_TILDE, KEY_MINUS, KEY_EQUALS,
      KEY_BACKSPACE, KEY_TAB, KEY_OPENBRACE, KEY_CLOSEBRACE,
      KEY_ENTER, KEY_COLON, KEY_QUOTE, KEY_BACKSLASH,
      KEY_BACKSLASH2, KEY_COMMA, KEY_STOP, KEY_SLASH,
      KEY_SPACE,

      KEY_INSERT, KEY_DEL, KEY_HOME, KEY_END, KEY_PGUP,
      KEY_PGDN, KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN,

      KEY_SLASH_PAD, KEY_ASTERISK, KEY_MINUS_PAD,
      KEY_PLUS_PAD, KEY_DEL_PAD, KEY_ENTER_PAD,

      KEY_PRTSCR, KEY_PAUSE,

      KEY_ABNT_C1, KEY_YEN, KEY_KANA, KEY_CONVERT, KEY_NOCONVERT,
      KEY_AT, KEY_CIRCUMFLEX, KEY_COLON2, KEY_KANJI,

      KEY_LSHIFT, KEY_RSHIFT,
      KEY_LCONTROL, KEY_RCONTROL,
      KEY_ALT, KEY_ALTGR,
      KEY_LWIN, KEY_RWIN, KEY_MENU,
      KEY_SCRLOCK, KEY_NUMLOCK, KEY_CAPSLOCK
 

Finalmente, podemos comprobar si se ha pulsado una tecla, pero sin quedarnos esperando a que esto ocurra. No lo usaremos en nuestro primer juego, pero aun así vamos a comentarlo. En Turbo C y compatibles existe la función kbhit(), que está declarada en "conio.h" En Allegro tenemos:

 
  • int keypressed();


que devuelve TRUE si se ha pulsado alguna tecla (después comprobaríamos con "readkey" de qué tecla se trata, y en ese momento, "keypressed" volvería a valer FALSE).
 

En cualquier caso, hay una línea que deberemos incluir siempre al principio de nuestros programas, si vamos a usar las rutinas de control de teclado que nos proporciona Allegro:
 

  • install_keyboard();

Existe otra forma de comprobar el teclado, que nos permitiría saber si se han pulsado varias teclas a la vez, pero eso es algo que no necesitamos aún, y que dejamos para un poco más adelante...

3.3. Teclado y texto con Pascal.

La orden básica para escribir texto en la pantalla gráfica con Free Pascal es:
 
  • OutTextXY(x, y, texto);


También existe una variante a la que no se le indica en qué posición queremos escribir, y entonces lo hará a continuación del último texto escrito. Es "OutText( texto )"

Si queremos que el texto esté alineado a la derecha o al centro, lo haríamos con "SetTextJustify(horizontal, vertical)", donde "horizontal" puede ser LeftText (izquierda), CenterText (centro) o RightText (derecha) y la alineación vertical puede ser BottomText (texto bajo el puntero), CenterText (centrado) o TopText (sobre el puntero). 

Existen otras posibilidades, que no usaremos, al menos por ahora, porque no existen como tal en Allegro y lo haremos de la misma forma que con dicha librería (artesanalmente). Es el caso de emplear distintos tipos de letra, en distintos tamaños o escribir con otras orientaciones (por ejemplo, de arriba a abajo).

Lo que no tenemos en FreePascal es ninguna orden equivalente a "printf" para pantalla gráfica. Sólo podemos escribir cadenas de texto, de modo que si queremos escribir números o expresiones complejas, deberemos convertirlas primero a cadenas de texto.
 

En cuanto a la introducción de texto, en la unidad crt tenemos las funciones equivalente s a las dos principales de Allegro: "keypressed" devuelve "false" mientras no se haya pulsado ninguna tecla, y "true" cuando se ha pulsado. Entonces se puede leer la tecla con "readkey". No tenemos garantías de que funciones como ReadLn vayan a trabajar correctamente en modo gráfico, así que en general tendremos que ser nosotros quienes creemos rutinas fiables si queremos que se pueda teclear un texto entero, y más aún si queremos corregirlo con las flechas del cursor, retroceso, etc.

Eso sí, no tenemos constantes predefinidas con las que comprobar si se ha pulsado una cierta tecla. Para las teclas alfanuméricas no es problema, podemos hacer cosas como 

tecla := readkey;
if tecla = 'E' then ...

Pero para las teclas de función, readkey devuelve 0, y debemos volver a leer su valor para obtener el número de tecla pulsada. Es fácil crear un programa que nos diga el número de cada tecla pulsada, y crearnos nuestras propias constantes simbólicas, así que no profundizamos más por ahora. Si en alguno de nuestros juegos usamos varias teclas, indicaremos entonces los códigos de cada una de ellas.

Aun así, habrá un problema que afectará a nuestros juegos con Free Pascal para Windows, que comentaremos en el siguiente apartado.

Un ejemplo similar al anterior en C sería este:

(*----------------------------*)
(* Intro a la programac de *)
(* juegos, por Nacho Cabanes *)
(* *)
(* IPJ03P.PAS *)
(* *)
(* Tercer ejemplo: escribir *)
(* texto en modo grafico *)
(* *)
(* Comprobado con: *)
(* - FreePascal 2.0 -Windows *)
(*----------------------------*)

uses graph, crt, sysutils;

var
gd,gm, error : integer;


BEGIN
gd := 0;
gm := 0;
initgraph(gd, gm, '');

error := graphResult;
if error <> grOk then
begin
writeLn('No se pudo entrar a modo grafico');
writeLn('Error encontrado: '+ graphErrorMsg(error) );
halt(1);
end;

setColor(15);
outTextXY(160,80, 'Ejemplo de texto');

setTextJustify(CenterText,CenterText);
setColor(14);
outTextXY(160,100, 'Ejemplo de texto');

setTextJustify(RightText,CenterText);
setColor(13);
outTextXY(160,120, 'Ejemplo de texto');


setTextJustify(LeftText,CenterText);
setColor(12);
outTextXY(100,40, 'Dos por dos es '+intToStr(2*2));

readkey;
closeGraph;
END.

Y su resultado sería


3.4. Teclado y texto con Java.


Escribir
en un Applet de Java no es difícil. La orden básica es "drawString":
  • drawString(String texto, int x, y);

Si usamos esta orden, no es tan sencillo como en Allegro el ajustar el texto a un lado de esas coordenadas, al otro o centrarlo. Existen otras órdenes que nos permitirán hacerlo con facilidad, pero las veremos más adelante.

Lo que sí es algo más complicado en Java es eso de leer del teclado. Además es distinto en modo texto (consola) o en modo gráfico (como nuestro Applet). La forma más sencilla (en mi opinión) de hacerlo dentro de un Applet es implementando un "KeyListener", que nos da la estructura básica, a cambio de obligarnos a detallar las funciones keyPressed (que se pondrá en marcha en el momento de apretar una tecla), keyReleased (al dejar de apretar la tecla) y keyTyped (que se activa cuando el carácter correspondiente aparecería en pantalla). Las tres funciones admiten un parámetro de tipo KeyEvent, que tiene métodos como "getChar" que dice la letra que corresponde a esa tecla pulsada o "getKeyCode" que nos indica el código de tecla. La letra pulsada es fácil de comprobar porque tenemos las letras impresas en el teclado, sabemos qué letra corresponde a cada tecla. Pero el código es menos intuitivo, así que tenemos una serie de constantes que nos ayuden a recordarlos:

  • VK_A a VK_Z para las teclas alfabéticas.
  • VK_0 a VK_9 para las numéricas y VK_NUMPAD0 a VK_NUMPAD9 para el teclado numérico adicional.
  • VK_F1 en adelante para las teclas de función.
  • VK_LEFT, VK_RIGHT, VK_DOWN, VK_UP para las flechas del teclado.
  • VK_SPACE, VK_ENTER, VK_ESCAPE, VK_HOME, VK_END y similares para las demás teclas especiales.


De modo que en la práctica lo usaríamos haciendo cosas como

public void keyPressed(KeyEvent e) {
 if (e.getKeyChar() == 'a' ) ...

o como

public void keyPressed(KeyEvent e) {
 if (e.getKeyCode() == VK_ESCAPE ) ...
 

Solo tres comentarios más:

  • En el método "init" (que se encarga de inicializar nuestro Applet antes de empezar a dibujar) deberíamos añadir "addKeyListener(this);"
  • Sólo se leerán pulsaciones del "componente" actual. Esto quiere decir que si nuestro Applet tuviera varios componentes (cosas que no hemos visto, como cuadros de texto, listas desplegables, etc), nos podría interesar indicar cual queremos decir que sea el activo con "requestFocus()", que en el ejemplo aparece dentro de un comentario por no ser necesario.
  • Por eso mismo, debemos hacer clic con el ratón en nuestro Applet antes de empezar a usar el teclado, o no nos hará caso, si no está "activo".


Todo listo. Vamos a ver un ejemplo sencillo del uso de estas órdenes:
 
 
/*----------------------------*/ 
/*  Intro a la programac de   */ 
/*  juegos, por Nacho Cabanes */ 
/*                            */ 
/*    ipj03j.java             */ 
/*                            */ 
/*  Tercer ejemplo: escribir  */ 
/*  en colores, leer del      */ 
/*  teclado                   */
/*                            */
 
/*  Comprobado con:           */ 
/*  - JDK 1.4.2_01            */  
/*----------------------------*/ 

import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
 
public class ipj03j extends Applet 
    implements KeyListener 
{

    char letra;
    
    public void paint(Graphics g) {

        g.setColor(Color.green);
        g.drawString("Ejemplo de texto 1",
            140, 80);

        g.setColor(Color.blue);
        g.drawString("Ejemplo de texto 2", 
            160, 100);

        g.setColor(new Color(0, 128, 128));
        g.drawString("Ejemplo de texto 3",
            180, 120);
        
        if (letra =='a')
            g.drawString("Has pulsado A", 
                60, 60);
    } 


    public void init() {
        //requestFocus();
        addKeyListener(this);
    }

    
    public void keyTyped(KeyEvent e) {
    }
    
    public void keyReleased(KeyEvent e) {
    }
 
    public void keyPressed(KeyEvent e) {
        letra = e.getKeyChar();
        repaint();
        
    }
    
    
} 
 

que tendría como resultado:


Contenido Indice Cambios Enlaces Autor
 
Nacho Cabanes, 2005
Última versión en www.pobox.com/users/ncabanes