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":


04 noviembre 2010

Compilar C# con Geany en Windows

Geany es un interesante editor de textos. Quizá con menos cantidad de posibilidades que otros como Notepad++, pero aun así muy potente y razonablemente sencillo de manejar.

Una de las mayores ventajas que tiene (para mi gusto) frente a Notepad++ es que es fácil compilar y ejecutar programas creados en distintos lenguajes de programación sin necesidad de salir del editor. Eso sí... a veces hay que afinarlo un poco:

En el caso de C#, viene preconfigurado para compilar con Mono, pero esto en Windows es menos inmediato de lo que podría parecer. Vamos a ver por qué y cómo solucionarlo:

La configuración relacionada con C# está en un fichero llamado "filetypes.cs", que se encuentra en la carpeta "data" de Geany (en "C:\Archivos de programa\Geany\data\filetypes.cs" si hacemos una instalación típica en Windows XP). Podemos abrir ese fichero desde el propio Geany.

Las dos últimas líneas de ese fichero deberían ser algo como:

compiler=mcs /t:winexe "%f" /r:System,System.Drawing
run_cmd=mono "%e.exe"

El problema es que esas dos líneas esperan compilar fuentes en C# usando Mono, algo que es inmediato en Linux, pero que no es tan trivial en Windows, porque supondría cambiar la configuración básica de Windows, para que la carpeta de Mono sea una de las carpetas de búsqueda del sistema.

Hay una alternativa sencilla, si tenemos instalada cualquier versión de la plataforma "punto net", algo que es casi seguro si tenemos algún Service Pack para Windows XP, o bien si tenemos una versión de Windows más moderna: podemos usar el compilador que se incluye con la propia plataforma.

Primero tenemos que encontrarlo: abrimos nuestro disco C (o el que tenga instalado el sistema operativo), entramos a la carpeta de Windows, y en ella debería estar una subcarpeta llamada "Microsoft.net", que a su vez contiene otra carpeta llamada "Framework", y en ésta encontraremos varias subcarpetas, una por cada versión de "punto net" que tengamos instalada:


Por ejemplo, en esta imagen se muestra una versión básica de Windows XP, que tiene las versiones v1.0.3705 y v1.1.4322. Entramos a cualquiera de esas carpetas (preferiblemente a una reciente, con un número de versión elevado) y comprobamos si en ella está el compilador, un fichero llamado "csc".

Si es así, ya sabemos cual es la ruta completa del compilador, algo como

C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\csc.exe

(no es necesario memorizarlo, podemos copiar y pegar desde la barra de dirección de nuestro explorador).

Entonces ya podemos modificar las dos líneas de configuración que nos interesan: para compilar bastará indicar la ruta anterior seguida por el nombre de nuestro fuente, y para lanzar el ejecutable resultante usaremos el nombre de nuestro fuente terminado en ".exe", así:

compiler=C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\csc.exe "%f"
run_cmd="%e.exe"

Por cierto, otras opciones de Geany que a mí me parecen cómodas son:

  • En Editar / Preferencias / Editor / Sangría: ancho 4 o 2, pero siempre relleno con Espacios en vez de Tabulaciones.
  • En Editar / Preferencias / Editor / Mostrar: marcador de líneas largas activado, en la columna 80, para que no descuadre al imprimir; si uso fuentes antiguos o de otros programadores, que quizá usen el carácter de tabulación en vez de espacios, marcaría también la opción de "Mostrar espacio en blanco".
  • También me parece cómodo que se cambien tabulaciones por espacios de forma automática y que se borren los espacios al final de cada línea cada vez que se guarda un fichero, algo que se puede conseguir desde Editar / Preferencias / Archivos
Para que los cambios sean efectivos, podemos salir del editor y volver a entrar, o bien usar la opción "Recargar configuración" del menú (esto sirve para los de apariencia; no para el "nuevo compilador" para fuentes en C#; para eso sí debemos salir del editor y volverlo a abrir).

Happy coding! ;-)


(Todo esto está probado con la versión 0.19.1 en Windows XP y Windows 7)

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

05 julio 2010

SQLite, C# y DataGridView


Hemos visto cómo mostrar datos de una base de datos de SQLite desde C#, tanto desde consola como usando Windows Forms y componentes básicos como los TextBox y los ListView.

Si nos basta con una "cuadrícula" similar a las de las hojas de cálculo, en la que podamos modificar y añadir datos, hay una forma muy sencilla de conseguirlo: desde el editor visual añadimos un DataGridView a nuestro formulario y conectarlo a nuestra tabla usando un SQLiteDataAdapter, un DataSet y un DataTable.

La apariencia sería esta


Y el fuente sería básicamente el mismo que el anterior, pero la función encargada de actualizar la lista de ciudades quedaría así:




 1: private void ActualizarListaCiudades()
2: {
3: conexion =

4: new SQLiteConnection
5: ("Data Source=personal.sqlite;Version=3;New=False;Compress=True;");
6: conexion.Open();

7:
8: // Lanzamos la consulta y preparamos la estructura para leer datos
9:
string consulta = "select * from ciudad";
10:

11: // Adaptador de datos, DataSet y tabla
12:
SQLiteDataAdapter db = new SQLiteDataAdapter(consulta, conexion);

13: DataSet ds = new DataSet();
14: ds.Reset();

15: DataTable dt = new DataTable();
16: db.Fill(ds);

17:
18: //Asigna al DataTable la primer tabla (ciudades)
19:
// y la mostramos en el DataGridView
20:
dt = ds.Tables[0];

21: dataGridView1.DataSource = dt;
22:
23: // Y ya podemos cerrar la conexion
24:
conexion.Close();

25: }

25 junio 2010

SQLite con C# desde Windows Forms



Después de ver cómo crear una aplicación de consola en C# que cree una base de datos con SQLite, guarde datos y los lea, vamos a ver cómo hacer algo similar en una aplicación "de ventana", usando Windows Forms.

Lo haremos usando componentes "normales", en vez de los predefinidos que permiten acceder a bases de datos. Tendremos una ventana principal, que muestre los datos actuales en un ListView, y que incluya un botón para añadir datos nuevos:


El botón de "Añadir" mostrará un segundo formulario, que será el que pida los datos:


Lo podemos hacer dando los siguientes pasos (en Visual C# 2008):
  • Crear un nuevo proyecto, que sea una "Aplicación de Windows Forms"
  • En la ventana principal, añadir un ListView y un botón.
  • Personalizar el ListView usando la pestaña de propiedades, de modo que tenga dos columnas (una para el código y otra para el nombre), y que como modo de visualización ("View") tenga el de "detalles" ("Details").
  • Crear una segunda ventana, desde el menú "Proyecto", en la opción "Agregar Windows Forms".
  • Añadir a esa ventana dos TextBox para los textos que introducirá el usuario, junto con sus Label para los textos aclaratorios y un botón para confirmar.
  • Añadir el fichero DLL a las referencias del proyecto (en la ventana del "Explorador de soluciones", pulsando el botón derecho sobre "References", y escogiendo el fichero System.Data.SQLite.DLL desde la pestaña "Examinar").
  • Completar el código fuente del proyecto.
En cuanto al código, las dos partes claves son la parte que lee los datos para rellenar el ListView y la parte que lanza la ventana auxiliar, toma de ella lo que teclee el usuario y después introduce esos datos.

La de rellenar el ListView (que se llamaría al final del contructor, y después de cada introducción de un nuevo dato) podría ser así:






 1:         private void ActualizarListaCiudades()
2: {

3: conexion =
4: new SQLiteConnection
5: ("Data Source=personal.sqlite;Version=3;New=False;Compress=True;");

6: conexion.Open();
7:
8: lstCiudades.Items.Clear();

9:
10: // Lanzamos la consulta y preparamos la estructura para leer datos
11:
string consulta = "select * from ciudad";
12: SQLiteCommand cmd = new SQLiteCommand(consulta, conexion);

13: SQLiteDataReader datos = cmd.ExecuteReader();
14: // Leemos los datos de forma repetitiva
15:
while (datos.Read())

16: {
17: string codigo = Convert.ToString(datos[0]);

18: string nombre = Convert.ToString(datos[1]);
19: // Y los mostramos

20:
ListViewItem item = new ListViewItem(codigo);
21: item.SubItems.Add(nombre);

22: lstCiudades.Items.Add(item);
23: }
24: conexion.Close();

25: }


Y la de añadir otro dato podría sería así:


 1:         private void InsertarDatos(string codigo, string nombre)
2: {

3:
4: string insercion; // Orden de insercion, en SQL
5:
SQLiteCommand cmd; // Comando de SQLite

6:

7: conexion =
8: new SQLiteConnection
9: ("Data Source=personal.sqlite;Version=3;New=False;Compress=True;");

10: conexion.Open();
11:
12: try
13: {

14: insercion = "INSERT INTO ciudad " +
15: "VALUES ('"+codigo+"','"+nombre+"');";

16: cmd = new SQLiteCommand(insercion, conexion);
17: cmd.ExecuteNonQuery();

18: }
19: catch (Exception e)
20: {
21: MessageBox.Show(

22: "No se ha podido insertar. Posiblemente un codigo esta repetido",
23: "Aviso");
24: }
25: conexion.Close();

26: }


De modo que la acción completa sobre el botón de "Añadir" sería algo como

1:         private void btAnadir_Click(object sender, EventArgs e)
2: {

3: ventanaAnadir.Limpiar();
4: ventanaAnadir.ShowDialog();

5: InsertarDatos(ventanaAnadir.GetCodigo(), ventanaAnadir.GetNombre());

6: ActualizarListaCiudades();
7: }


Y aquí puedes descargar el proyecto, con los dos fuentes, los ficheros DLL y el ejecutable: csharp_sqlite2.zip



13 junio 2010

SQLite desde C#

SQlite es un gestor de bases de datos, que permite hacer consultas usando el lenguaje SQL, y que se puede incluir como parte de un programa en C o C++, apenas incluyendo un fichero DLL adicional... o incluso ni eso, ya que realmente es una biblioteca que se puede "incrustar" como parte del programa.

Desde C# no puede usar la DLL de SQLite directamente, pero sí a través de otras como System.Data.Sqlite.Dll, que se puede encontrar en

http://sqlite.phxsoftware.com/

Con esa DLL, junto con la original de SQLite, podremos hacer programas en C# que nos permitan acceder a bases de datos y que sean fáciles de distribuir. SQLite se puede encontrar en

http://www.sqlite.org/

Vamos a ver un pequeño ejemplo de cómo crear una base de datos, cómo introducir datos y cómo mostrarlos. Primero veremos los fragmentos de código, luego un fuente completo, después cómo compilarlo y probarlo, y finalmente incluiré la descarga completa, con fuentes, ejecutable y DLLs.

Para conectar con la base de datos, crearíamos un objeto de tipo "SQLiteConnection":


conexion =
new SQLiteConnection
("Data Source=personal.sqlite;Version=3;New=True;Compress=True;");
conexion.Open();


Para consultas de creación y modificación, crearemos un objeto de tipo SQLiteCommand y lo ejecutaremos con "ExecuteNonQuery()":


string creacion = "CREATE TABLE ciudad "
+"(codigo VARCHAR(2) PRIMARY KEY, nombre VARCHAR(30));";
SQLiteCommand cmd = new SQLiteCommand(creacion, conexion);
cmd.ExecuteNonQuery();


Si se trata de una consulta que sí devuelve datos (un "select"), usaremos "ExecuteReader()" y leeremos datos de forma repetitiva con "while (datos.Read())"


string consulta = "select * from ciudad";
SQLiteCommand cmd = new SQLiteCommand(consulta, conexion);
SQLiteDataReader datos = cmd.ExecuteReader();
// Leemos los datos de forma repetitiva
while (datos.Read())
{
string codigo = Convert.ToString(datos[0]);
string nombre = Convert.ToString(datos[1]);
// Y los mostramos
Console.WriteLine("Codigo: {0}, Nombre: {1}",
codigo, nombre);
}


En los "ExecuteNonQuery", podemos comprobar la cantidad de datos afectados, lo que nos puede ayudar a descubrir errores:


cantidad = cmd.ExecuteNonQuery();
if (cantidad < 1)
Console.WriteLine("No se ha podido insertar");



Aun así, muchos tipos de errores, como una clave duplicada, pueden provocar que salte una excepción y se interrumpa el programa, por lo que sería recomendable incluir las órdenes de inserción dentro de un bloque "try...catch"


En cualquier caso, al terminar de acceder deberemos cerrar la conexión a la base de datos:


conexion.Close();





El fuente completo podría ser así:

// Ejemplo de acceso a SQLite desde C#
// Nacho Cabanes, junio 2010

using System;
using System.IO;
using System.Data.SQLite; //Utilizamos la DLL


public class pruebaSQLite01
{
static SQLiteConnection conexion;

private static void CrearBBDDSiNoExiste()

{
if (!File.Exists("personal.sqlite") )

{
// Creamos la conexion a la BD.
// El Data Source contiene la ruta del archivo de la BD
conexion =
new SQLiteConnection
("Data Source=personal.sqlite;Version=3;New=True;Compress=True;");

conexion.Open();

// Creamos la primera tabla
string creacion = "CREATE TABLE ciudad "
+"(codigo VARCHAR(2) PRIMARY KEY, nombre VARCHAR(30));";

SQLiteCommand cmd = new SQLiteCommand(creacion, conexion);
cmd.ExecuteNonQuery();

// Creamos la segunda tabla

creacion = "CREATE TABLE persona "
+"(codigo VARCHAR(2) PRIMARY KEY, nombre VARCHAR(30),"
+" codigoCiudad VARCHAR(2) );";
cmd = new SQLiteCommand(creacion, conexion);

cmd.ExecuteNonQuery();
}
else
{
// Creamos la conexion a la BD.

// El Data Source contiene la ruta del archivo de la BD
conexion =
new SQLiteConnection
("Data Source=personal.sqlite;Version=3;New=False;Compress=True;");
conexion.Open();

}
}


private static void InsertarDatos()

{

string insercion; // Orden de insercion, en SQL
SQLiteCommand cmd; // Comando de SQLite
int cantidad; // Resultado: cantidad de datos


try
{
insercion = "INSERT INTO ciudad "+
"VALUES ('t','Toledo');";
cmd = new SQLiteCommand(insercion, conexion);

cantidad = cmd.ExecuteNonQuery();
if (cantidad < 1)

Console.WriteLine("No se ha podido insertar");

insercion = "INSERT INTO ciudad "+
"VALUES ('a','Alicante');";

cmd = new SQLiteCommand(insercion, conexion);
cantidad = cmd.ExecuteNonQuery();

if (cantidad < 1)
Console.WriteLine("No se ha podido insertar");


insercion = "INSERT INTO persona "+
"VALUES ('j','Juan','t');";
cmd = new SQLiteCommand(insercion, conexion);

cantidad = cmd.ExecuteNonQuery();
if (cantidad < 1)

Console.WriteLine("No se ha podido insertar");

insercion = "INSERT INTO persona "+
"VALUES ('p','Pepe','t');";

cmd = new SQLiteCommand(insercion, conexion);
cantidad = cmd.ExecuteNonQuery();

if (cantidad < 1)
Console.WriteLine("No se ha podido insertar");

}
catch (Exception e)
{
Console.WriteLine("No se ha podido insertar");

Console.WriteLine("Posiblemente un código está repetido");
Console.WriteLine("Error encontrado: {0} ", e.Message);

}
}

private static void MostrarCiudades()

{
// Lanzamos la consulta y preparamos la estructura para leer datos
string consulta = "select * from ciudad";
SQLiteCommand cmd = new SQLiteCommand(consulta, conexion);

SQLiteDataReader datos = cmd.ExecuteReader();
// Leemos los datos de forma repetitiva
while (datos.Read())

{
string codigo = Convert.ToString(datos[0]);

string nombre = Convert.ToString(datos[1]);
// Y los mostramos

Console.WriteLine("Codigo: {0}, Nombre: {1}",
codigo, nombre);
}

}


private static void MostrarPersonas()
{

// Lanzamos la consulta y preparamos la estructura para leer datos
string consulta = "select * from persona";
SQLiteCommand cmd = new SQLiteCommand(consulta, conexion);

SQLiteDataReader datos = cmd.ExecuteReader();
// Leemos los datos de forma repetitiva
while (datos.Read())

{
string codigo = Convert.ToString(datos[0]);

string nombre = Convert.ToString(datos[1]);
// Y los mostramos

Console.WriteLine("Codigo: {0}, Nombre: {1}",
codigo, nombre);
}

}

private static void MostrarCiudadesYPersonas()
{

// Lanzamos la consulta y preparamos la estructura para leer datos
string consulta =
"select persona.nombre, ciudad.nombre "+
"from persona, ciudad "+
"where persona.codigoCiudad = ciudad.Codigo";

SQLiteCommand cmd = new SQLiteCommand(consulta, conexion);
SQLiteDataReader datos = cmd.ExecuteReader();

// Leemos los datos de forma repetitiva
while (datos.Read())
{
string nombrePersona = Convert.ToString(datos[0]);

string nombreCiudad = Convert.ToString(datos[1]);
// Y los mostramos

Console.WriteLine("{0} vive en {1}",
nombrePersona, nombreCiudad);
}

}

private static void CerrarBBDD()
{

conexion.Close();
}


public static void Main()

{
Console.WriteLine("Accediendo...");
CrearBBDDSiNoExiste();

Console.WriteLine("Añadiendo datos...");
InsertarDatos();
Console.WriteLine("Datos de ciudades:");

MostrarCiudades();
Console.WriteLine("Datos de personas:");
MostrarPersonas();

Console.WriteLine("Datos de personas y ciudades:");
MostrarCiudadesYPersonas();
CerrarBBDD();

}

}






Para compilar este fuente, deberemos incluir "System.Data.SqLite.dll" dentro de las referencias del proyecto. Esto se haría desde el propio entorno visual si usamos herramientas como Visual Studio o SharpDevelop, o bien añadiendo la opción /r si compilamos desde línea de comandos, por ejemplo usando Mono:

gmcs pruebaSQLite.cs /r:System.Data.SqLite.dll







Y aquí puedes descargar el fuente, los ficheros DLL y el ejecutable: csharp_sqlite.zip