18 septiembre 2009

Mi primer videojuego independiente (1)

Durante una serie de artículos os voy a explicar mi pequeña experiencia en lo que se refiere a la programación de mi primer videojuego independiente y comercial así como su venta y distribución. Sobre todo voy a orientarme en la programación en Delphi.

Lo que voy a explicar viene a ser una extensión de esta serie de artículos que escribí hace tiempo sobre programación con SDL:

Programar videojuegos con la librería SDL (1)
Programar videojuegos con la librería SDL (2)
Programar videojuegos con la librería SDL (3)
Programar videojuegos con la librería SDL (4)
Programar videojuegos con la librería SDL (5)
Programar videojuegos con la librería SDL (6)
Programar videojuegos con la librería SDL (7)
Programar videojuegos con la librería SDL (8)
Programar videojuegos con la librería SDL (9)
Programar videojuegos con la librería SDL (10)
Programar videojuegos con la librería SDL (11)

Para los que crean que con Delphi sólo se pueden crear aplicaciones de gestión y utilidades les voy a demostrar que están muy equivocados. Trabajando a tiempo parcial y durante 7 meses al final pudimos hacer entre dos personas (programador y diseñador gráfico) el videojuego Pequepon Adventures que he metido en mi página web de Divernova Games.

Y todo con Delphi 7 y programando a pelo en Win32. Ni siquiera llego a utilizar el objeto Application. Podéis descargar la demo de aquí:

http://www.divernovagames.com/pequeponadventures.html

Si hubiese trabajado 8 horas al día de Lunes a Viernes sin matarme, realmente hubiera tardado unos 3 meses en realizarlo. Lo que más que costó fue el diseño de pantallas, ya que el juego tiene 30 niveles con más o menos 10 pantallas cada uno, lo que dan un total de 300 pantallas.

Aunque pueda parecer sencillo al principio, diseñar pantallas es desesperante. Para hacer un videojuego como dios manda creo que harían falta por lo menos cuatro personas (programador, diseñador de niveles, diseñador gráfico y músico).

PARA APRENDER HAY QUE EQUIVOCARSE

Cuando uno es novato en estos temas lo primero que hace es tirarse al toro sin planificar nada y teniendo las ideas más o menos claras pero sin un objetivo concreto. Durante muchos años he intentado hacer videojuegos primero en lenguaje ensamblador programando directamente los gráficos a la SVGA mediante puertos, luego en C/C++ con las librerías Allegro y SDL para terminar programando el Delphi con SDL y OpenGL. Eso sin contar con los DIV Games Studio, Fenix, GameMaker y demás hierbas.

Pero lo que pasa siempre es que empiezas el juego con ilusión y conforme van pasando los meses y va incrementándose la dificultad al final abandonas creyendo que el lenguaje o la librería gráfica que estas utilizando no son lo suficientemente buenos y comienzas a programar en otros lenguajes más potentes.

Pero el problema no esta en los lenguajes, está en nosotros mismos. Hay que comenzar con un proyecto pequeño y abarcable a corto plazo, da igual que sea cutre y pequeño, lo importante es aprender a terminarlo en un tiempo estimado y sin errores.

Podéis comenzar con algo como un Tetris, un juego de objetos ocultos o una pequeña aventura gráfica con un plazo no superior a tres meses. Una vez terminado el primer juego tendremos la moral suficiente para comenzar el siguiente con más calidad y contenidos.

Tampoco creo lo que dicen muchos que para crear un buen proyecto hay que estar motivado e ilusionado. Con el tiempo la ilusión se acaba y terminas abandonando. Creo que hay que tener una meta clara: ganar dinero. Lo demás son tonterías. Tu modelo de negocio puede basarse en hacer algo gratuito que descargue mucha gente y vivir de la publicidad, o bien cobrar por copia (arriesgándonos siempre con el pirateo).


EL DESARROLLO DE PEQUEPON ADVENTURES

Uno no se explica como un juego tan pequeño puede dar tantos quebraderos de cabeza. Y es por una sencilla razón: la falta de experiencia. Ya puedes leerte 100 libros de programación en los mejores lenguajes o en las librerías OpenGL o DirectX, pero hasta que no te pones a programar no te puedes ni imaginar los problemas que van a salir.

Después de tirarme 6 meses programando todo el videojuego, me puse a probarlo en otros equipos y me llevé un chasco impresionante. La librería SDL en modo 2D (sin utilizar aceleración gráfica) funciona muy bien cuando las pantallas son estáticas, pero si realizamos un scroll entonces el retrazo vertical de algunas tarjetas gráficas llega a crear unos cortes tan feos y parpadeantes que pueden matar a un epiléctico.

Me ofusqué buscando por Internet como activar la sincronización vertical en este modo de vídeo pero no encontré absolutamente nada. Todo el mundo decía que sólo funciona cuando la SDL dibuja polígonos OpenGL.

Así que después de una semana maldiciendo mi suerte me propuse dedicarme un mes más a cambiar todo el motor gráfico a OpenGL con las siguientes ventajas y dificultades:

- Todas las rutinas de dibujado de sprites hay que rehacerlas de nuevo.

- Hay que dibujar con polígonos con un ancho y alto que sea potencia de 2 (32x32, 64x64, 128x256, 32x128, etc.).

- Los polígonos no pueden superar un máximo de 256x256. Si se puede pero no todas las tarjetas lo soportan, por lo que si queréis que vuestro videojuego funcione en el mayor número de ordenadores posible no hay que pasarse de ese rango. Supongo que las últimas versiones de DirectX se pasarán esto por el forro.

- Las texturas de los polígonos hay que enviarlas todas de una vez a la memoria de la tarjeta de vídeo antes de comenzar a dibujar. No podemos subir o eliminar texturas en tiempo real porque el desastre puede ser impresionante.

- Por fin podemos activar el retrazo vertical.

- No es necesario crear pantallas temporales ni doble o triple buffer. OpenGL se encarga de todo.

- Aunque OpenGL puede dibujar polígonos de todo tipo yo prefiero partirlo todo en triángulos para que la tarjeta gráfica no tenga que complicarse la vida, aunque las últimas GPU ya ha hacen de todo. Por tanto, cada sprite tiene dos polígonos (triángulos).

- Al dibujar sprites por hardware el consumo de recursos es mínimo. Si ejecutaís Pequepon Adventures en modo ventana (ver Opciones) y abrís el Administrador de tareas de Windows veréis que a veces el juego llega a consumir entre un 0 y un 5% de procesador y todo a 40 frames por segundo. Esto es importantísimo en portátiles para que dure más la batería.

El resultado fue menos traumático de lo que me creía ya que aproveché el código fuente que tenía en C++ de hace años cuando intenté hacer videojuegos 3D en OpenGL con mi propio motor 3D tipo Quake, pero nunca lo terminé por lo que he hablado: el mucho abarca poco aprieta.

Así que en estos artículos pondré fragmentos de código referentes al este nuevo motor 2D pero con aceleración gráfica OpenGL. Como comprenderéis no voy a dar todo el código fuente del juego por dos razones: es un juego comercial y que son 9.700 líneas de código. Pero sí hablaré de las partes que he considerado más difíciles.


EL EDITOR DE PANTALLAS

Programar un juego no sólo es hacer un motor 2D o 3D. Tenemos que crear herramientas externas que guarden la información que necesitamos. En mi caso fue el editor de pantallas. El juego esta programado a una resolución de 640 x 480. Lo que hice fue partir la pantalla en piezas (tiles) de 40 x 40 pixels, lo que sale un total de 16 piezas horizontales y 12 verticales:

Aquí cometí mi primer error. Al principio pensamos en hacer un pequeño videojuego gratuito tipo Snowy con una sola pantalla por nivel. Pero vimos que el juego se hacía muy corto. O hacíamos las piezas más pequeñas o le metíamos más pantallas por la derecha haciendo el Pequepon saltara a la siguiente pantalla cuando llegara a la parte derecha de la pantalla.

Pero entonces se me cruzaron los cables e intenté hacer un scroll. Al principio me salió lento y horrible. Pero entonces comencé a optimizar el motor y llegué a hacer un scroll suave y aceptable que se mueve de 3 en 3 pixels.

El error cometido fue el tener que dibujar entre dos pantallas. Cuando estas entre la primera y la segunda pantalla tienes que dibujar un trozo de cada, con la dificultad que conlleva. Cuando lleguemos a la parte de mi motor 2D os diré como resolví el problema.

Lo que debería haber hecho es un mundo gigante con un buen array bidimensional que abarque todas las pantallas de ese nivel. Ya he aprendido la lección para el siguiente juego. Pero volvamos al editor.

El editor es una aplicación normal de Delphi que contiene un solo formulario:

El juego se dibuja a tres capas:

1º capa: pantalla de fondo.

2º capa: las piezas con las que choca pequepon (suelos, paredes, bloques, etc.).

3º capa: los objetos que recogemos (fresas, llave, esfera, puerta, etc.).

En la parte derecha del editor tenemos una columna para las piezas y otra para los objetos. Como hay más piezas y objetos de lo que pueden caber en pantalla, lo que hice fue meter los bitmaps de las piezas y los objetos dentro de un componente TScrollBox para poder llegar a todas moviendo la barra de desplazamiento vertical.

Si selecciono una pieza o un objeto coloco una flecha verde a su lado. Luego cuando nos vamos a la pantalla si pinchamos con el botón izquierdo del ratón dibujamos piezas y si lo hacemos con el botón derecho del ratón ponemos objetos. La primera pieza y el primer objeto los he dejado transparentes.

¿Por qué el fondo de las piezas son de color rosa? Pues porque es el color que utilizo de máscara para dibujarlas. No me interesaba el negro porque tanto los personajes como los objetos tienen el borde negro.

Entonces en el evento OnMouseDown del formulario compruebo primero si estamos dentro de la pantalla y comenzamos a dibujar:

procedure TFPrincipal.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var i, j: Integer;
begin
if Button = mbLeft then
begin
bIzquierdo := True;

// ¿Está dentro de la pantalla?
if ( x > 40 ) and ( y > 40 ) and ( x <= 680 ) and ( y <= 520 ) then
begin
i := x div 40;
j := y div 40;
EX.Caption := IntToStr( i );
EY.Caption := IntToStr( j );

Piezas[i,j] := iPieza;
DibujarPantalla;
end;
end;

if Button = mbRight then
begin
bDerecho := True;

// ¿Está dentro de la pantalla?
if ( x > 40 ) and ( y > 40 ) and ( x <= 680 ) and ( y <= 520 ) then
begin
i := x div 40;
j := y div 40;
EX.Caption := IntToStr( i );
EY.Caption := IntToStr( j );

Objetos[i,j] := iObjeto;
DibujarPantalla;
end;
end;
end;

Las piezas y los objetos son dos array bidimensionales de bytes, por lo tanto solo podemos poner un máximo de 255 piezas distintas:

var
Piezas, Objetos: array[1..16,1..12] of byte;

Esto no me preocupa porque por cada mundo vuelvo a cargar piezas nuevas:

En cambio, los objetos son iguales para todos los mundos. Aún así sólo he gastado 77 objetos de los 255 que tengo disponibles. Si necesitáis más piezas pues hacéis un array de DWord. La procedimiento de DibujarPantalla es este:

procedure TFPrincipal.DibujarPantalla;
var i, j: Integer;
Origen, Destino: TRect;
begin
// Lo dibujamos todo el el buffer

with Buffer.Canvas do
begin
// Primero dibujamos el fondo
Origen.Top := 0;
Origen.Left := 0;
Origen.Right := 640;
Origen.Bottom := 480;
Destino.Top := 0;
Destino.Left := 0;
Destino.Right := 640;
Destino.Bottom := 480;
CopyMode := cmSrcCopy;
CopyRect( Destino, ImagenFondo.Canvas, Origen );

for j := 1 to 12 do
for i := 1 to 16 do
begin
if Piezas[i,j] > 0 then
begin
Origen.Top := Piezas[i,j] * 40;
Origen.Left := 0;
Origen.Right := 40;
Origen.Bottom := Piezas[i,j] * 40 + 40;
Destino.Top := (j-1)*40;
Destino.Left := (i-1)*40;
Destino.Right := (i-1)*40 + 40;
Destino.Bottom := (j-1)*40 + 40;

// ¿La pieza que va a poner ya no existe?
if Piezas[i,j] > ImagenPiezas.Height div 40 then
begin
Brush.Color := clRed;
FillRect( Destino );
end
else
if MascaraPiezas = nil then
begin
CopyMode := cmSrcCopy;
CopyRect( Destino, ImagenPiezas.Canvas, Origen );
end
else
begin
CopyMode := cmSrcAnd;
CopyRect( Destino, MascaraPiezas.Canvas, Origen );
CopyMode := cmSrcPaint;
CopyRect( Destino, MascaraPiezas2.Canvas, Origen );
end;
end;

if Objetos[i,j] > 0 then
begin
Origen.Top := Objetos[i,j] * 40;
Origen.Left := 0;
Origen.Right := 40;
Origen.Bottom := Objetos[i,j] * 40 + 40;
Destino.Top := (j-1)*40;
Destino.Left := (i-1)*40;
Destino.Right := (i-1)*40 + 40;
Destino.Bottom := (j-1)*40 + 40;

// ¿El objeto que va a poner ya no existe?
if Objetos[i,j] > ImagenObjetos.Height div 40 then
begin
Brush.Color := clYellow;
FillRect( Destino );
end
else
if MascaraObjetos = nil then
begin
CopyMode := cmSrcCopy;
CopyRect( Destino, ImagenObjetos.Canvas, Origen );
end
else
begin
CopyMode := cmSrcAnd;
CopyRect( Destino, MascaraObjetos.Canvas, Origen );
CopyMode := cmSrcPaint;
CopyRect( Destino, MascaraObjetos2.Canvas, Origen );
//CopyRect( Destino, ImagenObjetos.Canvas, Origen );
end;
end;
end;
end;

// copiamos el buffer a pantalla
with Canvas do
begin
Origen.Top := 0;
Origen.Left := 0;
Origen.Right := 640;
Origen.Bottom := 480;
Destino.Top := 40;
Destino.Left := 40;
Destino.Right := 680;
Destino.Bottom := 520;
CopyMode := cmSrcCopy;
CopyRect( Destino, Buffer.Canvas, Origen );
end;
end;

Dibujar con el canvas de Delphi es un auténtico coñazo. Tenemos primero que cargar los sprites, crear una máscara y luego dibujar la máscara y después los sprites. Para ello tuve que declarar primero todas estas imágenes:

var
MascaraPiezas, MascaraPiezas2, Buffer: TImage;
MascaraObjetos, MascaraObjetos2: TImage;

Y para crear la máscara recorro todos los pixels de la imagen y si el color es rosa entonces invierto los pixels o los elimino:

procedure TFPrincipal.CrearMascaraPiezas;
var i, j: Integer;
begin
// Creamos la máscara de color blanco con el contorno del sprite
MascaraPiezas := TImage.Create( nil );
MascaraPiezas.Width := ImagenPiezas.Width;
MascaraPiezas.Height := ImagenPiezas.Height;

MascaraPiezas2 := TImage.Create( nil );
MascaraPiezas2.Width := ImagenPiezas.Width;
MascaraPiezas2.Height := ImagenPiezas.Height;

for j := 0 to ImagenPiezas.Height - 1 do
for i := 0 to ImagenPiezas.Width - 1 do
begin
if ImagenPiezas.Canvas.Pixels[i,j] = $FF00FF then
begin
MascaraPiezas.Canvas.Pixels[i,j] := clWhite;
MascaraPiezas2.Canvas.Pixels[i,j] := clBlack;
end
else
begin
MascaraPiezas.Canvas.Pixels[i,j] := clBlack;
MascaraPiezas2.Canvas.Pixels[i,j] := ImagenPiezas.Canvas.Pixels[i,j];
end;
end;
end;

Luego hay que acordarse de destruirlo todo al cerrar el formulario:

procedure TFPrincipal.FormDestroy(Sender: TObject);
begin
Suelos.Free;
Paredes.Free;
Matan.Free;
Buffer.Free;
MascaraObjetos.Free;
MascaraObjetos2.Free;
MascaraPiezas.Free;
MascaraPiezas2.Free;
end;

Tanto para el videojuego como para este editor utilicé el experto EurekaLog como mi compañero inseparable. Cuando seleccionamos Archivo -> Abrir cargo la pantalla:

procedure TFPrincipal.AbrirPClick(Sender: TObject);
begin
Abrir.InitialDir := sRutaDat;
Abrir.Filter := 'Pantalla (*.pan)|*.pan';

if Abrir.Execute then
begin
sArchivoPantalla := Abrir.FileName;
CargarPantalla( sArchivoPantalla );
ETituloPantalla.Caption := ExtractFileName ( Abrir.FileName );
end;
end;

La rutina de cargar pantalla debe cargar el fondo que tiene esta pantalla, las piezas, los objetos, la posición del heroe, los enemigos, etc.:

procedure TFPrincipal.CargarPantalla( sArchivo: String );
var
F: File of byte;
i, j, iNumSue, iNumPar, iNumMat, iNumSueReal, iNumParReal, iNumMatReal: Integer;
b: Byte;
begin
AssignFile( F, sArchivo );
Reset( F );

// Cargamos las piezas
for j := 1 to 12 do
for i := 1 to 16 do
Read( F, Piezas[i,j] );

// Cargamos los objetos
for j := 1 to 12 do
for i := 1 to 16 do
Read( F, Objetos[i,j] );

// Leemos el nombre de el archivo de piezas
sArchivoPiezas := '';
for i := 1 to 30 do
begin
Read( F, b );
if b <> 32 then
sArchivoPiezas := sArchivoPiezas + Chr( b );
end;
CargarPiezas( sRutaGfcs + sArchivoPiezas + '.bmp' );

// Leemos el nombre de el archivo de fondo
sArchivoFondo := '';
for i := 1 to 30 do
begin
Read( F, b );
if b <> 32 then
sArchivoFondo := sArchivoFondo + Chr( b );
end;
CargarFondo( sRutaGfcs + sArchivoFondo + '.bmp' );

// Leemos el nombre de el archivo de piezas
sArchivoObjetos := '';
for i := 1 to 30 do
begin
Read( F, b );
if b <> 32 then
sArchivoObjetos := sArchivoObjetos + Chr( b );
end;
CargarObjetos( sRutaGfcs + sArchivoObjetos + '.bmp' );

Suelos.Clear;
Paredes.Clear;
Matan.Clear;

if not Eof(F) then
begin
// Leemos el número de suelos
Read( F, b );
iNumSue := b;

// Leemos el número de paredes
Read( F, b );
iNumPar := b;

// Leemos los suelos
iNumSueReal := 0;
for i := 1 to iNumSue do
begin
if not Eof(F) then
Read( F, b );

if b < ImagenPiezas.Height div 40 then
begin
Suelos.Add( IntToStr( b ) );
Inc(iNumSueReal);
end;
end;
iNumSue := iNumSueReal;

// Leemos las paredes
iNumParReal := 0;
for i := 1 to iNumPar do
begin
if not Eof(F) then
Read( F, b );

if b < ImagenPiezas.Height div 40 then
begin
Paredes.Add( IntToStr( b ) );
Inc(iNumParReal);
end;
end;
iNumPar := iNumParReal;

// Leemos el número que matan
if not Eof(F) then
begin
Read( F, b );
iNumMat := b;

// Leemos los que matan
iNumMatReal := 0;
for i := 1 to iNumMat do
begin
if not Eof(F) then
Read( F, b );

if b < ImagenPiezas.Height div 40 then
begin
Matan.Add(IntToStr(b));
Inc(iNumMatReal);
end;
end;

iNumMat := iNumMatReal;
end;
end;

CloseFile( F );
sArchivo := Abrir.FileName;
DibujarPantalla;
MostrarSuelosParedes;
end;

Los objetos también los utilizo para colocar los enemigos en pantalla y saber que recorrido van a tener. Para delimitar los movimientos de los enemigos cree un objeto especial (la X) que se ve en el editor pero no en el juego. Cuando un enemigo encuentra este objeto da la vuelta:


Los objetos de los enemigos tampoco se verán en el juego. Me valen para saber donde comienza cada enemigo y el movimiento que va a hacer. Para hacer el editor más cómodo también hice que si dejamos pulsado los botones izquierdo o derecho del ratón podemos dibujar una columna o fila de bloques sin tener que hacer clic cada vez. Esto se hace con el evento OnMouseMove:

procedure TFPrincipal.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
var i, j: Integer;
begin
// ¿Está dentro de la pantalla?
if ( x > 40 ) and ( y > 40 ) and ( x <= 680 ) and ( y <= 520 ) then
begin
i := x div 40;
j := y div 40;

if ( i < 1 ) or ( i > 16 ) or ( j < 1 ) or ( j > 12 ) then
Exit;

EX.Caption := IntToStr( i );
EY.Caption := IntToStr( j );

// ¿Está pulsado el botón izquierdo del ratón?
if bIzquierdo then
begin
Piezas[i,j] := iPieza;
DibujarPantalla;
end;

// ¿Está pulsado el botón izquierdo del ratón?
if bDerecho then
begin
Objetos[i,j] := iObjeto;
DibujarPantalla;
end;
end;
end;

Pero como tenemos que saber si hemos dejado pulsado el botón izquierdo o derecho del ratón entonces creé dos variables globales:

var
bIzquierdo: Boolean; // ¿Está pulsado el botón izquierdo del ratón?
bDerecho: Boolean; // ¿Está pulsado el botón derecho del ratón?

Y en los eventos OnMouseDown que hemos visto antes y en el evento OnMouseUp controlo cuando el usuario los ha apretado o soltado:

procedure TFPrincipal.FormMouseUp( Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer );
begin
if Button = mbLeft then
bIzquierdo := False;

if Button = mbRight then
bDerecho := False;
end;

Lo que más me costó al principio fue la rutina de guardar pantalla ya que hay que hay que guardar el nombre del bitmap de fondo, las piezas, los objetos (y enemigos) así como saber con que piezas choca el personaje y los enemigos:

procedure TFPrincipal.GuardaPantalla;
var
F: file of byte;
i, j: Integer;
Espacio, b: Byte;
begin
if sArchivoPiezas = '' then
begin
Application.MessageBox( 'Debe seleccionar el archivo de las piezas.',
'Atención', MB_ICONEXCLAMATION );
Exit;
end;

if sArchivoFondo = '' then
begin
Application.MessageBox( 'Debe seleccionar la imagen de fondo.',
'Atención', MB_ICONEXCLAMATION );
Exit;
end;

if sArchivoObjetos = '' then
begin
Application.MessageBox( 'Debe seleccionar el archivo de los objetos.',
'Atención', MB_ICONEXCLAMATION );
Exit;
end;

// Guardamos las piezas y objetos de la pantalla
AssignFile( F, sArchivoPantalla );
Rewrite( F );
Espacio := 32;

// Piezas
for j := 1 to 12 do
for i := 1 to 16 do
Write( F, Piezas[i,j] );

// Objetos
for j := 1 to 12 do
for i := 1 to 16 do
Write( F, Objetos[i,j] );

// Guardamos el nombre del archivo de las piezas
for i := 1 to 30 do
if i <= Length( sArchivoPiezas ) then
Write( F, Byte( sArchivoPiezas[i] ) )
else
Write( F, Espacio ); // espacio en blanco

// Guardamos el nombre del archivo de fondo
for i := 1 to 30 do
if i <= Length( sArchivoFondo ) then
Write( F, Byte( sArchivoFondo[i] ) )
else
Write( F, Espacio ); // espacio en blanco

// Guardamos el nombre del archivo de los objetos
for i := 1 to 30 do
if i <= Length( sArchivoObjetos ) then
Write( F, Byte( sArchivoObjetos[i] ) )
else
Write( F, Espacio ); // espacio en blanco

// Guardamos el número de suelos
b := Suelos.Count;
Write(F, b);

// Guardamos el número de paredes
b := Paredes.Count;
Write(F, b);

// Guardamos los suelos
for i := 0 to Suelos.Count-1 do
begin
b := StrToInt(Suelos[i]);
Write(F, b);
end;

// Guardamos las paredes
for i := 0 to Paredes.Count-1 do
begin
b := StrToInt(Paredes[i]);
Write(F, b);
end;

// Guardamos el número que matan
b := Matan.Count;
Write(F, b);

// Guardamos los que matan
for i := 0 to Matan.Count-1 do
begin
b := StrToInt(Matan[i]);
Write(F, b);
end;

CloseFile( F );
end;

Si os fijáis en las columnas de las piezas y los objetos a la izquierda guardo si cada pieza es libre o es suelo o pared: Estoy es muy importante para controlar si nuestro personaje va a chocar sólo verticalmente al caer o choca por todos lados, por ejemplo con el bloque de piedra. Aunque mi editor lleva mucho más código creo que he comentado las partes más importantes. Crear un buen editor de niveles es fundamental para evitar programar demasiado. En el próximo artículo comenzaré a explicar el núcleo del juego y el motor 2D creado con SDL + OpenGL.
Pruebas realizadas en Delphi 7.

11 comentarios:

Jordi Coll Corbilla dijo...

Enhorabuena!, estoy impresionado, ya tengo ganas de echarle un vistazo!.

BlueIcaro dijo...

Hola, hace ya tiempo que sigo tu blog, me ha resultado de mucha utilidad.
Pero hoy entré y me llevé una grata sorpresa, cuando me pongo a leer esta artículo.
No me lo puedo creer, vas a hablar de SDL y OpenGL. Encima comentas los problemas que hay al hacer un scroll, creo que estos días me has estado leyendo la mente.
Porque desde hace un mes, que lei los artículos sobre SDL, y me decidí a hacer un jueguecito, he estado leyendo sobre SDL, y haciendo "experimentos", y la verdad es que estaba metiendome en OpenGl.
Sobre el scroll y sus problemas en modo "normal" llevo un par de semanas rayando sobre eso, por lo problemas que comentas.
Pero mira tú, hoy es un buen día, voy a aprender muchísimo con tus artículos, estoy seguro.
Los espero con impaciencia.
Saludos
/Blueicaro

MAXIUM dijo...

Excelente, has cumplido lo prometido. te felicito por tu gran trabajo con este blog, no sabes cuanto nos ayudas.

Jose dijo...

Genial. Encontré este blog cuando hacias la anterior saga de artículos sobre la SDL y ahora estoy encantado de que vuelvas a incidir sobre el tema.

¿Cuantos de los que nos dedicamos a la programación, ya sea de forma amateur o de forma profesional, no hemos intentado alguna vez hacer un juego?

Personalmente creo que es un gran reto porque éste tipo de programación requiere de mucho ingenio para salir de los atolladeros en los que, poco a poco, te vas metiendo. De hecho, siempre he pensado que el auténtico reto del juego está ahí precisamente, en empezar a hacerlo.

Particularmente a mi lo que me cuesta horrores es el diseñar los gráficos. Si los gráficos que has hecho te gustan, parece que todo se va volviendo más fácil XD

Un saludo a todos y al administrador, mi más sincera enhorabuena.

Jose

Anónimo dijo...

Estimado Administrador, te escribo para FELICITARTE por este increíble recurso que has puesto a disposición de todos los que amamos este maravilloso lenguaje. Si bien no es el tema que trata esta entrada quería agradecerte por enseñarme a trabajar con las aplicaciones multicapa que expusiste en una oportunidad. No he encontrado tal claridad y practicidad de conceptos en ningún lado.
Por eso, aprovecho para pedirte un favor. Si en algún momento, y enganchando con lo de aplicaciones multicapa, hagas una introducción acerca de webservices y principios de funcionamiento de aplicaciones con SOA orientadas siempre a su utilización con Delphi. Estoy seguro que habrá muchos interesados en esta nueva tecnología.
Nuevamente aprovecho a saludarte y agradecerte por tu trabajo.

Un abrazo grande desde Argentina.

Administrador dijo...

Pues quizá algún día profundice en las aplicaciones multicapa según las últimas tecnologias de Delphi 2010 y escriba un remake de aquellos artículos.

Saludos.

franmacías dijo...

Hola
Quiero felicitarte por el trabajo, y agradecerte las explicaciones tan completas.
Muchas gracias

Arveja dijo...

La verdad es sorprendente lo que se puede hacer todavia con el querido Delphi 7.

Tengo una consulta para hacerte, de que forma "empaquetas" los recursos del juego para que no queden todos los archivos a la vista?
Estoy armando una pequeña aplicación que simularia un catalogo (es una BD con imagenes asociadas a cada articulo) y me gustaria poder empaquetar todo el conjunto de imagenes para que quede mas prolijo

Saludos

Administrador dijo...

Para empaquetar los gráficos he utilizado el componente ZipForge, que permite comprimir en zip y con contraseña.

Y lo bueno es que no necesita librerías DLL externas. Aquí tienes el artículo que habla sobre este componente:

http://delphiallimite.blogspot.com/2008/11/comprimir-y-descomprimir-archivos-con.html

Saludos.

Integra Hellsing dijo...

Hola estaba buscando tutoriales sobre delphi y he quedado muy impresionada con los conocimientos que tienes acerca de este programa, me gustaría saber si puedes ayudarme a realizar un examen psicométrico en este programa ya que solo soy una aprendiz y me lo han pedido en la escuela te lo agradecería bastante espero poder contactarte lo mas pronto posible flor2303@hotmail.com Gracias

Administrador dijo...

Un programa de gestión, por muy pequeño de sea (al final nunca lo son), no es una cosa que se realice en dos tardes, y más si no tienes conocimientos profundos de un lenguaje de programación en concreto como Delphi.

Hay que tener conocimientos por lo menos de Interbase/Firebird por si queremos hacer una aplicación cliente/servidor y controlar la seguridad de los datos por usuarios, permisos, etc.

En lo único que yo te podría ayudar es para responder en alguna duda respecto a los artículos que he escrito, pero de ahí a sugerir como hacerse un programa paso a paso es otra cosa.

Casi no tengo tiempo de actualizar este blog semanalmente y un programa como el que pides hay que tomárselo en serio y planificarlo antes de comenzar.

Te recomiendo antes de nada, adquirir un buen conocimiento de Delphi antes de comenzar esta aventura.

Recibe un cordial saludo.

Publicidad