ES

www.codigo-facil.com > Programar videojuegos con Object Pascal y SDL (1)

Programar videojuegos con Object Pascal, SDL y OpenGL


Capitulo 1: Herremientas y nuecleo principal  de nuestro juego.

Capitulos

Programar videojuegos con Object Pascal y SDL (1)

Primer capitulo de una serie en la que intentare explicar de forma sencilla los primeros pasos para desarrollar un mini juego en 2d.

En este articulo veremos las herramientas que vamos a utilizar y definiremos lo que sera el nucleo principal de nuestro juego.

Programar videojuegos con Object Pascal y SDL (2)

En este capitulo crearemos la clase sprite, que nos servira para poder dibujar los elementos que van a interactuar en nuestro juego.

Veremos como queda nuestro avion protagonista en la pantalla

Programar videojuegos con Object Pascal y SDL (3)

En este capitulo veremos como desplazar nuestro avion con las teclas del teclado. Ademas añadiremos un fondo al que aplicaremos un efecto para dar la sensacion de movimiento.

Introduccion


Antes de empezar quiero dar las gracias al portal Delphi al Limite por todas sus publicaciones, ya que gracias a dicho portal puedo hoy publicar este tutorial sobre de iniciacion a la programacion de videojuegos.

El objetivo de este tutorial es compartir mi modesta experiencia en la creacion del mini juego publicado en esta web, para dar las bases a aquellos que como yo tengan la curiosidad de como crear un videjuego y no saben como empezar.

Herramientas para el desarrollo


Para el desarrollo del mini juego vamos a utilizar Lazarus, que es un entorno de programacion basado en Object Pascal y que resulta ideal para aquellos que no tengan a su disposicion Delphi, ya que es una alternativa totalmente gratuita. Lo podeis descargar directamente desde su web.

Ademas vamos a incorporar al desarrollo la biblioteca SDL (version 2.0) para todo lo referente al mandejo de audio, video, openGL y el control del teclado y raton. Comentar que aunque vamos a desarrollar un juego sencillo en 2D, lo vamos a implementar utilizando openGL para aprovechar las ventajas que nos ofrece la tarjeta grafica y que asi nos sirva de introduccion para proyectos posteriores.

Para la utilizacion de la biblioteca SDL y todos sus componentes necesitaremos las traducciones a Pascal ya que dicha libreria esta realizada en C. En este enlace podeis descargar directamente las traducciones realizadas para la version 2.0. No olvideis que debeis incluir la ruta de dichos archivos para su correcta utilizacion. Pero antes de continuar debemos descarganos los archivos dll de la libreria SDL donde se encuentran las rutinas para su utilizacion. Estos archivos los podeis descargar en este enlace y basta con incluirlos en el directorio donde se encuentre el ejecutable del juego.

Por ultimo solo comentar que este proyecto es una modificacion del ya publicado en Delphi al Limite, y que recomiendo enormemente, sobre la programacion de videojuegos utilizando la libreria SDL, pero en mi caso voy a utilizar la version 2.0 del libreria SDL ademas de desarrollarlo en openGL. Aqui teneis el enlace para que le deis un vistazo por que es bastante interesante.

Creando el proyecto


Una vez que ya tenemos las herramientas que vamos a utilizar ya podemos ponermos mano a la obra. Vamos a empezar creando un nuevo proyecto en lazarus. Para ello clicamos en archivo y despues a nuevo. Normalmente se nos abre una ventana con las diferentes opciones, como nosotros no vamos a utuilizar la interfaz grafica seleccionamos directamente la opcion "Programa" :


A continuacion vamos a configurar nuestro programa para que se ejecute directamente en Win32. Para ello debemos activar la siguiente opcion dentro de las opciones de proyecto :



Creando la unidad U_SDL.pas


Para la ejecucion de cualquier juego vamos a necesitar una serie de funciones basicas para su correcto funcionamiento. Estas funciones las vamos a implementar dentro de una nueva unidad a la que llamaremos U_SDL.pas. Para ello vamos a crear una nueva unidad que incluiremos despues dentro de nuestro programa principal.

Cambiando el modo de video y activacion de OpenGL.

Vamos a empezar incluyendo el procedimiento que se encargara de cambiar el modo de video para la ejecucion de nuestro juego. Antes de empezar debemos incluir la libreria SDL en la calususla uses de nuestra unidad ya que vamos a empezar a utilizar algunas de sus funciones.

  1. (...)
  2. uses
  3. SDL2;
  4. (...)

Ahora ya podemos implementar el procedimiento sin que el compilador nos de error. Destacar que debemos ademas de implementar el procedimiento incluir su declaracion para poder invocarlo despues dentro del programa principal. Para declarar un procedimiento basta con incluir su nombre tal y como lo hemos implementado justo antes de la clausula Implementation dentro de la unidad.

  1. (...)
  2. procedure ModoVideo( ancho, alto, profundidadColor: Integer; ventana: Boolean );

  3. Implementation

  4. procedure ModoVideo( ancho, alto, profundidadColor: Integer; ventana: Boolean );
  5. begin
  6.   if SDL_Init( SDL_INIT_VIDEO )>=0 then
  7.   begin
  8.     {Configuracion basica}
  9.     SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 16 );
  10.     SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
  11.     SDL_SetHint ( SDL_HINT_RENDER_SCALE_QUALITY , 'lineal' ) ;
  12.     {activar la sincronizacion vertical y la aceleracion por hardware}
  13.     SDL_GL_SetSwapInterval(1);
  14.     SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL,1);

  15.     {
  16.     Cambiamos el modo de video segun la resolucion y el
  17.     tipo de visualizacion que hemos parametrizado
  18.     }
  19.     if ventana then
  20.       begin
  21.       pantalla := SDL_CreateWindow( 'Programa un videojuego con SDL' ,  SDL_WINDOWPOS_UNDEFINED ,  SDL_WINDOWPOS_UNDEFINED ,  ancho ,  alto ,  SDL_WINDOW_RESIZABLE or SDL_WINDOW_OPENGL);
  22.       end
  23.       else
  24.       begin
  25.       pantalla := SDL_CreateWindow( 'Programa un videojuego con SDL' ,  SDL_WINDOWPOS_UNDEFINED ,  SDL_WINDOWPOS_UNDEFINED ,  ancho ,  alto ,  SDL_WINDOW_FULLSCREEN or SDL_WINDOW_OPENGL);
  26.       end;

  27.     contexto := SDL_GL_CreateContext ( pantalla );

  28.     if Pantalla = nil then
  29.     begin
  30.       SDL_Quit;
  31.       exit;
  32.     end
  33.     else
  34.     begin
  35.       InicializarOpenGL(ancho, alto);
  36.     end;

  37.   end
  38.   else
  39.   begin
  40.     SDL_Quit;
  41.     exit;
  42.   end;
  43. end;
  44. (...)

Si nos fijamos en el procedimiento hemos hecho referencia a 2 variables que no han sido declaradas hasta el momento ( pantalla y contexto ), por lo que debemos añadirlas a nuestra unidad para evitar el error del compilador.

  1. (...)
  2. var
  3.   Pantalla: PSDL_Window;
  4.   contexto:SDL_GLContext;
  5. (...)

Como podemos ver hemos declarado la funcion ModoVideo con 4 parametros, ancho y alto para definir la resolucion de pantalla y otro para la profunndidad de color. Nosotros vamos a utilizar una resulicion de 640px x 480px y 16bits de prfundidad de color. El ultimo paramtero que utilizamos es para saber si vamos a ejecutar el juego en modo ventana (true) o a pantalla completa (false). Yo recomiendo en el desarrollo siempre ejecutar en mode ventana, para que nos sea mas facil parar la ejecucion del programa en caso de error.

Ademas si nos fijamos al final del procedimiento hemos invocado otra funcion (iniciarOpenGL) que vamos a implementar a continuacion y se encrga de preparar la escena para la ejecucion de openGL. Destacar que solo vamos a incluir la implementacion ya que al ejecutarse desde la misma unidad no es necesaria su declaracion.

Aunque antes de empezar debemos incluir la libreria openGL a nuestra unidad para poder utilizar sus funciones sin que el compilador nos de un error.

  1. (..)
  2. uses
  3. SDL2,
  4. gl; {para las funciones espedificas de openGL}
  5. (...)

Ahora ya podemos implementar la funcion tranquilamente.

  1. (...)
  2. Implementation

  3. procedure InicializarOpenGL(ancho, alto: Integer);
  4. begin
  5.   { Definimos el color de fondo }
  6.   glClearColor( 0, 0, 0, 0 );

  7.   {
  8.   Establecemos el area de renderizado de la pantalla
  9.   }
  10.   glViewPort(0, 0, ancho, alto);

  11.   {
  12.   Acitivamos el modo de fusion de pixeles y como estos se van a combinar
  13.   Esto es fundamental para pintar los sprites transparentes sin que tapen lo
  14.   que hay detras
  15.   }
  16.   glEnalble(GL_BLEND);
  17.   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  18.   {
  19.   Activamos el suabizado de bordes  para los poligonos, esto es importante
  20.   ya que aunque es un juego en 2D con sprites voy a utilizar triangulos para
  21.   el dibujo de los mismos.
  22.   }
  23.   glShadeModel(GL_SMOOTH);

  24.   {
  25.   Seleccionamos la matriz de proyeccion, vamos a definir el tipo
  26.   de proyeccion o perspectiva que vamos a utilizar, en este caso
  27.   es un tipo de proyeccion ortogonal, esto quiere decir, sin entrar en
  28.   mas detalles, que no vamos a ver los objetos con profundidad
  29.   o en perspectiva.
  30.   }
  31.   glMatrixMode( GL_PROJECTION );
  32.   { reseteamos los valores de la matriz proyeccion }
  33.   glLoadIdentity;
  34.   { definimos el tipo de proyeccion }
  35.   glOrtho(0,ancho,0,alto,0,1);

  36.   {
  37.   Seleccionamos la matriz de modelo, que es la que se encarga 
  38.   de dibujar los diferentes objetos en la escena
  39.   }
  40.   glMatrixMode( GL_MODELVIEW );
  41.   { Reseteamos la matriz }
  42.   glLoadIdentity;
  43. end;
  44. (...)


Informacion adicional

Como he comentado antes en el procedimiento que inicializa la configuracion inicial para openGl, hemos definido un tipo de proyeccion ortografica. Este tipo de proyeccion consiste en trazar lineas perpendiculares al plano de proyeccion que resultan ser paralelas entre si, con lo que los objetos no reducen su tamaño segun se alejan del plano, lo que se traduce en que en estas proyecciones no se preserva las dimensiones reales de los objetos. A continuacion os dejo una imagen de ejemplo para entender mejor este concepto y en que se diferencia de una proyeccion en perspectiva.


Limpiando y volcando el buffer

Una vez que ya tenemos los metodos para cambiar el modo el video, ahora necesitamos unos metodos para volcar el buffer donde se realizan las operaciones de dibujo a la pantalla y para limpiar el mismo. Al igual que sucedia anteriormente incluiremos la declaracion de estos metodos para poder utilizarlos en el programa principal. El codigo para su implementacion es el siguiente :

  1. (...)
  2. procedure limpiarBuffer;
  3. procedure volcarBuffer;
  4. (...)

  5. Implementation

  6. procedure limpiarBuffer;
  7. begin
  8.   glClear( GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT );
  9. end;

  10. procedure pintarBuffer;
  11. begin
  12.   SDL_GL_SwapWindow(pantalla);
  13. end;
  14. (...)


Control del tiempo.

Toda ejecucion de un juego se basa en la repeticion de una seria de rutinas hasta la finalizacion del mismo. Para que la experiencia de juego sea optima es necesario controlar este ciclo de repeticion de cada instruccion. Por este motivo vamos a añadir una nueva clase a nuestra unidad que se encarga de provocar un retardo entre la ejecucion de un ciclo y el siguiente.

Nosotros vamos a intentar ejecutar nuestro juego a 60 imagenes por segundo, asi que lo primero que vamos a hacer es añadir una constante a nuestra unidad para poder precisar los fotogramas a los que queremos que funcione nuestro juego. Asi siempre lo podemos cambiar sin problema para ver como funcionaria a diferentes velocidades. Esta constante que vamos a incluir tomara el valor de 16, que hace referencia a los milisegundos resultantes en de la division de 1000 milisegundos que tiene un 1 segundo y el numero de imagenes que queremos que se ejecute, en este caso 60.

  1. unit U_SDL;

  2. {$mode objfpc}{$H+} 

  3. interface

  4. uses
  5.   SDL2,
  6.   gl;

  7. const delay = 16;
  8. (...)

A continuacion vamos a definir la clase que vamos a utilizar para el control de los ciclos de ejecucion del bucle principal de nuestro juego.

  1. (...)
  2. const delay = 16;

  3. type
  4. TTemporizador = class
  5.   tempActual, tempUltimo: Integer;
  6.   procedure actualizar;
  7.   procedure esperar;
  8. end;
  9. (...)

La implementacion de los metodos de la clase TTemporizador la vamos a ver a continuacion :

  1. (...)
  2. Implementation
  3. (...)
  4. procedure TTemporizador.Actualizar;
  5. begin
  6.   tempActual := SDL_GetTicks;
  7. end;

  8. procedure TTemporizador.Esperar;
  9. var del : word;
  10. begin
  11.   del:=delay - ( SDL_GetTicks - tempActual );
  12.   if (del<=0)then
  13.   else
  14.   if (del>delay)then
  15.     del:=delay;
  16.   SDL_Delay(del);
  17. end;
  18. (...)

Como podemos comprobar tenemos 2 metodos uno para registrar un instante determinado, y otro que es para calcular el tiempo transcurrido entre el momento inicial, registrado en el primer metodo, y el instante en el que se ejecuta el segundo. Despues lo que hacemos es descontar el tiempo transcurrido al retardo que habiamos especificado por defecto en la constante "delay". Esto es asi para asegurarse que el ciclo se cumple con exactitud en cualquier sistema.

Por ultimo solo nos falta incluir la variable en nuestra unidad para poder utilizar la clase "TTemporizador"

  1. (...)
  2. var
  3.   Pantalla: PSDL_Window; 
  4.   contexto: SDL_GLContext; 
  5.   Temporizador: TTemporizador; 
  6. (...)

Control del teclado.

Ya hemos desarrollado alguna funciones principales para iniciar el juego, ahora es el turno de implementar los procedimientos necesarios para poder interactuar con nuestra aplicacion. Por este motivo vamos a crear una nueva clase que nos permita controlar el teclado.

Como ya hicimos anteriormente vamos a incorporar esta nueva clase a la interfaz de nuestra unidad.

  1. (...)
  2. type
  3. TTeclado = class
  4.   arriba, abajo, izquierda, derecha : boolean; { flechas de direccion }
  5.   espacio, intro : boolean;
  6.   evento : TSDL_Event;
  7.   ultTec : integer; { Registrar ultima tecla pulsada }

  8.   procedure leer;
  9. end;
  10. (...)

A continuacion vamos a implementar el metodo "leer" de la clase  TTeclado.

  1. (...)
  2. Implementation

  3. procedure TTeclado.Leer;
  4. begin
  5.   {
  6.   Detectamos si se ha producido un evento del teclado
  7.   }
  8.   if SDL_PollEvent( @Evento )>0 then
  9.   begin
  10.  
  11.     { Se ha pulsado una tecla}
  12.     if Evento.type_ = SDL_KEYDOWN then
  13.     begin
  14.       UltTec := evento.key.keysym.sym;
  15.  
  16.       case UltTec of
  17.         SDLK_ESCAPE: Salir := True;
  18.         SDLK_RIGHT: Derecha := True;
  19.         SDLK_LEFT: Izquierda := True;
  20.         SDLK_UP: Arriba := True;
  21.         SDLK_DOWN: Abajo := True;
  22.         SDLK_SPACE: Espacio := True;
  23.         SDLK_RETURN: Intro := True;
  24.       end;
  25.  
  26.     end;
  27.  
  28.     { Se ha dejado de pulsar una tecla}
  29.     if Evento.type_ = SDL_KEYUP then
  30.     begin
  31.       UltTec := 0;
  32.  
  33.       case Evento.key.keysym.sym of
  34.         SDLK_RIGHT: Derecha := False;
  35.         SDLK_LEFT: Izquierda := False;
  36.         SDLK_UP: Arriba := False;
  37.         SDLK_DOWN: Abajo := False;
  38.         SDLK_SPACE: Espacio := False;
  39.         SDLK_RETURN: Intro := False;
  40.       end;
  41.  
  42.     end;
  43.  
  44.   end;
  45. end;
  46. (...)

Si nos fijamos en el procedimiento hemos utilizado una nueva variable (salir) que aun no hemos declarado, esta variable la vamos a utilizar para saber cuando debemos cerrar el juego. En este caso cerraremos el juego cuando pulsemos la tecla "Escape", asi que basta con checkear la tecla al producirse el evento correspondiente para que cambie su valor cuando corresponda. 

Ademas tambien vamos a incluir una variable para poder utilizar la clase TTeclado en el bucle pricipal del juego. La declaracion de estas variables la vamos a incluir en la interfaz de la unidad como hemos hecho anteriormente.

  1. (...)
  2. var
  3.   Pantalla : PSDL_Window;
  4.   contexto : SDL_GLContext;
  5.   Temporizador : TTemporizador;
  6.   Teclado : TTeclado;
  7.   salir : boolean;
  8. (...)

Con la implementacion de la clase para el control del teclado hemos terminado de desarrollar esta unidad con las funciones genericas necesarias para la inicializacion del juego. Ahora ya podemos desarrollar lo que sera el bucle principal del juego.

Al final del capitulo inlcuire un descargable con el ejecutable del juego y los archivos fuentes con los avances realizados en cada capitulo para que se puede ver el resultado final. Pero he creido conveniente explicar un poco cada parte para que se sepa en cada momento lo que se hace y por que se hace, siempre abierto a consejos y sugerencias por vuestra parte claro esta.

Creando el bucle principal


Una vez que ya hemos desarrollado nuestra unidad para las funciones genericas, ahora ya podemos volver a la unidad principal de nuestro juego para desarrollar lo llamaremos como bucle principal. Seguramente a lo largo de los capitulos volveremos a este punto para ir añadiendo los nuevos procedimientos que vayamos implementando.

La estructura de nuestro programa principal sera la siguiente :

  1. program juego;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6. {$IFDEF UNIX}{$IFDEF UseCThreads}
  7. cthreads,
  8. {$ENDIF}{$ENDIF}
  9. Classes,
  10. { Mis unidades a partir de aqui }
  11. U_SDL; { Unidad para los procedimientso genericos }
  12.  
  13. begin
  14.   {
  15.   Cambio el modo de video, indicando ademas que se ejecute en modo ventana
  16.   Ademas creo los objetos para el control del teclado y el temporizador que me 
  17.   permite hacer la pausa entre cada ciclo de instrucciones
  18.   }
  19.   ModoVideo( 640, 480, 16, true );
  20.   Teclado := TTeclado.Create;
  21.   Temporizador := TTemporizador.Create;

  22.   Empieza el bucle principal del juego }
  23.   { El bucle se sigue ejecutando hasta que se pulse Escape }
  24.   while salir=false do
  25.   begin
  26.     { Registro el momento actual }
  27.     Temporizador.Actualizar;
  28.     { Limpio el buffer }
  29.     limpiarBuffer;
  30.     { Compruevo los eventos del teclado }
  31.     Teclado.Leer;
  32.     { Vuelco el buffer a la pantalla }
  33.     pintarBuffer;
  34.     { Hago la pausa hasta el siguiente ciclo de instrucciones }
  35.     Temporizador.esperar;
  36.   end;
  37.   { Fin del bucle principal y del juego }

  38. end

Como podemos ver no tiene mayor complicacion, hemos incluido la unidad que habiamos creado para poder invocar sus procedimientos. Despues hemos cambiado el modo de video y los objetos necesarios para poder controlar el teclado y utilizar el temporizador.

Despues en el bucle pricipal tan solo hemos hecho las tareas basicas como limpiar el buffer, comprobar el teclado, actualizar la pantalla y realizar la pausa. Realmente si ejecutamos el juego lo unico que nos aparecera es una ventana en negro hasta que demos a Escape para cerrarlo. En el siguiente capitulo seguiremos avanzando y veremos como crear una nueva unidad para el manejo de sprites, podremos ver el sprite que creemos en la pantalla.

Antes de terminar solo comentaros que en este enlace os dejo un archivo comprimido con los avances realizados hasta este capitulo. En ese archivo he incluido el ejecutable del juego ademas de todos los archivos fuentes para poder ser trasteados desde Lazarus.

Gracias por vuestro apoyo, espero este articulo os haya resultado de utilidad y nos vemos en la siguiente publicacion.

Ultimas noticias

Crea tu propio framework en javascript

Recopilacion de articulos donde mostrare paso a paso como podemos crear nuestro propio framework en javascript, totalmente funcional y listo para ser utilizado en nuestros futuros proyectos.

Para mas informacion :

Tutorial para crear tu propio FrameWork en JavaSript

Más información
13/12/2013 11:42:57

Como crear una DLL en delphi?

En esta serie de 2 capitulos veremos como crear y utilizar una DLL en Delphi.

Abajo os dejo los enlaces a estos 2 capitulos que componen este mini tutorial, espero que sea de vuestro agrado :

Capitulo 1 : Creacion y utilizacion de una DLL

Capitulo 2 : Creacion de un formulario dinamico utilizando una DLL
Más información
19/09/2013 17:35:59

Ya puedes publicar tu opinion

A partir de ahora ya puedes comentar todas las publicaciones que encuentres en el portal.

Podras opinar tanto si algo te gusta como si no, o si crees que es conveniente completar alguna publicacion, ya que la encuentras incompleta o erronea.

O simplemente por si nos quieres felictar por algo bien hecho :-).

Valoraremos cualquier critica que nos puedas hacer.
Más información
20/05/2013 15:30:10

Tutorial PHP5

Fundamentos de la programacion orientada a objetos
Un interesante tutorial repartido en una serie de capitulos donde se tratan los conocimientos basicos de la programacion orientada a objetos (POO) en PHP5.

Para mayor informacion siga el siguiente enlace :

Tutorial POO en PHP

Más información
04/09/2013 15:44:29
1