Mostrando entradas con la etiqueta SDL. Mostrar todas las entradas
Mostrando entradas con la etiqueta SDL. Mostrar todas las entradas

13 agosto 2012

Raspberry Pi y PyGame - Introducción

El fin de semana del 21 de julio (de 2012), un grupo de adolescentes decidió usar su Raspberry Pi y el lenguaje Python para hacer un marathon de programación colaborativa de 24 horas, que llamaron "Raspithon". Usaron la biblioteca de juegos Pygame (basada en SDL) para crear un juego de asteroides, que iban programando entre 4 personas, usando un repositorio Git como punto en el que centralizar todo lo que iban creando.

Puedes leer la propuesta que hicieron, el post que habla sobre los resultados e incluso descargar el programa resultante desde GitHub.

Si cuatro chavales de entre 12 y 16 años, que ni siquiera se conocen entre ellos, son capaces de crear un juego... ¿es que cualquiera puede hacer un juego con Pygame? Sí y no. Python es más fácil de aprender que otros lenguajes como C, pero aun así necesita algo de dedicación si se quiere tener soltura. SDL es una biblioteca de un nivel bastante bajo, de modo que no es especialmente sencilla; Pygame la "encapsula" y la hace un poco más accesible, pero aun así no es trivial. Además, no todo tipo de juegos se podrá hacer con una cantidad de esfuerzo razonable: Pygame puede ayudar para juegos en 2D (plataformas, matamarcianos simples, etc) pero no simplifica las tareas adicionales que conllevaría crear un juego en 3D, de modo que un juego de este tipo puede suponer una cantidad de trabajo desproporcionada.

¿Y basta con un Raspberry Pi? Sí. La instalación básica de Raspbian (de fecha 15-07-2012) incluye Pygame, aunque sólo para la versión 2.7 de Python, pero debería ser suficiente.

¿Y si no tengo un Raspberry Pi... no puedo? Claro que sí. Python es multiplataforma. Cualquier Linux podría bastar (tendremos que usar Synaptic o nuestro gestor de paquetes para instalar "python-pygame"). Incluso en Windows debería ser fácil de instalar... aunque yo lo he probado (¿aún?).

Un programa básico en Python, usando PyGame para que un "marciano" rebote en la pantalla podría ser así:

import pygame, sys
pygame.init()

ancho = 800
alto = 600
velocidadX = 3
velocidadY = 3

pantalla = pygame.display.set_mode( (ancho, alto) )

imagen = pygame.image.load("spaceinvader.png")
rectanguloImagen = imagen.get_rect()

while 1:
    for event in pygame.event.get():
        if event.type == pygame.QUIT: sys.exit()

    rectanguloImagen = rectanguloImagen.move( (velocidadX, velocidadY) )
    if rectanguloImagen.left < 0 or rectanguloImagen.right > ancho:
        velocidadX = -velocidadX
    if rectanguloImagen.top < 0 or rectanguloImagen.bottom > alto:
        velocidadY = -velocidadY

    pantalla.fill( (0,0,0) )
    pantalla.blit(imagen, rectanguloImagen)
    pygame.display.flip()


En mi caso, ha bastado con teclear esto en el editor Geany, guardarlo con el nombre "rebota1.py" y pulsar el botón "ejecutar".

El fuente debería ser fácil de seguir incluso si no conoces Python:
  • "Importamos" las bibliotecas que vamos a usar: pygame (por supuesto) y "sys", que usaremos para abandonar el programa cuando sea necesario: import pygame, sys
  • Inicializamos pygame: pygame.init()
  • Declaramos los que serán el ancho y el alto de la pantalla de juego, en puntos, porque los usaremos para preparar la pantalla pero también para que "rebote" nuestro "marciano": ancho = 800, alto = 600
  • Preparamos también la velocidad a la que se moverá el "marciano" (la cantidad de puntos que se desplazará en cada "fotograma" del supuesto "juego"): velocidadX = 3, velocidadY = 3
  • Preparamos la pantalla (el ancho y el alto se deben indicar entre paréntesis, porque no son dos números aislados, sino un "par"): pantalla = pygame.display.set_mode( (ancho, alto) )
  • Cargamos la imagen que representará nuestro marciano (esa imagen deberá existir y estar en la misma carpeta que se encuentra nuestro fuente): imagen = pygame.image.load("spaceinvader.png")
  • Cada imagen estará contenida en un "rectángulo", que será el que movamos: rectanguloImagen = imagen.get_rect()
  • Y empieza la parte que se va a repetir indefinidamente: while 1:
  • Revisamos todos los "sucesos" del sistema; si alguno de ellos es una petición de salir del programa (porque se pulse la X de la ventana), salimos al sistema:    for event in pygame.event.get():   if event.type == pygame.QUIT: sys.exit()
  • En cada fotograma, desplazamos el "marciano" a la que será su siguiente posición:      rectanguloImagen = rectanguloImagen.move( (velocidadX, velocidadY) )
  • Si se sale por un lado de la pantalla o por el opuesto, en horizontal, cambiamos su "velocidad", para que pase a moverse en dirección contraria:  if rectanguloImagen.left < 0 or rectanguloImagen.right > ancho:  velocidadX = -velocidadX
  • Y hacemos las mismas comprobaciones en vertical: if rectanguloImagen.top < 0 or rectanguloImagen.bottom > alto: velocidadY = -velocidadY
  • Rellenamos la pantalla de color negro (el color (0,0,0), sin nada de rojo, verde ni azul, es el negro):  pantalla.fill( (0,0,0) )
  • Volcamos la imagen a la pantalla: pantalla.blit(imagen, rectanguloImagen)
  • Y hacemos que lo que hemos preparado en pantalla sea visible:  pygame.display.flip()
  • Todo eso se repetirá hasta que abandonemos el programa.

El resultado será pobre, pero es un comienzo:



¿Quieres más?  Deja un comentario... ;-)

(Ese fuente se basa en uno tomado de uno de los muchos tutoriales de Pygame disponibles en la red; en concreto, en uno de Pete Shinners; si quieres ver más detalles, aquí tienes un texto paso a paso).

10 agosto 2012

Raspberry Pi y SDL

Mi primer intento de recompilar para Raspberry Pi una aplicación basada en SDL (el emulador de Amstrad CPC llamado Caprice) no fue un éxito.

Resulta que en la instalación básica de Raspbian (al menos en la recopilación de 15-07-2012) no incluye SDLdev (la parte de SDL que es necesaria para compilar programas usando SDL). Pero no es grave, tampoco es difícil añadirlo. Los pasos son:

  • Actualizar (update) los componentes que ya están instalados.
  • Ampliar (dist-upgrade) la distribución con los nuevos paquetes que hayan decididos sus recopiladores.
  • Reiniciar el equipo.
  • Instalar SDLdev y el resto de herramientas de desarrollo.
  • Recompilar el fuente.
Los primeros cuatro pasos equivalen a las siguientes órdenes

sudo apt-get update
sudo apt-get dist-upgrade
sudo shutdown -r now
sudo apt-get install gcc build-essential libsdl1.2-dev

Y el último paso supone entrar a la carpeta en la que está el fuente (en mi caso, "cpc") y recompilarlo (en este caso con "make"):

cd cpc
make -f makefile.unix

Aparecerá algún mensaje de aviso si el fuente incluye construcciones que actualmente se consideren obsoletas (como en el caso de Caprice), pero aun así debería compilar correctamente, y aparecer un ejecutable llamado "cap32". Si lo lanzamos desde consola tecleando

./cap32

nos aparecerá la versión del emulador basada en consola:



Si lo lanzamos desde el entorno gráfico, usando el gestor ficheros ("file manager") que tenemos en la carpeta de accesorios ("accesories") del menú, aparecerá la versión del emulador para entorno gráfico:


Esto no lo he descubierto yo desde cero, sino gracias a la ayuda de conversaciones mantenidas en los propios foros de Raspberry Pi y también a partir de los esfuerzos de otros usuarios, que habían portado ya juegos como Quake3.

30 abril 2011

Bad image format exception en 64 bits (juegos con SDL que dejan de funcionar)


Los períodos de transición en el software suelen tener un problema: a veces las cosas dejan de funcionar por culpa de los cambios.

Hace poco me he encontrado con uno de esos casos: juegos creados en C# usando SDL, que funcionan correctamente en Windows XP, en Windows 7 de 32 bits... y que en cambio no funcionan en Windows 7 de 64 bits. En concreto, el mensaje de error que devuelven es "Bad image format exception" (excepción provocada por un formato incorrecto de imagen).

Si se tiene acceso al código fuente, es fácil de solucionar: hay que cambiar la plataforma de destino de nuestro proyecto, para que en vez de ser "Any CPU" (cualquier procesador), sea "X86". En Visual Studio 2008 se hacia apenas en un par de clics, pero en Visual Studio 2010 está un poco más escondido.

Los pasos que habría que dar en Visual C# 2010 Express serían:

1) Abrir nuestro proyecto.


2) En la propia barra de herramientas deberíamos poder escoger el "destino" de nuestro proyecto: tanto si es una versión de depuración (Debug) o una definitiva (Release), como el tipo de procesador de destino, que en principio debería ser "Any CPU" (cualquier procesador).


3) Es posible que sólo tengamos la opción "Any CPU" y no se nos permita escoger "X86", que es el nombre genérico para un procesador de 32 bits, y que es la opción que deberemos elegir, porque es lo que esperan las librerías auxiliares que emplea nuestro juego. En ese caso, entramos al menú Generar, y a la opción "Administrador de configuración":



4) Si en la opción de "plataforma", desplegamos la lista correspondiente e "Any CPU", veremos que no tenemos más opciones disponible, pero que podemos añadirlas (con la opción "Nueva..."):



5) Entonces nos mostrarán las demás plataformas que podemos escoger, y que deberían ser Itanium, X64, y (como pretendíamos) X86:


6) Ahora el "Administrador de configuración" debería parecerse más a lo que buscamos:


7) Y la propia barra de herramientas nos debería permitir escoger que nuestro proyecto sea para la plataforma X86:


8) Finalmente, no está de más comprobar que en nuestro proyecto ha quedado también "X86" como destino. Lo haríamos desde el menú "Proyecto", en la opción "Propiedades de...", y dentro de la pestaña "Generar":



Con eso, nuestro juego ya debería funcionar correctamente bajo Windows 64 bits.

¡ Suerte !

24 diciembre 2010

Creando un juego más modular usando C# y Tao.SDL

En entregas anteriores he comentado cómo dar crear el esqueleto de un juego usando C# y Tao.SDL. El problema de este planteamiento es que dejar todo el proyecto en un único fuente hace que sea cada vez más incómodo de manejar cuando se va haciendo más grande, y además imposibilita (casi) repartir trabajo entre varias personas.

Por eso, una alternativa más razonable cuando el proyecto no es muy pequeño, es descomponerlo en una serie clases que interaccionan.

Por ejemplo, en un juego de plataformas clásico, tendríamos un "Personaje" al que manejamos, una serie de "Enemigos" que nos atacan, "Premios" que recoger, quizá "Disparos" (o armas de otro tipo) con las que defendernos, todo ello coordinado por una clase "Juego", que se encargara de la lógica del juego: no sólo de repetir el bucle de juego sino de tomar las decisiones como:

  • Cuando nos toca un enemigo, ¿perdemos una vida o parte de la energía?
  • ¿El enemigo muere también cuando nos toca?
  • ¿Cuando se cambia de nivel? ¿Hay que recoger todos los premios? ¿Llegar a alguna puerta?
  • ¿Hay tiempo límite para acabar de recorrer un nivel? ¿Al agotar el tiempo perdemos una vida o toda la partida?
  • ...

Además, podríamos afinar un poco más y distinguir entre una "Partida" (una sesión de juego) y el conjunto del "Juego" (que también podría contener una pantalla de presentación, una de créditos sobre los autores, opciones para personalizarlo...).

Si somos prácticos, podríamos incluso crear una clase "Imagen", que ocultara los detalles de la manipulación de imágenes con SDL, y una clase "Hardware", que ocultara los nombres en inglés de las funciones y que simplificara alguna tarea tediosa, como la escritura de texto. Así, además, costaría menos trabajo portar nuestro juego a otra biblioteca gráfica, como Allegro. Y por encima de estas clases auxiliares podríamos crear otras más versátiles. Por ejemplo, por encima de "Imagen" podríamos crear una clase "ElementoGrafico", que permitiera figuras animadas (formadas por una secuencia de imágenes), que tuviera comprobaciones básicas de colisiones, etc.

¿Y como se puede hacer todo esto? ¿Hay algún fuente de ejemplo? Por supuesto, pero como sería demasiado largo leer todos los fuentes aquí, puedes descargar un proyecto de ejemplo en la página del "remake de Manic Miner", alojada en Google Code.

La versión 0.01 es sólo un esqueleto básico que muestra cómo podrían ser las clases antes mencionadas, tanto las "de infraestructura" (Hardware, Imagen, ElementoGrafico y alguna otra para manejo de tipos de letra y de sonidos) como las de "lógica de juego" (Juego, Presentacion, Creditos, Partida, Personaje). Versiones posteriores irán ampliando este proyecto poco a poco hasta llegar a algo que realmente sea "jugable":


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();

}

}

19 octubre 2010

Imágenes PNG y JPG con Tao.SDL

Sabemos mostrar imágenes en formato BMP desde un programa en C# que use Tao.Sdl. Pero las imágenes en este formato ocupan mucho espacio, y no permiten características avanzadas, como la transparencia (aunque se podría imitar). Si queremos usar imágenes en formatos más modernos, como JPG o PNG, sólo tenemos que incluir unos cuantos ficheros DLL más y hacer un pequeño cambio en el programa.

Los nuevos ficheros que necesitamos son: SDL_image.dll (el principal), libpng13.dll (para imágenes PNG), zlib1.dll (auxiliar para el anterior) y jpeg.dll (si queremos usar imágenes JPG - pero cuidado, quizá SDL_image.dll provoque un mensaje de error si no está también esta DLL, así que puede ser interesante dejarla también en la carpta del proyecto, incluso si no empleaños imágenes JPG).

En el fuente, sólo cambiaría la orden de cargar cada imagen, que no utilizaría Sdl.SDL_LoadBMP sino SdlImage.IMG_Load:

imagen = SdlImage.IMG_Load("personaje.png");

13 octubre 2010

Fuentes TTF con SDL y C#

Si queremos escribir texto usando tipos de letra TrueType (los habituales en Windows y Linux), los cambios no son grandes:

Debemos incluir el fichero DLL llamado SDL_ttf.DLL en la carpeta del ejecutable de nuestro programa, así como el fichero TTF correspondiente a cada tipo de letra que queramos usar.

Tenemos que declarar un nuevo dato que será nuestro tipo de letra, del tipo genérico "IntPtr":

IntPtr tipoDeLetra;


También tenemos que inicializar SdlTtf después de la inicialización básica de SDL:

SdlTtf.TTF_Init();


Luego preparamos el tipo de letra que queremos usar, indicando a partir de qué fichero TTF y en qué tamaño:

// 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);
}


A continuación, podemos crear una imagen a partir de una frase:

// Y creamos una imagen a partir de ese texto
Sdl.SDL_Color colorAzulIntenso = new Sdl.SDL_Color(50, 50, 255);
string texto = "Texto de ejemplo";
IntPtr textoComoImagen = SdlTtf.TTF_RenderText_Solid(
tipoDeLetra, texto, colorAzulIntenso);

if (textoComoImagen == IntPtr.Zero) {
System.Console.WriteLine("No se puedo renderizar el texto");
Environment.Exit(6);
}


Y podríamos dibujar esa imagen en cualquier parte de la pantalla, como cualquier otra imagen:

// Escribimos el texto, como imagen
origen = new Sdl.SDL_Rect(0,0,anchoPantalla,altoPantalla);
dest = new Sdl.SDL_Rect(200,100,anchoPantalla,altoPantalla);
Sdl.SDL_BlitSurface(textoComoImagen, ref origen,
pantallaOculta, ref dest);


Como esta forma de trabajar puede resultar engorrosa, dentro de poco lo mejoraremos, creando una clase "Fuente" que nos oculte todos estos detalles y nos permita escribir texto de forma sencilla.

11 octubre 2010

C#: Una imagen que se mueve con el teclado

Hemos visto lo básico de como mostrar imágenes en pantalla usando C# y SDL.

Un segundo ejemplo algo más detallado podría permitirnos mover el personaje con las flechas del teclado, a una velocidad de 50 fotogramas por segundo, así (tienes los comentarios tras el fuente):



/* Ejemplo de acceso a SDL
desde C# (2),
usando Tao.SDL

Por Nacho Cabanes
*/


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

public class Sdl02

{

private static void Main()
{


short anchoPantalla = 800;
short altoPantalla = 600;

int bitsColor = 24;
int flags = Sdl.SDL_HWSURFACE | Sdl.SDL_DOUBLEBUF | Sdl.SDL_ANYFORMAT
| Sdl.SDL_FULLSCREEN;

IntPtr pantallaOculta;

// Inicializamos SDL
Sdl.SDL_Init(Sdl.SDL_INIT_EVERYTHING);

pantallaOculta = Sdl.SDL_SetVideoMode(
anchoPantalla,
altoPantalla,
bitsColor,
flags);


// 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
IntPtr imagen;

imagen = Sdl.SDL_LoadBMP("personaje.bmp");
if (imagen == IntPtr.Zero) {

System.Console.WriteLine("Imagen inexistente!");
Environment.Exit(4);

}

// Parte repetitiva
bool terminado = false;
short x = 400;

short y = 300;
short anchoImagen = 50;

short altoImagen = 50;
Sdl.SDL_Event suceso;
int numkeys;

byte[] teclas;

do
{
// 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;


// Borramos pantalla
Sdl.SDL_Rect origen = new Sdl.SDL_Rect(0,0,

anchoPantalla,altoPantalla);
Sdl.SDL_FillRect(pantallaOculta, ref origen, 0);

// Dibujamos en sus nuevas coordenadas
origen = new Sdl.SDL_Rect(0,0,anchoImagen,altoImagen);

Sdl.SDL_Rect dest = new Sdl.SDL_Rect(x,y,

anchoImagen,altoImagen);
Sdl.SDL_BlitSurface(imagen, ref origen,
pantallaOculta, ref dest);


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

// Y esperamos 20 ms
System.Threading.Thread.Sleep( 20 );


} while (!terminado);

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

}

}


Las diferencias de este fuente con el anterior son:

  • Al inicializar, añadimos una nueva opción, Sdl.SDL_FULLSCREEN, para que el "juego" se ejecute a pantalla completa, en vez de hacerlo en ventana.
  • Usamos un bucle "do...while" para repetir hasta que se pulse la tecla ESC.
  • SDL_Event es el tipo de datos que se usa para comprobar "sucesos", como pulsaciones de teclas, o de ratón, o el uso del joystick.
  • Con SDL_PollEvent forzamos a que se mire qué sucesos hay pendientes de analizar.
  • SDL_GetkeyState obtenemos un array que nos devuelve el estado actual de cada tecla. Luego podemos mirar cada una de esas teclas accediendo a ese array con el nombre de la tecla en inglés, así: teclas[Sdl.SDLK_RIGHT]
  • SDL_FillRect rellena un rectángulo con un cierto color. Es una forma sencilla de borrar la pantalla o parte de ésta.


13 septiembre 2010

Juegos con C# usando SDL

SDL es una conocida biblioteca para la realización de juegos, que está disponible para diversos sistemas operativos y que permite tanto dibujar imágenes como comprobar el teclado, el ratón o el joystick, así como reproducir sonidos.

Tao.SDL es una adaptación de esta librería, que permite emplearla desde C#. Se puede descargar desde http://www.mono-project.com/Tao

SDL no es una librería especialmente sencilla, y tampoco lo acaba de ser Tao.SDL, así que los fuentes siguientes pueden resultar difíciles de entender, a pesar de realizar tareas muy básicas. Por eso, muchas veces es preferible "ocultar" los detalles de SDL creando nuestras propias clases "Hardware", "Imagen", etc. Lo haremos más adelante, pero de momento v

amos a ver un primer ejemplo, básico pero completo, que muestre cómo entrar a modo gráfico, cargar una imagen, dibujarla en pantalla, esperar cinco segundos y volver al sistema operativo. Tras el fuente comentaré las principales funciones.




/* Ejemplo de acceso a SDL
desde C# (1),
usando Tao.SDL

Por Nacho Cabanes
*/


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

public class Juego

{

private static void Main()
{


short ancho = 800;
short alto = 600;

int bitsColor = 24;
int flags = (Sdl.SDL_HWSURFACE|Sdl.SDL_DOUBLEBUF|Sdl.SDL_ANYFORMAT);

IntPtr pantallaOculta;

// Inicializamos SDL
Sdl.SDL_Init(Sdl.SDL_INIT_EVERYTHING);

pantallaOculta = Sdl.SDL_SetVideoMode(
ancho,
alto,
bitsColor,
flags);


// Indicamos que se recorte lo que salga de la pantalla oculta
Sdl.SDL_Rect rect2 =
new Sdl.SDL_Rect(0,0, (short) ancho, (short) alto);

Sdl.SDL_SetClipRect(pantallaOculta, ref rect2);

// Cargamos una imagen
IntPtr imagen;

imagen = Sdl.SDL_LoadBMP("personaje.bmp");
if (imagen == IntPtr.Zero) {

System.Console.WriteLine("Imagen inexistente!");
Environment.Exit(4);

}

// Dibujamos la imagen
short x = 400;
short y = 300;

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);


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

// Y esperamos 5 segundos
System.Threading.Thread.Sleep( 5000 );


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

}

}



Algunas ideas básicas:

· SDL_Init es la rutina de inicialización, que entra a modo gráfico, con cierto ancho y alto de pantalla, cierta cantidad de colores y ciertas opciones adicionales.

· El tipo SDL_Rect define un "rectángulo" a partir de su origen (x e y), su ancho y su alto, y se usa en muchas operaciones.

· SDL_SetClipRect indica la zona de recorte (clipping) del tamaño de la pantalla, para que no tengamos que preocuparnos por si dibujamos una imagen parcialmente (o completamente) fuera de la pantalla visible.

· SDL_LoadBMP carga una imagen en formato BMP (si sólo usamos SDL "puro", no podremos emplear otros tipos que permitan menores tamaños, como el JPG, o transparencia, como el PNG). El tipo de dato que se obtiene es un "IntPtr" (del que no daemos más detalles), y la forma de comprobar si realmente se ha podido cargar la imagen es mirando si el valor obtenido es IntPtr.Zero (y en ese caso, no se habría podido cargar) u otro distinto (y entonces la imagen se habría leido sin problemas).

· SDL_BlitSurface vuelca un rectángulo (SDL_Rect) sobre otro, y lo usamos para ir dibujando todas las imágenes en una pantalla oculta, y finalmente volcar toda esa pantalla oculta a la pantalla visible, con lo que se evitan parpadeos (es una técnica que se conoce como "doble buffer").

· SDL_Flip vuelca esa pantalla oculta a la pantalla visible (el paso final de ese "doble buffer").

· Para la pausa no hemos usado ninguna función de SDL, sino Thread.Sleep, que ya habíamos comentado en el apartado sobre temporización.

· SDL_Quit libera los recursos (algo que generalmente haríamos desde un destructor).

Para compilar este ejemplo usando Mono, deberemos:

· Teclear (o copiar y pegar) el fuente.

· Copiar en la misma carpeta los ficheros DLL (Tao.Sdl.Dll y SDL.Dll) y las imágenes (en este caso, "personaje.bmp").

· Compilar con:

mcs sdl01.cs /r:Tao.Sdl.dll

Y si empleamos Visual Studio o SharpDevelop, tendremos que:

· Crear un proyecto de "aplicación de consola".

· Reemplazar nuestro programa principal por éste.

· Copiar el fichero Tao.Sdl.Dll a la carpeta de fuentes, y añadirlo a las referencias del proyecto (normalmente, pulsando el botón derecho del ratón en la vista de clases del proyecto y escogiendo la opción "Agregar referencia").

· Copiar en la carpeta de ejecutables (típicamente bin/debug) los ficheros DLL (Tao.Sdl.Dll y SDL.Dll) y las imágenes (en este caso, "personaje.bmp").

· Compilar y probar.

Si no vamos a usar imágenes comprimidas (PNG o JPG), ni tipos de letra TTF, ni sonidos en formato MP3, ni funciones adicionales de dibujo (líneas, recuadros, círculos, etc)., no necesitaremos ninguna DLL adicional.

Puedes descargar el fuente y el ejecutable, con las DLL y un personaje de ejemplo, aquí:

http://www.nachocabanes.com/fich/descargar.php?nombre=csharp_sdl_01.zip