Si alguien ha programado un videojuego sin una librería específica comprenderá lo difícil que es programar los diferentes apartados: animaciones, colisiones, controles, etc. Es obvio que un motor facilita enormemente la programación.
Para JavaScript existen muchísimas librerías para crear videojuegos, pero ninguno me convencía. Sin embargo, Phaser ofrece un motor completo y muy bien documentado, además con licencia de código abierto MIT, compatible con la GPL.
Phaser tiene todo lo necesario para que puedas terminar un videojuego simple en cuestion de minutos y, además, con una documentación excelente.
Una de las excusas mas repetidas por los usuarios de Windows para no pasarse a Linux es la falta de juegos, una verdad a medias pero gracias a Desura esto cambiará.
Desura es un plataforma de juegos, clon del conocido Steam (el cual puede ser instalado bajo Wine, si bien no funcionan todos los juegos) que recientemente salio en las portadas de varios blogs linuxeros ya que por fin los gamers linuxeros podíamos disponer de una plataforma de distribución digital de juegos de manera nativa.
En Desura podemos encontrar diversos grupos de usuarios, ademas de las dos oficiales (la que abarca todo Desura y la comunidad global linuxera) por lo que nos sera fácil encontrar gente con nuestros gustos (yo mismo cree una comunidad para los usuarios del cliente de Linux de habla hispana). Tanto las comunidades como en los perfiles de usuarios podemos encontrar un pequeño sistema de blog para comentar novedades, compartir puntos de vista, etc.
En la parte para juegos, la mayor parte de los que podemos encontrar son de los llamados juegos indies, pero ademas hay varios juegos libres como Xonotic o Smokin’ Guns, ademas de no ser caros (muchos no pasan de los 9,95 euros, incluso hay un rpg por 2,49 €). De momento la lista no es muy grande, pero que va aumentando y eso es algo de agradecer.
El resultado no es en absoluto profesional, pero espero que este ejemplo os haya demostrado lo fácil (o al menos no tan difícil) que puede llegar a ser desarrollar un juego. Existen otras muchas maneras de hacerlo, pero SDL es quizás la alternativa más potente en dos dimensiones de las que existen en el universo del software libre. Recordando siempre que podemos usarla conjuntamente con OpenGL para conseguir gráficos tridimensionales.
Otras opciones además de Allegro y ClanLib de las que he hablado, son por ejemplo plataformas destinadas expresamente al desarrollo de videojuegos, como el Proyecto Fénix (el compilador alternativo al famoso DIV propietario). Sin embargo, como ya comenté anteriormente, estas opciones pueden parecer más fáciles pero son menos potentes que SDL.
Finalmente, sólo nos queda darle continuidad a nuestro juego. En función de los objetivos que nos habíamos propuesto, es que los aviones vuelvan a aparecer por la parte superior de la pantalla una vez lleguen al final. Esto es bastante simple ya que solamente tenemos que copiar y pegar la inicialización de los aviones enemigos dentro de una condición que compruebe si ha llegado ya al final de la pantalla por la parte inferior.
Una vez ya tenemos los enemigos moviéndose, el doble búfer implementado e incluso si queremos la pantalla completa pasemos a establecer algún objetivo en el juego. Para este ejemplo el objetivo será evitar que nuestro avión se choque con los aviones enemigos. Una vez se choquen terminaremos el juego. Aunque esto en un juego real no pasaría dado que es un ejemplo didáctico, es la opción que tomamos. Podríamos también poner un número de vidas por ejemplo, pero esto ya alargaría demasiado el ejemplo (tened en cuenta que tendríamos entonces que escribir con letras y números en nuestra pantalla de SDL, lo que no es tan fácil). Lo que si haremos será que cada vez que desaparezcan los enemigos posicionarlos de nuevo en el comienzo para que vuelvan a aparecer.
Comprobando si existe colisión
Para comprobar si existe colisión calcularemos la distancia entre el jugador y cada uno de los enemigos. Para calcular la distancia, primero hallaremos el centro de cada uno de los enemigos y del jugador para posteriormente calcular la distancia euclidea entre ambos puntos.
Ya que las dimensiones son:
El centro de cada uno de ellos debe estar desplazado de la posición en la que están dibujados (teniendo en cuenta que SDL dibuja desde la posición superior izquierda del objeto):
Veamos cuál es la distancia límite a la que pueden estar el jugador y un enemigo (si la distancia es menor se considerará que ha habido una colisión y por lo tanto el juego se termina):
Es decir el punto en el que se tocan los extremos de las superficie enemigo con la superficie jugador. Este modo de calcular la distancia no es el más exacto, pero es bastante orientativo. Si quisiéramos calcular la distancia de manera más exacta deberíamos estudiar mejor la geometría y las dimensiones reales de nuestro dibujo (despreciando la parte con transparencia en los extremos).
La función que hace esta comprobación sería la siguiente:
int existeColision(int x, int y, int numeroEnemigos, struct posicion* posicionesEnemigos) { int i; int centroJugador[2]={x+30,y+21}; struct posicion* centroEnemigos; double distancia; centroEnemigos=(struct posicion*)malloc (sizeof(struct posicion)*numeroEnemigos); for(i=0; i < numeroEnemigos; i++) { centroEnemigos[i].x=posicionesEnemigos[i].x+25; centroEnemigos[i].y=posicionesEnemigos[i].y+20; distancia=sqrt(pow((double)centroJugador[0]- centroEnemigos[i].x,2)+pow((double)centroJugador[1]-centroEnemigos[i].y,2)); if(distancia <= 69) return 1; } return 0; }
Habréis podido observar que los aviones parpadean al moverse por la pantalla. Esto se produce porque dibujamos directamente sobre la pantalla, es decir sobre lo que hemos llamado screen, de manera que dibujamos antes unos objetos y luego otros. Este problema es bien sencillo de solucionar con SDL gracias a la llamada técnica del doble búfer que nos ofrece. Mediante esta técnica dibujaremos sobre un búfer en el que una vez dibujados todos lo necesario, pasaremos a dibujar sobre el screen, de esta manera eliminaremos el parpadeo que creaba la técnica anterior.
¿Qué sería de un videojuego de este tipo sin enemigos? La verdad es que es difícil de imaginar por lo que aunque de una manera no demasiado cuidada vamos a incluirlos. Sabiendo ya incluir imágenes y moverlas, la operación es sencilla:
for(i=0;i<numeroEnemigos;i++)
{
enemigos[i]=SDL_LoadBMP(“enemigo.bmp”);
SDL_SetColorKey (enemigos[i], SDL_SRCCOLORKEY, SDL_MapRGB (enemigos[i]->format, MASK));
posicionesEnemigos[i].x=rand()%590+1;
posicionesEnemigos[i].y=60-rand()%40+1;
}
Veamos en una imagen cómo queda:
Y el código completo serían el siguiente:
#include <stdio.h> #include <stdlib.h> #include <SDL/SDL.h> #include <time.h> #define MASK 255, 255, 255 struct posicion{ int x; int y; }; int main() { SDL_Surface *screen; SDL_Surface *fondo; SDL_Surface *jugador; SDL_Surface **enemigos; struct posicion *posicionesEnemigos; SDL_Event event; SDL_Rect posicion; int terminar=0; int numeroEnemigos=10; int i, j; int posicionJugadorX=290, posicionJugadorY=430; Uint8* teclas; srand(time(NULL)); if (SDL_Init(SDL_INIT_VIDEO) < 0) { printf("No se pudo inicializar SDL: %s\n", SDL_GetError()); exit(1); } /*creando vector para contener enemigos*/ enemigos=(SDL_Surface**)malloc(sizeof(SDL_Surface*)*numeroEnemigos); /*y vector para posiciones de los mismos*/ posicionesEnemigos=(struct posicion*)malloc(sizeof(struct posicion) *numeroEnemigos); fondo = SDL_LoadBMP("fondo.bmp"); jugador = SDL_LoadBMP("avion.bmp"); SDL_SetColorKey (jugador, SDL_SRCCOLORKEY, SDL_MapRGB (jugador->format, MASK)); /*cargando graficos para enemigos y generando posiciones iniciales para los enemigos*/ for(i=0;i<numeroEnemigos;i++) { enemigos[i]=SDL_LoadBMP("enemigo.bmp"); SDL_SetColorKey (enemigos[i], SDL_SRCCOLORKEY, SDL_MapRGB (enemigos[i]->format, MASK)); posicionesEnemigos[i].x=rand()%590+1; posicionesEnemigos[i].y=60-rand()%40+1; } screen = SDL_SetVideoMode( 640, 480, 16, SDL_HWSURFACE ); if( screen == NULL ) { printf( "Error al entrar a modo grafico: %s\n", SDL_GetError() ); SDL_Quit(); return -1; } SDL_WM_SetCaption( "Incluyendo enemigos", "Incluyendo enemigos" ); while(!terminar) { for (i=0; i<10; i++) for (j=0; j<10; j++) { posicion.x=64*i; posicion.y=48*j; SDL_BlitSurface(fondo, NULL, screen, &posicion); } posicion.x=posicionJugadorX; posicion.y=posicionJugadorY; SDL_BlitSurface(jugador, NULL, screen, &posicion); for(i=0;i<numeroEnemigos;i++) { posicion.x=posicionesEnemigos[i].x; posicion.y=posicionesEnemigos[i].y; SDL_BlitSurface(enemigos[i], NULL, screen, &posicion); } SDL_Flip(screen); while ( SDL_PollEvent(&event) ) { if (event.type == SDL_QUIT || event.key.keysym.sym == SDLK_ESCAPE) terminar = 1; } teclas = SDL_GetKeyState(NULL); if (teclas[SDLK_UP]) { posicionJugadorY -= 2; } if (teclas[SDLK_DOWN]) { posicionJugadorY += 2;} if (teclas[SDLK_LEFT]) { posicionJugadorX -= 2; } if (teclas[SDLK_RIGHT]) { posicionJugadorX += 2; } for(i=0;i<numeroEnemigos;i++) { posicionesEnemigos[i].y+=2; } } SDL_Quit(); return 0; }
Tras cargar la imagen y quedarse estática en la pantalla os darán ganas de moverla. Aprendamos a como moverla por la pantalla con las flechas del teclado. Aprovecharemos para cambiar la forma de salirnos de la aplicación, ahora lo podremos hacer mediante la tecla ESC o haciendo clic en el cierre de la ventana. Veamos como en el caso anterior lo que debemos incluir:
De esta manera conseguiremos que nuestro avión se mueva a través de la pantalla siguiendo nuestras órdenes. Modificaremos nuestro fichero Makefile para poder compilar este ejemplo. El resultado es el mismo que antes pero podemos mover nuestro avión. El código completo:
#include <stdio.h> #include <stdlib.h> #include <SDL/SDL.h> #define MASK 255, 255, 255 int main() { SDL_Surface *screen; SDL_Surface *fondo; SDL_Surface *jugador; SDL_Event event; int terminar=0; SDL_Rect posicion; int i, j; int posicionJugadorX=320, posicionJugadorY=40; Uint8* teclas; if (SDL_Init(SDL_INIT_VIDEO) < 0) { printf("No se pudo inicializar SDL: %s\n", SDL_GetError()); exit(1); } fondo = SDL_LoadBMP("fondo.bmp"); jugador = SDL_LoadBMP("avion.bmp"); SDL_SetColorKey (jugador, SDL_SRCCOLORKEY, SDL_MapRGB (jugador->format, MASK)); screen = SDL_SetVideoMode(640, 480, 16, SDL_HWSURFACE ); if( screen == NULL ) { printf( "Error al entrar a modo grafico: %s\n", SDL_GetError() ); SDL_Quit(); return -1; } SDL_WM_SetCaption( "Moviendo nuestro avion", "Moviendo nuestro avion" ); while(!terminar) { for (i=0; i<10; i++) for (j=0; j<10; j++) { posicion.x=64*i; posicion.y=48*j; SDL_BlitSurface(fondo, NULL, screen, &posicion); } posicion.x=posicionJugadorX; posicion.y=posicionJugadorY; SDL_BlitSurface(jugador, NULL, screen, &posicion); SDL_Flip(screen); /*salida con ESC o cerrar ventana*/ while ( SDL_PollEvent(&event) ) { if (event.type == SDL_QUIT || event.key.keysym.sym == SDLK_ESCAPE) terminar = 1; } /* Vemos el estado indivudal de las teclas de la cruceta */ teclas = SDL_GetKeyState(NULL); if (teclas[SDLK_UP]) { posicionJugadorY -= 2; } if (teclas[SDLK_DOWN]) { posicionJugadorY += 2;} if (teclas[SDLK_LEFT]) { posicionJugadorX -= 2; } if (teclas[SDLK_RIGHT]) { posicionJugadorX += 2; } } SDL_Quit(); return 0; }
Ya hemos visto cómo cargar una ventana simple con SDL y ponerle título. No está mal para comenzar, pero estaréis deseando hacer algo más. Lo siguiente que haremos será cargar una imagen en pantalla operación que se repite multitud de veces en cualquier juego a la hora de cargar tanto fondos, como personajes.
Para carga una imagen haremos lo siguiente:
Ya tenemos la imagen cargada. El problema de cargarla de esta manera es que no tenemos transparencias y tendríamos la imagen nuestra, avión sobre fondo blanco, apareciendo sobre el fondo, azul (imitando al mar), por lo que alrededor del avión quedaría un recuadro blanco que sin duda quedaría muy mal. Para eliminar este fondo blanco y hacer que SDL lo cargue transparente hacemos lo siguiente:
De esta manera conseguimos un resultado así:
Veamos el código al completo:
#include <stdio.h> #include <stdlib.h> #include <SDL.h> #define MASK 255, 255, 255 int main() { SDL_Surface *screen; SDL_Surface *fondo; SDL_Surface *jugador; SDL_Event event; int terminar=0; SDL_Rect posicion; int i, j; if (SDL_Init(SDL_INIT_VIDEO) < 0) { printf("No se pudo inicializar SDL: %s\n", SDL_GetError()); exit(1); } fondo = SDL_LoadBMP("fondo.bmp"); jugador = SDL_LoadBMP("avion.bmp"); SDL_SetColorKey (jugador, SDL_SRCCOLORKEY, SDL_MapRGB (jugador->format, MASK)); screen = SDL_SetVideoMode( 640, 480, 16, SDL_HWSURFACE ); if( screen == NULL ) { printf( "Error al entrar a modo grafico: %s\n", SDL_GetError() ); SDL_Quit(); return -1; } SDL_WM_SetCaption( "Moviendo nuestro avion", " Moviendo nuestro avion" ); for (i=0; i<10; i++) for (j=0; j<10; j++) { posicion.x=64*i; posicion.y=48*j; SDL_BlitSurface(fondo, NULL, screen, &posicion); } posicion.x=320; posicion.y=400; SDL_BlitSurface(jugador, NULL, screen, &posicion); SDL_Flip(screen); while(terminar == 0) { while ( SDL_PollEvent(&event) ) { if ( event.type == SDL_KEYUP ) terminar = 1; } } SDL_Quit(); return 0; }