23 octubre 2010

El bucle de juego

Un fuente con SDL puede resultar difícil de leer, y más aún si no está estructurado, sino que tiene toda la lógica dentro de "Main" y va creciendo arbitrariamente. Por eso, suele ser preferible crear nuestras propias funciones que la oculten un poco: funciones como "Inicializar", "CargarImagen", "ComprobarTeclas", "DibujarImagenes", etc.

Un "bucle de juego clásico" tendría una apariencia similar a esta:
  • Comprobar eventos (pulsaciones de teclas, clics o movimiento de ratón, uso de joystick...)
  • Mover los elementos del juego (personaje, enemigos, fondos móviles)
  • Comprobar colisiones entre elementos del juego (que pueden suponer perder vidas o energía, ganar puntos, etc)
  • Dibujar todos los elementos en pantalla en su estado actual
  • Hacer una pausa al final de cada "fotograma", para que la velocidad del juego sea la misma en cualquier ordenador, y, de paso, para ayudar a la multitarea del sistema operativo.

Por tanto, crearemos esas funciones, junto con otras auxiliares para inicializar el sistema, dibujar una imagen en pantalla oculta, escribir un texto en pantalla oculta… El resultado podría ser éste:




/*---------------------------*/
/* sdl05.cs */
/* */
/* Quinto acercamiento */
/* a SDL */
/* */
/* Por Nacho Cabanes */
/*---------------------------*/

using Tao.Sdl;
using System; // Para IntPtr (puntero: imágenes, etc)

public class Juego
{
short anchoPantalla = 800;
short altoPantalla = 600;
bool terminado = false;
short x = 400;
short y = 300;
short anchoImagen = 50;
short altoImagen = 50;
Sdl.SDL_Event suceso;
int numkeys;
byte[] teclas;
IntPtr pantallaOculta;
IntPtr tipoDeLetra;
IntPtr imagen;

void inicializar()
{
int bitsColor = 24;
int flags = Sdl.SDL_HWSURFACE | Sdl.SDL_DOUBLEBUF | Sdl.SDL_ANYFORMAT
| Sdl.SDL_FULLSCREEN;

// Inicializamos SDL
Sdl.SDL_Init(Sdl.SDL_INIT_EVERYTHING);
pantallaOculta = Sdl.SDL_SetVideoMode(
anchoPantalla,
altoPantalla,
bitsColor,
flags);
// Y SdlTTF, para escribir texto
SdlTtf.TTF_Init();

// Indicamos que se recorte lo que salga de la pantalla oculta
Sdl.SDL_Rect rect2 =
new Sdl.SDL_Rect(0,0, (short) anchoPantalla, (short) altoPantalla);
Sdl.SDL_SetClipRect(pantallaOculta, ref rect2);

// Cargamos una imagen
imagen = SdlImage.IMG_Load("personaje.png");
if (imagen == IntPtr.Zero) {
System.Console.WriteLine("Imagen inexistente: personaje.png!");
Environment.Exit(4);
}

// Y un tipo de letra, en tamano 18
tipoDeLetra = SdlTtf.TTF_OpenFont("FreeSansBold.ttf", 18);
if (tipoDeLetra == IntPtr.Zero) {
System.Console.WriteLine("Tipo de letra inexistente: FreeSansBold.ttf!");
Environment.Exit(5);
}

}

void comprobarTeclas()
{
// Comprobamos sucesos
Sdl.SDL_PollEvent(out suceso);
teclas = Sdl.SDL_GetKeyState(out numkeys);

// Miramos si se ha pulsado alguna flecha del cursor
if (teclas[Sdl.SDLK_UP] == 1)
y -= 2;
if (teclas[Sdl.SDLK_DOWN] == 1)
y += 2;
if (teclas[Sdl.SDLK_LEFT] == 1)
x -= 2;
if (teclas[Sdl.SDLK_RIGHT] == 1)
x += 2;
if (teclas[Sdl.SDLK_ESCAPE] == 1)
terminado = true;
}


void comprobarColisiones()
{
// Todavia no comprobamos colisiones con enemigos
// ni con obstaculos
}


void moverElementos()
{
// Todavia no hay ningun elemento que se mueva solo
}

void dibujarElementos()
{
// Borramos pantalla
Sdl.SDL_Rect origen = new Sdl.SDL_Rect(0,0,
anchoPantalla,altoPantalla);
Sdl.SDL_FillRect(pantallaOculta, ref origen, 0);

// Dibujamos la imagen en sus nuevas coordenadas
dibujarImagenOculta(imagen,x,y, anchoImagen, altoImagen);

// Escribimos el texto, como imagen
escribirTextoOculta("Texto de ejemplo",
/* Coordenadas */ 200,100,
/* Colores */ 50, 50, 255,
tipoDeLetra
);

// Mostramos la pantalla oculta
Sdl.SDL_Flip(pantallaOculta);
}


void pausaFotograma()
{
// Esperamos 20 ms
System.Threading.Thread.Sleep( 20 );
}


void dibujarImagenOculta(IntPtr imagen, short x, short y,
short ancho, short alto)
{
Sdl.SDL_Rect origen = new Sdl.SDL_Rect(0, 0, ancho, alto);
Sdl.SDL_Rect dest = new Sdl.SDL_Rect(x, y, ancho, alto);
Sdl.SDL_BlitSurface(imagen, ref origen, pantallaOculta, ref dest);
}


void escribirTextoOculta(string texto,
short x, short y, byte r, byte g, byte b, IntPtr fuente)
{
// Creamos una imagen a partir de ese texto
Sdl.SDL_Color color = new Sdl.SDL_Color(r, g, b);
IntPtr textoComoImagen = SdlTtf.TTF_RenderText_Solid(
fuente, texto, color);
if (textoComoImagen == IntPtr.Zero){
System.Console.WriteLine("No se puedo renderizar el texto");
Environment.Exit(6);
}
dibujarImagenOculta(textoComoImagen, x,y, (short) (800-x), (short) (600-y));
}


bool partidaTerminada()
{
return terminado;
}


private static void Main()
{
Juego j = new Juego();
j.inicializar();

// Bucle de juego
do {
j.comprobarTeclas();
j.moverElementos();
j.comprobarColisiones();
j.dibujarElementos();
j.pausaFotograma();
} while (! j.partidaTerminada() );

// Finalmente, cerramos SDL
Sdl.SDL_Quit();

}

}