Intro a la programación de juegos

Por Nacho Cabanes

Contenido Indice Cambios Enlaces Autor

6. Evitemos esperar al teclado. Tercer juego: motos de luz.

Contenido de este apartado:


6.1. Ideas generales.

En la mayoría de los juegos no ocurre lo que en el anterior: no podemos permitirnos que el ordenador que "parado " esperando a que se pulse una tecla, sino que la acción debe proseguir aunque nosotros no toquemos el teclado.

Esto es muy fácil de conseguir. Entre la rutinas estándar de casi cualquier compilador de C tenemos la función kbhit( ), que devuelve "verdadero" (distinto de cero) si se ha pulsado alguna tecla y "falso" (cero) si no se ha pulsado ninguna. Posteriormente, con getch( ) sabríamos exactamente qué tecla es la que se ha pulsado y vaciaríamos el "buffer" (memoria intermedia) del teclado para que se pudieran seguir comprobando nuevas teclas.

En el caso de Allegro, la rutina que comprueba si hemos pulsado alguna tecla se llama keypressed( ), y en la práctica podríamos hacer cosas como esta para que el programa no se pare pero a la vez esté pendiente de qué teclas pulsemos:

do 
{
  /*Actualizar reloj y pantalla, mover nave, etc */
}
while  ( ! keypressed () );
teclaPulsada = readkey();

Al principio del programa deberemos añadir la línea "install_keyboard()" para tener acceso a estas rutinas de manejo de teclado. 


En el caso de Pascal (Free Pascal) es muy similar, incluso con los mismos nombres de funciones auxiliares (que están en la unidad CRT):

repeat 
  (* Actualizar reloj y pantalla, mover nave, etc *)
until  keypressed;

teclaPulsada := readkey;

Para Java.. existe una dificultad en este juego con una de las cosas que no hemos comentado aún, y que surgirá enseguida...

Vamos a aplicarlo a un primer juego de habilidad, el clásico de las "motos de luz" (si alguien recuerda la película Tron, quizá sepa a qué me refiero). La idea es la siguiente:

  • Participan dos jugadores.
  • Cada uno de los jugadores maneja una "moto", que va dejando un rastro cuando se desplaza.
  • Si una de las motos "choca" con el rastro dejado por la otra, o por ella misma, o con el borde de la pantalla, estalla y pierde la partida.
  • Por tanto, el objetivo es "arrinconar" al contrario sin ser arrinconado antes por él.
Con un poco más de detalle, lo que tendría que hacer nuestro programa será lo siguiente:
  • Borrar la pantalla y dibujar un recuadro alrededor de ella.
  • Preparar las variables iniciales: dirección de cada jugador (incremento en X e Y de la posición de cada uno). En el instante inicial, las motos se desplazarán en direcciones contrarias. En un juego "más real", este incremento podría ser variable (o disminuir el tiempo de pausa entre un movimiento y otro), de modo que las motos circularan cada vez más deprisa, para mayor dificultad.
  • Parte repetitiva:
    • Si la siguiente posición de alguna de las motos está ocupada, la moto estalla y ese jugador ha perdido: se acabó el juego.
    • Desplazar las motos a su siguiente posición.
    • Si se ha pulsado alguna tecla, analizar esta tecla y cambiar la dirección de la moto según corresponda.
    • Pausa fija (o variable según la dificultad) antes de la siguiente repetición, fijada por nuestro programa, para que la velocidad no dependa de la rapidez del ordenador que se utilice.
  • Repetir indefinidamente (la condición de salida la comprobamos "dentro").

Eso de que una moto choque con otra... suena difícil, pero en este primer caso nos bastará con mirar el punto de la pantalla al que nos vamos a mover. Si ese punto tiene un color distinto del fondo, habremos chocado (quizá sea con el borde de la pantalla, quizá con nuestra estela, quizá con la del otro jugador... pero eso nos da igual).

Ese es el problema en el caso de Java: no se puede leer un punto de la pantalla con esa facilidad, lo que nos obligaría a "memorizar" de otra forma las posiciones que no se pueden tocar. No es difícil, pero es algo que veremos dentro de poco, así que aplazamos un poco la versión en Java de este juego.


Lo único que aún no sabemos hacer es la pausa. Con C y Allegro usaremos la función

rest(n);

(donde "n" es el tiempo en milisegundos), que requiere que antes hayamos instalado el manejador de temporizadores (también será necesario para otras cosas, por ejemplo cuando accedamos al ratón, y al reproducir ciertos tipos de animaciones y de ficheros musicales) con:

install_timer():

En Pascal, la pausa de "n" milisegundos se haría con

delay(n);

Y en Java (que insisto que no usaremos aún en este ejemplo), la pausa sería

try
   {
   Thread.sleep( n );
   }
catch ( InterruptedException e )
   {
   }

pero nos obligará también a hacer algunos cambios más en la esctructura del fuente.

La apariencia del juego será también sencilla, así:


6.2 Las motos en C. 

Debería ser fácil de seguir...
 

/*----------------------------*/
/*  Intro a la programac de   */
/*  juegos, por Nacho Cabanes */
/*                            */
/*    IPJ06C.C                */
/*                            */
/*  Sexto ejemplo: juego de   */
/*  "motos de luz"            */
/*                            */
/*  Comprobado con:           */
/*  - Djgpp 2.03 (gcc 3.2)    */
/*    y Allegro 4.02 - MsDos  */
/*  - MinGW 2.0.0-3 (gcc 3.2) */
/*    y Allegro 4.02 - Win 98 */
/* - MinGW DevStudio 2.05     */
/*   (gcc 3.4.2) y Allegro    */
/*   4.03, Windows XP         */
/*----------------------------*/

#include <allegro.h>
 

  /* Posiciones X e Y iniciales de ambos jugadores */
#define POS_X_INI_1 150
#define POS_X_INI_2 170
#define POS_Y_INI_1 100
#define POS_Y_INI_2 100

#define INC_X_INI_1 -1
#define INC_X_INI_2  1
#define INC_Y_INI_1  0
#define INC_Y_INI_2  0

  /* Pausa en milisegundos entre un "fotograma" y otro */
#define PAUSA 150

  /* Teclas predefinidas para cada jugador */
#define TEC_ARRIBA_1 KEY_E
#define TEC_ABAJO_1  KEY_X
#define TEC_IZQDA_1  KEY_S
#define TEC_DCHA_1   KEY_D

#define TEC_ARRIBA_2 KEY_8_PAD
#define TEC_ABAJO_2  KEY_2_PAD
#define TEC_IZQDA_2  KEY_4_PAD
#define TEC_DCHA_2   KEY_6_PAD
 
 

int posX1, posY1, posX2, posY2;  /* Posiciones actuales */
int incX1, incY1, incX2, incY2;  /* Incremento de la posicion */
int futX1, futY1, futX2, futY2;  /* Posiciones futuras */

  /* Si ha chocado alguna moto */
int chocado;

  /* La tecla pulsada */
int tecla;
 

int main()
{

  allegro_init();            /* Inicializamos Allegro */
  install_keyboard();
  install_timer();

                             /* Intentamos entrar a modo grafico */
  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;
  }

                              /* Si todo ha ido bien: empezamos */

           /* Rectangulo amarillo alrededor */
  rect(screen,0,0,319,199, palette_color[14]);
 

           /* Valores iniciales */
  posX1 = POS_X_INI_1;
  posX2 = POS_X_INI_2;
  posY1 = POS_Y_INI_1;
  posY2 = POS_Y_INI_2;

  incX1 = INC_X_INI_1;
  incX2 = INC_X_INI_2;
  incY1 = INC_Y_INI_1;
  incY2 = INC_Y_INI_2;
 

           /* Parte repetitiva: */
  do {
    chocado = FALSE;

    /* Compruebo si alguna va a colisionar */
    futX1 = posX1 + incX1;
    futX2 = posX2 + incX2;
    futY1 = posY1 + incY1;
    futY2 = posY2 + incY2;

    if (getpixel(screen, futX1, futY1)!=0){
      textout(screen, font,
        "La moto 1 ha chocado!", 100,90, palette_color[13]);
      chocado =TRUE;
    }

    if (getpixel(screen, futX2, futY2)!=0){
      textout(screen, font,
        "La moto 2 ha chocado!", 100,110, palette_color[12]);
      chocado = TRUE;
    }

    if (chocado)break;
 

    /* Si ninguna ha colisionado, avanzan */
    line (screen, posX1, posY1, futX1, futY1, palette_color[13]);
    posX1 = futX1;  posY1 = futY1;

    line (screen, posX2, posY2, futX2, futY2, palette_color[12]);
    posX2 = futX2;  posY2 = futY2;
 

    /* Compruebo si se ha pulsado alguna tecla */
    if ( keypressed() ){
        tecla = readkey() >>8;

        switch(tecla){
          case TEC_ARRIBA_1:
            incX1 =  0; incY1 = -1;break;
          case TEC_ABAJO_1:
            incX1 =  0; incY1 =  1;break;
          case TEC_IZQDA_1:
            incX1 = -1; incY1 =  0;break;
          case TEC_DCHA_1:
            incX1 =  1; incY1 =  0;break;

          case TEC_ARRIBA_2:
            incX2 =  0; incY2 = -1;break;
          case TEC_ABAJO_2:
            incX2 =  0; incY2 =  1;break;
          case TEC_IZQDA_2:
            incX2 = -1; incY2 =  0;break;
          case TEC_DCHA_2:
            incX2 =  1; incY2 =  0;break;
        }
    }

    /* Pequeña pausa antes de seguir */
    rest ( PAUSA );

  }
  while (TRUE);/* Repetimos indefininamente */
               /* (la condición de salida la comprobamos "dentro") */

  readkey();
  return 0;

}

                 /* Termino con la "macro" que me pide Allegro */
END_OF_MAIN();





6.3. Las motos desde Pascal.

 Muuuuy parecido a la versión en C:

(*----------------------------*)
(*  Intro a la programac de   *)
(*  juegos, por Nacho Cabanes *)
(*                            *)
(*    IPJ06P.PAS              *)
(*                            *)
(*  Sexto ejemplo: juego de  *)
(*  "motos de luz"            *)
(*                            *)
(*  Comprobado con:           *)
(*  - FreePascal 1.10 (Dos)   *)
(* - FreePascal 2.0 -Windows *)
(*----------------------------*)

uses graph, crt;
(* Cambiar por "uses wincrt, ..." bajo Windows *)

const

  (* Posiciones X e Y iniciales de ambos jugadores *)
  POS_X_INI_1 = 150;
  POS_X_INI_2 = 170;
  POS_Y_INI_1 = 100;
  POS_Y_INI_2 = 100;

  INC_X_INI_1 = -1;
  INC_X_INI_2 =  1;
  INC_Y_INI_1 =  0;
  INC_Y_INI_2 =  0;

  (* Pausa en milisegundos entre un "fotograma" y otro *)
  PAUSA = 150;

  (* Teclas predefinidas para cada jugador *)
  TEC_ARRIBA_1 = 'E';
  TEC_ABAJO_1  = 'X';
  TEC_IZQDA_1  = 'S';
  TEC_DCHA_1   = 'D';

  TEC_ARRIBA_2 = '8';
  TEC_ABAJO_2  = '2';
  TEC_IZQDA_2  = '4';
  TEC_DCHA_2   = '6';


var
  posX1, posY1, posX2, posY2: integer;  (* Posiciones actuales *)
  incX1, incY1, incX2, incY2: integer;  (* Incremento de la posicion *)
  futX1, futY1, futX2, futY2: integer;  (* Posiciones futuras *)

  (* Si ha chocado alguna moto *)
  chocado: boolean;

  (* La tecla pulsada *)
  tecla:char;



var
  gd,gm, error : integer;


BEGIN
  gd := D8bit;
  gm := m320x200;
(* Si falla bajo Windows, probar gd:=0; gm:=0; *)
  initgraph(gd, gm, '');

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

                                (* Si todo ha ido bien: empezamos *)

            (* Rectangulo amarillo alrededor *)
  setcolor(14);
  rectangle(0,0, 319, 199);


            (* Valores iniciales *)
  posX1 := POS_X_INI_1;
  posX2 := POS_X_INI_2;
  posY1 := POS_Y_INI_1;
  posY2 := POS_Y_INI_2;

  incX1 := INC_X_INI_1;
  incX2 := INC_X_INI_2;
  incY1 := INC_Y_INI_1;
  incY2 := INC_Y_INI_2;


            (* Parte repetitiva: *)
  repeat
    chocado := FALSE;

    (* Compruebo si alguna va a colisionar *)
    futX1 := posX1 + incX1;
    futX2 := posX2 + incX2;
    futY1 := posY1 + incY1;
    futY2 := posY2 + incY2;

    if (getpixel(futX1, futY1) <> 0) then
      begin
      SetColor(13);
      OutTextXY( 100, 90,
        'La moto 1 ha chocado!');
      chocado := TRUE;
      end;


    if (getpixel(futX2, futY2) <> 0) then
      begin
      SetColor(12);
      OutTextXY( 100, 110,
        'La moto 2 ha chocado!');
      chocado := TRUE;
      end;

    if chocado then break;



    (* Si ninguna ha colisionado, avanzan *)
    setColor(13);
    line (posX1, posY1, futX1, futY1);
    posX1 := futX1;  posY1 := futY1;

    setColor(12);
    line (posX2, posY2, futX2, futY2);
    posX2 := futX2;  posY2 := futY2;


    (* Compruebo si se ha pulsado alguna tecla *)
    if  keypressed then
        begin
        tecla := upcase(readkey);

        case tecla of
          TEC_ARRIBA_1:
            begin incX1 :=  0; incY1 := -1;  end;
          TEC_ABAJO_1:
            begin incX1 :=  0; incY1 :=  1;  end;
          TEC_IZQDA_1:
            begin incX1 := -1; incY1 :=  0;  end;
          TEC_DCHA_1:
            begin incX1 :=  1; incY1 :=  0;  end;

          TEC_ARRIBA_2:
            begin incX2 :=  0; incY2 := -1;  end;
          TEC_ABAJO_2:
            begin incX2 :=  0; incY2 :=  1;  end;
          TEC_IZQDA_2:
            begin incX2 := -1; incY2 :=  0;  end;
          TEC_DCHA_2:
            begin incX2 :=  1; incY2 :=  0;  end;
  end;
    end;

    (* Pequeña pausa antes de seguir *)
    delay ( PAUSA );

  until FALSE;   (* Repetimos indefininamente *)
                 (* (la condición de salida la comprobamos "dentro") *)

  readkey();

end.



6.4. Problemas en el caso de Java.

Como hemos dicho en la introducción, en el caso de Java, la situación se complica ligeramente, porque no tenemos ninguna función que nos diga cual es el color de un punto de la pantalla. Así que para imitar el funcionamiento de las versiones anteriores, tendríamos que "memorizar" el contenido de cada punto de la pantalla. Supone guardar información sobre 320x200 = 64.000 puntos. Hay al menos un par de formas de hacerlo, y las dos son sencillas, pero para llevar un poco de orden, lo aplazamos... el próximo tema nos ayudará a entender lo que nos falta.

También será en el próximo apartado donde veamos cómo hacer eso de que nuestro juego "avance" cada cierto tiempo, incluso aunque no se pulse una tecla.

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