Intro a la programación de juegos

Por Nacho Cabanes

Contenido Indice Cambios Enlaces Autor

2. Entrando a modo gráfico y dibujando. 

Contenido de este apartado:


2.1. Pautas generales.


Los pasos básicos serán practicamente los mismos, usemos el lenguaje que usemos:
  • Deberemos entrar a modo gráfico, y además generalmente deberemos indicar cuantos puntos queremos en pantalla y cuantos colores. Esta elección no siempre es trivial: cuantos más puntos y más colores queramos en pantalla, más costará "mover" toda esa información, así que se necesitará un ordenador más rápido (no sólo el nuestro, también el de las demás personas que usen nuestro juego) y más habilidad por nuestra parte a la hora de programar.
  • Además tenemos el problema añadido de que no todos los ordenadores permiten todos los modos gráficos, por lo que deberíamos descubrir qué permite el ordenador "cliente" de nuestro juego y adaptarnos a sus posibilidades.
  • Una vez en modo gráfico, tendremos órdenes preparadas para la mayoría de las necesidades habituales... pero frecuentemente no para todo lo que se nos ocurra, así que alguna vez tendremos que crear nuestras propias rutinas en casos concretos.
  • Algunas órdenes a las que estamos acostumbrados no las podremos usar. Por ejemplo, es frecuente que no podamos usar desde modo gráfico la orden que se encarga de leer todo un texto que teclee un usuario (scanf en C, readln en Pascal, input en Basic). En caso de que queramos "imitar" el funcionamiento de esas órdenes, supondrá un trabajo adicional para nosotros (por ejemplo, en el caso de esa orden, se debería poder escribir texto, borrarlo en caso de error sin "estropear" el fondo, controlando también lo que ocurre al llegar al extremo de la pantalla o bien si se pulsan teclas "especiales" como las flechas y Esc).
Por otra parte, las órdenes más habituales que usaremos serán las siguientes:
  • Algunas propias de las bibliotecas gráficas, y que nos permitirán desde lo más básico (que es con lo que empezaremos), como dibujar líneas, rectángulos o escribir textos, hasta opciones avanzadas para representación y manipulación de imágenes planas y de figuras en tres dimensiones (que veremos más adelante).
  • Otras generales del lenguaje escogido, que van desde las órdenes "habituales" para controlar el flujo de un programa (si..entonces, repetir..hasta) hasta órdenes más específicas (como las que generan números al azar).
En este apartado comentaremos cómo se entra a modo gráfico y como se dibujan los elementos habituales (líneas, rectángulos, círculos, puntos, etc) con las herramientas que hemos escogido.

En primer lugar, utilizaremos el modo gráfico más estándar, que encontraremos en cualquier tarjeta gráfica VGA o superior (cualquier PC posterior al 1992 debería tenerla): 320x200 puntos, en 256 colores. Más adelante veremos cómo cambiar a otros modos que nos permitan obtener imágenes de mayor calidad.


2.2 Cómo hacerlo en el caso del lenguaje C y la biblioteca Allegro.

Los pasos básicos con C y Allegro son:

  • Inicialización: Entre los "includes" deberemos añadir <allegro.h>, y al principio de nuestro "main" la orden allegro_init();
  • Entrar a modo gráfico: set_gfx_mode(GFX_SAFE,ancho,alto,0,0). De momento sólo nos importan los dos primeros números: anchura y altura (en puntos) de la pantalla (320x200, por ahora). Esta función nos devolvera 0 si todo ha ido bien, o un valor distinto de cero si no se ha podido entrar al modo gráfico que hemos elegido.
  • Dibujar una línea entre dos puntos: line(screen,x1,y1,x2,y2, color);, donde x1, y1 son las coordenadas horizontal y vertical del primer punto, x2 e y2 son las del segundo punto. "screen" indica dónde queremos dibujar (en la pantalla, ya veremos otros usos). "color" es el color en el que queremos que se dibuje la línea; si queremos que sea un color de la paleta estándar, podemos usar construcciones como  palette_color[15] (el color 15 de la paleta estándar de un PC es el color blanco).
  • Salir del modo gráfico: añadiremos la línea END_OF_MAIN(); después de "main".

Vamos a verlo un ejemplo "que funcione", que dibuje una diagonal en pantalla: 
 
 
/*----------------------------*/
/*  Intro a la programac de   */
/*  juegos, por Nacho Cabanes */
/*                            */
/*    IPJ01C.C                */
/*                            */
/*  Primer ejemplo: entra a   */
/*  modo gráfico y dibuja una */
/*  línea diagonal en la pan- */
/*  talla.                    */
/*                            */
/*  - MinGW DevStudio 2.05    */
/*    (gcc 3.4.2) y Allegro   */
/*    4.03, Windows XP        */
/*----------------------------*/

#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;
  }

  line(screen,20,10,310,175, palette_color[15]);
  getch();
}

END_OF_MAIN();


Sólo hay dos cosas "nuevas" frente a lo que habíamos preparado:

  • En caso de error, salimos a modo texto (GFX_TEXT), mostramos un mensaje de error (una aviso nuestro más el mensaje de error preparado por Allegro, y usamos para ello la función auxiliar "allegro_message"), y salimos con el código de error 1.
  • Antes de terminar el programa, esperamos a que el usuario pulse una tecla, con la función "getch()", que no es exclusiva de Allegro, sino que se encuentra en "conio.h".


El resultado será simplemente este (recuerda que en el apartado 1 tienes la forma de teclear y compilar este fuente):

 

Demos un repaso rápido a las posibilidades más básicas de esta biblioteca, en lo que se refiere a modo gráfico. 

  • Dibujar un punto en un cierto color: void putpixel(BITMAP *bmp, int x, int y, int color); 
  • Dibujar una línea: void line(BITMAP *bmp, int x1, int y1, int x2, int y2, int color); 
  • Línea horizontal: void hline(BITMAP *bmp, int x1, int y, int x2, int color); 
  • Línea vertical: void vline(BITMAP *bmp, int x, int y1, int y2, int color); 
  • Recuadro: void rect(BITMAP *bmp, int x1, int y1, int x2, int y2, int color); 
  • Recuadro relleno: void rectfill(BITMAP *bmp, int x1, int y1, int x2, int y2, int color); 
  • Círculo: void circle(BITMAP *bmp, int x, int y, int radius, int color); 
  • Círculo relleno: void circlefill(BITMAP *bmp, int x, int y, int radius, int color); 
  • Elipse: void ellipse(BITMAP *bmp, int x, int y, int rx, int ry, int color); 
  • Elipse rellena: void ellipsefill(BITMAP *bmp, int x, int y, int rx, int ry, int color); 
  • Arco circular: void arc(BITMAP *bmp, int x, y, fixed ang1, ang2, int r, int color); 
(Como hemos comentado antes, el primer parámetro "BITMAP *bmp" especifica dónde se dibujará cada una de esas figuras; para nosotros lo habitual por ahora será indicar "screen" -la pantalla-). 

También podemos rellenar una cierta zona de la pantalla con 

  • void floodfill(BITMAP *bmp, int x, int y, int color); 


O leer el color de un punto con 

  • int getpixel(BITMAP *bmp, int x, int y); 


Para elegir modo de pantalla se usa la rutina que ya hemos visto: 

int set_gfx_mode(int card, int w, int h, int v_w, int v_h); 

El parámetro "card" (tarjeta) normalmente debería ser GFX_AUTODETECT (autodetección); w y h son la anchura (width) y altura (height) en puntos, y v_w y v_h son la anchura y altura de la pantalla "virtual", más grande que la visible, que podríamos utilizar para hacer algún tipo de scroll, y que nosotros no usaremos por ahora. 

Pero... ¿y el número de colores? Se indica con 

  • void set_color_depth(int depth); 
Donde "depth" es la "profundidad" de color en bits. El valor por defecto es 8 (8 bits = 256 colores), y otros valores posibles son 15, 16 (65.536 colores), 24 y 32 bits ("color verdadero"). 
 

Ahora vamos a ver un ejemplo algo más completo: 
 
 
/*----------------------------*/
/*  Intro a la programac de   */
/*  juegos, por Nacho Cabanes */
/*                            */
/*    IPJ02C.C                */
/*                            */
/*  Segundo ejemplo:          */
/*  Figuras básicas en modo   */
/*  640x480 puntos, 64k color */
/*                            */
/*  Comprobado con:           */
/*  - Djgpp 2.03 (gcc 3.2)    */
/*    y Allegro 4.02          */
/*----------------------------*/

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

main()
{

  allegro_init();

  set_color_depth(16);
  if (set_gfx_mode(GFX_SAFE,640,480,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;
  }

  line(screen,20,10,310,175, palette_color[15]);
  line(screen,639,0,0,479, palette_color[14]);

  rectfill(screen,30,30,300,200, palette_color[3]);

  ellipse (screen,320,200,50,100, palette_color[11]);

  getch();
}

END_OF_MAIN();

El resultado de este programa sería algo como

Hay muchas más posibilidades, pero las iremos viendo según las vayamos necesitando...

2.3. Cómo hacerlo en el caso de Free Pascal.

En Free Pascal sería:

  • Inicialización: Debemos incluir uses graph;  al principio de nuestro fuente "main" la orden allegro_init();
  • Entrar a modo gráfico: initgraph(gd, gm, '');. En esta orden "gd" es el "driver gráfico" a usar. Para 256 colores indicaríamos antes d := D8bit; (un poco más adelante veremos los principales drivers disponibles); "gm" es el modo de pantalla (referido a la cantidad de puntos), que en este caso sería gm := m320x200;
  • Dibujar una línea entre dos puntos: line(x1,y1,x2,y2), donde x1, y1 son las coordenadas horizontal y vertical del primer punto, x2 e y2 son las del segundo punto. Para indicar el color se la línea, usamos antes setcolor(color), donde "color" es un color de la paleta estándar (15 es el blanco).
  • Salir del modo gráfico: closeGraph; justo antes del fin del programa.
Vamos a verlo en la práctica:
 
(*----------------------------*)
(*  Intro a la programac de   *)
(*  juegos, por Nacho Cabanes *)
(*                            *)
(*    IPJ01P.PAS              *)
(*                            *)
(*  Primer ejemplo: entra a   *)
(*  modo gráfico y dibuja una *)
(*  línea diagonal en la pan- *)
(*  talla.                    *)
(*                            *)
(*  Comprobado con:           *)
(*  - FreePascal 2.0 -Windows *)
(*----------------------------*)

uses graph, crt;

var
  gd,gm, error : integer;


BEGIN
  gd := D8bit;
  gm := m320x200;
  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);
  line (20,10, 310,175);

  readkey;
  closeGraph;
END.

(Recuerda que en el apartado 1 tienes cómo teclearlo y probarlo).

Es posible que obtengamos el error "Invalid graphics mode". Sería porque nuestro ordenador no permite el modo gráfico que hemos elegido. Ante la duda, podemos hacer que lo detecte, usando gd:=0; gm:=0;

Igual que en el ejemplo en C, las únicas cosas que aparecen en este fuente y que no habíamos comentado aun son:

  • graphResult nos dice si el paso a modo gráfico ha sido correcto. Si devuelve un valor distinto de grOk, querrá decir que no se ha podido, y podremos ayudarnos de "graphErroMsg" para ver qué ha fallado.
  • readkey, de la unidad crt, espera a que pulsemos una tecla (en este caso lo usaremos antes de salir, para que tengamos tiempo de ver lo que aparece en pantalla).

Las posibilidades más básicas de la biblioteca gráfica de Free Pascal son: 

  • Dibujar un punto en un color: putpixel(x,y, color); 
  • Cambiar el color de dibujo: setColor(c);
  • Dibujar una línea en ese color: line(x1, y1, x2, y2); 
  • Recuadro: rectangle(x1, y1, x2, y2); 
  • Recuadro relleno (barra): bar(x1, y1, x2, y2);
  • Elegir tipo de relleno y color de relleno: setFillStyle(patron, color);
  • Círculo: circle(x, y, radio); 
  • Elipse: ellipse(x, y, anguloIni, anguloFin, radioX, radioY); 
  • Elipse rellena: fillEllipse(x, y, radioX, radioY); 
  • Rellenar una cierta zona de la pantalla: floodfill(x, y, colorBorde);
  • Leer el color de un punto: getpixel(x, y); (devuelve un valor de tipo Word) 


Para elegir modo de pantalla se usa la rutina que ya hemos visto: 

int initgraph( driver,  modo, situacDrivers); 

En su uso más sencillo, tenemos:

  • Driver indica la cantidad de colores: D1bit para blanco y negro, D2bit para 4 colores, D4bit para 16 colores, D8bit para 256 colores,  D16bit para 65536 colores. Existen otros modos que aún no están disponibles (24 bits, 32 bits), y alguno específico de ciertos ordenadores (como 4096 colores para Commodore Amiga).
  • Modo indica la cantidad de puntos en pantalla. Los que más usaremos son m320x200, m640x480, m800x600, pero existen otros muchos, algunos de los cuales son específicos de ciertos ordenadores (Amiga, Atari, Mac). 
  • SituacDeDivers se puede dejar en blanco (una cadena vacía, ' ') salvo si quisiéramos usar tipos de letra de los que Borland creó para Turbo Pascal, pero eso es algo que no haremos, al menos no por ahora.


Un ejemplo un poco más completo es: 
 
(*----------------------------*)
(*  Intro a la programac de   *)
(*  juegos, por Nacho Cabanes *)
(*                            *)
(*    IPJ02P.PAS              *)
(*                            *)
(*  Segundo ejemplo:          *)
(*  Figuras basicas en modo   *)
(*  640x480 puntos,16 colores *)
(*                            *)
(*  Comprobado con:           *)
(*  - FreePascal 2.0 -Windows *)
(*----------------------------*)

uses graph, crt;

var
  gd,gm, error : integer;
 

BEGIN
  gd := D4bit;
  gm := m640x480;
  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);
  line (20,10,310,175);

  setColor(14);
  line (639,0,0, 479);

  setFillStyle( SolidFill,3);
  bar(30,30,300,200);

  setColor(11);
  ellipse (320,200,0,360,50,100);

  readkey;
  closeGraph;
END.

El resultado de este programa, si compilamos para Windows, es el siguiente:
 


2.4. Cómo hacerlo en el caso de Java.

En el lenguaje Java las cosas cambian ligeramente. La sintaxis recuerda mucho a la de C, pero muchas de las ideas base son distintas:

  • Nuestros programas creados en Java podrán funcionar en cualquier equipo para el que exista una "máquina virtual Java", lo que supone que funcionarán en más de un equipo/sistema operativo distinto sin problemas y sin cambios.
  • Por el otro lado, la existencia de esta "capa intermedia" hace que nuestros programas puedan funcionar algo más lento si están creados en Java que si es en C++ o en Pascal. No es grave, con la velocidad que tienen los ordenadores actuales, y menos si nuestros juegos no son excesivamente complejos.
  • Además, en Java existe la posibilidad de crear aplicaciones capaces de funcionar "por sí mismas", o bien otras pequeñas aplicaciones que funcionan dentro de una página Web, que son los llamados "Applets". También existen versiones "limitadas" del lenguaje Java para pequeños dispositivos como ciertos teléfonos móviles o PDAs. Nosotros empezaremos por crear "Applets" y más adelante veremos qué cambia si queremos crear aplicaciones completas y si queremos hacer algo para nuestro teléfono móvil.
El ejemplo de cómo cambiar a modo gráfico y dibujar una diagonal en un Applet sería: 
 
/*----------------------------*/ 
/*  Intro a la programac de   */ 
/*  juegos, por Nacho Cabanes */ 
/*                            */
/*    ipj01j.java             */
 
/*                            */ 
/*  Primer ejemplo: entra a   */
/*  modo gráfico y dibuja una */
 
/*  línea diagonal en la pan- */ 
/*  talla.                    */ 
/*                            */ 
/*  Comprobado con:           */ 
/*  - JDK 1.5.0               */ 
/*----------------------------*/ 


import java.awt.*;

public class ipj01j extends java.applet.Applet {

    public void paint(Graphics g) {
        g.setColor( Color.yellow );
        g.drawLine( 20, 10,  310, 175 );
    }

}


(Recuerda que en el apartado 1 tienes cómo teclearlo y probarlo).


Como es un Applet, está diseñado para ser usado desde una página Web. Por tanto, tendremos que crear esa página Web. Bastaría con teclear lo siguiente desde cualquier editor de texto:


<html> 
 <head> 
     <title>IPJ - Ejemplo 1</title> 
 </head> 
 <body> 
     <h1>IPJ - Ejemplo 1</h1> 
     <hr> 
     <applet code=ipj01j.class width=320 height=200> 
       alt=El navegador no esta mostrando el APPLET 
      </applet> 
  </body> 
</html> 


Guardamos este fichero y hacemos doble clic para probarlo desde nuestro navegador. Si nuestro navegador no reconoce el lenguaje Java, veríamos el aviso "El navegador no está mostrando el APPLET", pero si todo ha ido bien, debería aparecer algo como


Las posibilidades más básicas de las rutinas gráficas de Java son muy similares a las que hemos visto con Allegro y con Free Pascal, con alguna diferencia. Por ejemplo, en los rectángulos no se indican las coordenadas de las dos esquinas, sino una esquina, la anchura y la altura. De igual modo, en las elipses no se indican el centro y los dos radios, sino como el rectángulo que las rodea. Detallando un poco más:

    // Escribir texto: mensaje, coordenada x (horizontal) e y (vertical)
    g.drawString( "Hola Mundo!", 100, 50 ); 

    // Color: existen nombres predefinidos
    g.setColor( Color.black );

    // Línea: Coordenadas x e y de los extremos
    g.drawLine( x1, y1, x2, y2 );

    // Rectángulo: Origen, anchura y altura
    g.drawRect( x1, y1, ancho, alto );

    // Rectángulo relleno: idéntico
    g.fillRect( x1, y1, ancho, alto );

    // Rectángulo redondeado: similar + dos redondeos
    g.drawRoundRect( x1, y1, x2, y2, rnd1, rnd2 ); 

    // Rectángulo redondeado relleno: igual
    g.fillRoundRect( x1, y1, x2, y2, rnd1, rnd2 );

    // Óvalo (elipse): Como rectángulo que lo rodea
    g.drawOval( x1, y1, ancho, alto );

    // Óvalo relleno: idéntico
    g.fillOval( x1, y1, ancho, alto );
 

Ahora vamos a ver un ejemplo algo más completo con alguna de ellas: 
 
/*----------------------------*/ 
/*  Intro a la programac de   */ 
/*  juegos, por Nacho Cabanes */ 
/*                            */
/*    ipj02j.java             */
 
/*                            */ 
/*  Segundo ejemplo:          */
/*  Figuras básicas en modo   */
/*  640x480 puntos            */
/*                            */
 
/*  Comprobado con:           */ 
/*  - JDK 1.4.2_01            */ 
/*----------------------------*/ 

import java.awt.*;

public class ipj02j extends java.applet.Applet {

    public void paint(Graphics g) {
        // Primero borro el fondo en negro
        g.setColor( Color.black );
        g.fillRect( 0, 0, 639, 479 );
        // Y ahora dibujo las figuras del ejemplo
        g.setColor( Color.white );
        g.drawLine( 20, 10,  310, 175 );
        g.setColor( Color.yellow );
        g.drawLine( 639, 0,    0, 479);
        g.setColor( Color.blue );
        g.fillRect( 30, 30, 270, 170 );
        g.setColor( Color.cyan );
        g.drawOval( 270, 100, 100, 200 );
    }

}
   

 

La página Web encargada de mostrar este Applet no tendría grandes cambios si comparamos con la anterior. Poco más que el nombre de la clase, los "cartelitos" y el tamaño:

<html> 
 <head> 
     <title>IPJ - Ejemplo 2</title> 
 </head> 
 <body> 
     <h1>IPJ - Ejemplo 2</h1> 
     <hr> 
     <applet code=ipj02j.class width=640 height=480> 
       alt=El navegador no esta mostrando el APPLET 
      </applet> 
  </body> 
</html> 


Y el resultado sería este:

Hay muchas más posibilidades, pero las iremos viendo según las vayamos necesitando...


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