programando

TILED BASED GAMES

 Por Rodrigo Pelorosso (Assembly)

 

 
Este tutorial consta de las siguientes partes:
 
0.0   Antes de empezar 
1.0   ¿Que es un Tile Based Game?
2.0   El Mapa
2.1   Dibujando el mapa
2.2   Eligiendo el sprite para dibujar el bloque
2.3   Clipping
2.4   Parallax Scrolling
3.0         Game Loop
4.0         Conclusión
4.1   Mpdv
5.0   Contactando al autor
 
               
0. Antes de empezar
 
Quiero aclarar que no soy un programador profesional (y mucho menos escritor), de modo que la presente información puede contener (he tratado de evitarlos) errores.

 

En este tutorial vamos a aprender a programar juegos Tile Based en Visual Basic, se puede bajar el código de ejemplo (y en el que me voy a basar) haciendo clic AQUÍ, este código no es un tile based game completo, debido a que esta es la parte I del tutorial, de todas formas, vamos a apuntar a terminar haciendo algo como el Visual Mario (que lo podes conseguir en www.canalprogramadores.com.ar)

 

El código de ejemplo usa DirectDraw de DirectX 7.0 para manejo de gráficos, te recomiendo que consigas mi tutorial  sobre DirectDraw en Visual Basic si no lo sabes usar. Pueden escribirme y yo se los enviare con gusto ;)

 

1.0. Que es un Tile Based Game?

 

La traducción literal queda muy mal, de modo que la mejor forma de explicar que son estos juegos, es hacer referencia a alguno conocido, estos pueden ser el Mario Bros, Giana Sisters,

Duke Nukem I y II, y Visual Mario :p ... suficientes ejemplos.. si aun no saben que es, no entiendo porque están leyendo esto.. de todas formas, aquí hay un snapshot del Visual Mario.

 

                            

 

Existen 2 tipos de Tile Based Games , los que tienen este tipo de perspectiva (que por lo general se ven de costado), y los Isometric Tile Based Games, que se ven desde una perspectiva isométrica, un ejemplo de estos pueden ser los clásicos Head Over Hells, Batman, Killer Tomatoes y Gun Fright de la querida MSX, y Ultima 8, Relentless (L.B.A), Dungeon Hack,  The Summoning, y Veil of Darkness de PC.

 

mmm.. lamento desilusionarlos, pero no tocaremos el tema de la detección de colisión en este tutorial, lo siento..

 

2. El mapa

 

Si se presta un poco de atención (no mucha, resulta realmente obvio), se puede notar que el mapa esta compuesto por pequeños bloquecitos cuadrados uno detrás de otro. Bien, necesitamos conseguir un método de mantener ese mapa en memoria y manejarlo a la hora de mostrarlo, o lo que sea que le vayamos a hacer.

La forma mas fácil (o bueno.. como se me ocurrió..) es usar un array de strings para guardar el mismo, como seria este array? Muy fácil, a continuación se puede ver el mapa que usamos en el demo en su formato array.

 

  Mapa(0) = "T                                                        EF                  T"

  Mapa(1) = "T                                                                           NL"

  Mapa(2) = "T                  EF                  EF                          EF        T"

  Mapa(3) = "T      EF                                                                    T"

  Mapa(4) = "T                                                                 NO        NL"

  Mapa(5) = "T            VWX                                  VWX            NLU         T"

  Mapa(6) = "T            YZa                                  YZa   B       NLLU         T"

  Mapa(7) = "T GHI         c                    NMO             c   ADC     NLLLLO       NL"

  Mapa(8) = "TMMMMMO      NMMMMMMMO                        GHINMMMMMMMMMMMMMLLLLLLO       T"

  Mapa(9) = "TLLLLLU      TLLLLLLLLO                       NMMLLLLLLLLLLLLLLLLLLLLLO      T"

 Mapa(10) = "TLLLLLRPPPPPPQLLLLLLLLLO    NMMO         NMMMMLLLLLLLLLLLLLLLLLLLLLLLLLO GHI T"

 Mapa(11) = "TLLLLLeSSSSSSdLLLLLLLLLU   NLLLRPPPPPPPPPQLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMML"

 Mapa(12) = "TLLLLLeSSSSSSdLLLLLLL      TLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL"

 Mapa(13) = "TLLLLLeSSSSSSdLLLLLLLPPPPPPQLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL"

 Mapa(14) = "TLLLLLeSSSSSSdLLLLLLLSSSSSSdLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL"

 Mapa(15) = "TLLLLLeSSSSSSdLLLLLLLSSSSSSdLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL"

 

Bien, cada carácter del array, es un bloque en el mapa, así, dependiendo del carácter que sea, depende el bloque que es, y se usara una imagen diferente para representarlo. Por el momento, tene en cuenta que un carácter vacío (“ “), es un espacio libre en el mapa.

 

2.1. Dibujando el mapa

 

Genial, ahora, la gran pregunta será: ¿como dibujo el mapa?

Tenemos que ir barriendo el array, para detectar que caracteres hay, e ir dibujando según corresponda, para leer un carácter en la posición X e Y del mapa, usaremos simplemente nuestra querida función MID$ de la siguiente manera.

 

Dim CH as String *1

CH = MID$(Mapa(Y),X,1)

 

Entonces, para dibujar el mapa, barreremos los caracteres que podemos ver (no tiene sentido barrer y dibujar aquellos que están fuera del rango de visión, seria perder recursos en algo que no veremos), esto es fácil, mira este código:

 

For Y = 0 To 14

For X = 0 + ColMap To 20 + ColMap

Bloq = Mid$(Mapa(Y), X, 1)

If Bloq <> " " Then

posX = ((X) * 16) - (ColMap * 16)

posY = (Y * 16)

GraficarBloque(PosX, PosY, Bloque)

        End If

       Next

Next

 

Donde:

 

FilMap y ColMap son las coordenadas del mapa a partir de las cuales empezamos a leer el array,

Serian algo así como la posición de la cámara en X e Y

GraficarBloque es una función inventada ahora para que el código se entienda mejor, la misma graficaría al bloque del tipo Bloque, en la posición PosX, PosY.

Notar que los bloques tienen 16x16 píxeles de tamaño.

 

Ahora, la pregunta que podrían hacer es la siguiente:

 

 “cuando calculas PosX, porque le restas (ColMap * 16)?”

 

Mira esto, suponete que esas restas no estarían, y la cámara esta en ColMap=40,

Entonces cuando graficamos en bloque situado en X=5 e Y=7 (ver el For), lo cual corresponderia realmente al bloque X=ColMap+5 e Y=7 :

 

posX = (X) * 16)

posY = (Y) * 16)

 

Que valor tiene PosX y PosY  ahora? Serian...

 

PosX = (ColMap+5) *16 = 720

PosY = Y *16 = 112

 

Estaríamos graficando el bloque en la posición (720,112) -en píxeles- en una resolución de 320x240!!.. evidentemente no veríamos nada, de todas formas, eso se aplica solo a la coordenada X, ya que en las Y solo tenemos la cantidad de bloques necesaria para llenar la pantalla y nada mas (no haremos Scrolling vertical para no complicar mas esto, pero de todas formas es igual que el horizontal)

 

La posición donde deberíamos graficar seria en realidad de

 

X=5*16=80

Y=7*16=122

 

Fijate que ese es el resultado que obtenes con la resta. En resumen, podríamos decir que la misma hace que los bloques que veamos, o lo que tenemos que ver, se centren en la pantalla.

 

2.2. Eligiendo el sprite para dibujar el bloque

 

Antes una aclaración, en el archivo sprites.bmp, están todos los sprites, formando una grilla,

Ese BMP lo cargamos en una superficie DIRECTDRAWSURFACE7 (una vez mas, busca mi tutorial sobre DirectDraw en VB), y vamos sacando las imágenes de ahí usando un tipo RECT para seleccionar el sprite, el RECT es cuadrado (de 16x16), y lo que haremos es ir desplazándolo por la grilla, para sacar el sprite que queramos.

 

Dijimos que dependiendo del carácter que haya en el array, será el grafico que tendrá el bloque, entonces, tendremos que variar la posición del RECT de origen, como dijimos antes, para sacar la imagen correspondiente. A continuación se ve el archivo sprites.bmp

 

                                

Vamos a ver como sacar el sprite que necesitamos con el RECT en caso de que no lo entiendas bien, suponete que queremos la imagen de los ladrillitos naranjas, (Col. 10, Fil. 2), entonces el RECT tendría que tener sus miembros con los siguientes valores:

 

Dim rSprite As RECT

 

rSprite.Top = 1 + (17 * (2 - 1))

rSprite.Bottom = rSprite.Top + 16

rSprite.Left = 1 + (17 * (10 - 1))

rSprite.Right = rSprite.Left + 16

 

La formula se complica porque tenemos que saltearnos la línea divisoria entre los sprites, sin ella seria mucho mas fácil, pero un poco mas difícil para el grafista, de modo que, mejor suframos un poco nosotros y dejemos en paz al Gfx Man. :p

 

Ahora podemos hacer una formula general para extraer sprites de la grilla:

 

rSprite.Top = 1 + (17 * (Fila - 1))

rSprite.Left = 1 + (17 * (Columna - 1))

rSprite.Bottom = 16 + rSprite.Top

rSprite.Right = 16 + rSprite.Left

 

(odio la forma en la que el Editor de Visual te acomoda las cosas)

 

Bien, volviendo a lo de elegir el sprite según el carácter, lo que tenemos que hacer es modificar el valor de Columna y Fila segur el carácter, y después  modificar los valores del RECT.

Aquí hay una porción del código que hace esto en nuestro demo:

 

Bloq = Mid$(Mapa(Y), X, 1)

If Bloq <> " " Then

If Bloq = "A" Then Fila = 2: Columna = 1

If Bloq = "B" Then Fila = 2: Columna = 2

If Bloq = "C" Then Fila = 2: Columna = 3

If Bloq = "D" Then Fila = 2: Columna = 4

If Bloq = "E" Then Fila = 2: Columna = 5

If Bloq = "F" Then Fila = 2: Columna = 6

If Bloq = "G" Then Fila = 2: Columna = 7

If Bloq = "H" Then Fila = 2: Columna = 8

If Bloq = "I" Then Fila = 2: Columna = 9

If Bloq = "J" Then Fila = 2: Columna = 10

If Bloq = "K" Then Fila = 2: Columna = 11

If Bloq = "L" Then Fila = 3: Columna = 1

If Bloq = "M" Then Fila = 3: Columna = 2

If Bloq = "N" Then Fila = 3: Columna = 3

If Bloq = "O" Then Fila = 3: Columna = 4

 

..

..

 

 (y asi la cantidad de veces necesarias)

 

End If

 

Admito que es un poquito mediocre, pero.. buee..  :p

Listo, ahora lo único que nos queda por hacer es ver como graficamos el bloque en la pantalla (o mejor dicho en el back buffer),  esto lo haremos con un simple llamado a BltFast

 

BackBuffer.BltFast posX, posY, Sprite, rSprite, DDBLTFAST_SRCCOLORKEY Or DDBLTFAST_WAIT

 

Donde:

 

posX y posY son las coordenadas en la pantalla

Sprite es el DIRECTDRAWSURFACE7 donde esta la grilla de sprites (archivo sprites.bmp)

rSprite es el RECT fuente con que elegimos el Sprite que queremos

DDBLTFAST_WAIT y DDBLTFAST_SRCCOLORKEY son flags para BltFast.

 

2.3. Clipping

 

Aun nos queda algo por hacer: el clipping

 

Clipping se denomina a la acción de eliminar algo que  no veremos, DirectDraw tiene la opción de crear  un Clipper que hace esto automáticamente, pero como no lo usaremos (no pregunten porque), debemos crear un método para hacer esto. En teoría no debería ser necesario, pero el DirectDraw NO DIBUJA LOS BLOQUES QUE SE VAN FUERA DE LA SUPERFICIE!!! (re mediocre, viste?) Por lo tanto, tenemos que cortar a mano los bloques antes de graficarlos, para que ninguna parte de estos salga fuera de la misma (la superficie destino).

Bien, hay varias formas de hacer esto, podríamos recortar el RECT destino, para que no se vaya fuera de los limites de la superficie, pero, como no estamos usando Blt, sino BltFast, no podemos hacerlo (BltFast no toma un RECT de destino para saber donde graficar, sino que toma un X e Y , y copia el RECT fuente exactamente como es).

Entonces, lo que debemos hacer es tomar un RECT mas pequeño, de modo que cuando lo copiemos a la pantalla no salga fuera de esta.

Voy a darte un ejemplo grafico de nuestro problema, y la solución, de modo que se entienda un poco mejor.      

 

        

Aquí, queremos pegar un bloque en el borde derecho de la superficie, pero el sprite sale fuera de los limites de la misma. Fijate que cuando tomamos todo el sprite con el RECT fuente y lo pegamos, el DirectDraw no lo dibuja porque sale fuera.

Cuando tomamos una porción, en cambio, nada queda fuera de la superficie destino, y todo sale como esperábamos.

 

Entonces, cada vez que armamos un RECT para tomar un Sprite, tenemos que ver si el mismo, por la posición en la que ira pegado, va a salir de la pantalla, y si así es, debemos cortarlo para impedir que esto pase, obviamente esto dependerá de la resolución de video actual, que es la que le dará las dimensiones al back buffer, a lo largo de todo este tutorial y los siguientes (y probablemente en todos los que pueda llegar a escribir basados en Visual Basic), la resolución será de 320x240 (esto es por tres razones: soy un nostálgico y creo que todos los juegos deberían seguir siendo en  Modo X, la mediocridad de Visual Basic hace que en otra resolución vaya lento, y por ultimo, todos sabemos que arriba de 640x480, todas las resoluciones apestan).

 

Las únicas coordenadas del RECT que tendremos que modificar serán las de la derecha e izquierda, debido a que solo haremos Scrolling horizontal (de todos modos, el clipping vertical es exactamente igual al horizontal)

 

 

'/////////////////// clipping izquierdo /////////////////

If posX <= 0 Then

rSprite.Left = rSprite.Left + (Abs(posX))

posX = 0

End If

 

'/////////////////// clipping derecho /////////////////

If pos1X >= 319 Then

rSprite.Right = rSprite.Right - (pos1X - 319)

End If

 

Donde:

 

pos1X es la coordenada X de la Derecha en la pantalla

posX es la coordenada X de la Izquierda en la pantalla

rSprite es el RECT fuente, que indica que sprite sacaremos de la grilla

 

La explicación de este código es sencilla (iba a escribir “...y se la dejo al lector”, pero odio cuando alguien hace eso :p)

Lo que hacemos es lo siguiente, doy el ejemplo con el borde derecho ya que para el izquierdo es igual: Si el borde derecho del sprite sale fuera de la superficie destino (el back buffer) en n píxeles, entonces al RECT fuente, le sacamos n píxeles del lado derecho. Como dije, para el lado izquierdo es igual, solo que después de sacarle n píxeles del lado izquierdo, seteamos la coordenada de graficación del bloque en 0, así el mismo se graficara en el borde izquierdo, y no fuera de la pantalla. (Si aun no entendiste bien esto, te recomiendo dibujar en una hoja de papel un rectángulo grande [que seria el back buffer] y uno pequeño [que seria el sprite] saliendo fuera del mismo, y fijate que modificaciones deberías hacerle para que no salga fuera del rectángulo grande)

 

2.4.Parallax Scrolling

 

Sin duda la parte mas divertida para programar, el Scroll Parallax (me encanta como suena, creo que me voy a cambiar el nick).

No se de donde viene el nombre de Parallax, de todas formas, todos lo relacionamos con un paneo suave de cámara, o un Smooth Scroll, es lo mismo.

El Scroll que tenemos hasta ahora es muy brusco, ya que la cámara avanza de a bloques (que serian 16 píxeles (recordar ColMap), lo cual es extremadamente poco atractivo, tenemos que hacer que el Scroll sea de a 1 píxel, para lograr un paneo suave y agradable, para esto, debemos modificar la sección de código donde dibujamos el mapa. Para que no tengas que volver atrás, aquí esta nuestro código actualmente:

 

For Y = 0 To 14

For X = 0 + ColMap To 20 + ColMap

Bloq = Mid$(Mapa(Y), X, 1)

If Bloq <> " " Then

posX = ((X) * 16) - (ColMap * 16)

posY = (Y * 16)

GraficarBloque (PosX, PosY, Bloque)

        End If

       Next

Next

 

Otra vez, GraficarBloque es una función inventada ahora solo para simplificar el código, la línea resaltada es a la cual tenemos que prestarle atención, debemos agregarle algo que nos permita hacer que el mapa (entero) se desplace suavemente, esto es, si señores (y señoritas), una variable cualquiera que luego veremos como manejamos.

Así, esa línea resaltada será reaplazada por esta:

 

posX = ((X) * 16) - (ColMap * 16) – MapDspCol

 

Que es lo mismo que la anterior, solo que le restamos MapDspCol  (MapaDesplazamientoColumna)

Ahora, lo único que tenemos que hacer es manejar MapDspCol  y ColMap de manera que tengamos un buen Smooth Scroll.

 

Recordemos que hacia ColMap y que función tiene ahora MapDspCol:

 

ColMap es la posición de la cámara, en bloques, o sea que si incrementamos (o decrementamos) su valor, tenemos un Scroll de a 16 píxeles (que es el tamaño de los bloques).

 

MapDspCol nos proporciona un desplazamiento del mapa en  la pantalla, si modificamos este valor, vamos a ver que todo el mapa se mueve de a píxeles (o bueno, en la cantidad que hayamos aumentado la variable), pero, no tiene la misma función que ColMap, MapDspCol puede ser visto como el control de posición horizontal del mapa en la pantalla (algo así como el potenciómetro del monitor, pero del mapa).

 

Bien, ahora tenemos que combinar estas dos variables para tener un Smooth Scroll, esto es sencillo, ahora que entendimos que hace cada variable.

 

Vamos a analizar que pasaría si vamos a la derecha, voy a ir tirando algunas posibilidades, creo que así se va a entender, imagina que estoy al lado tuyo diciéndote esto:

 

- ¿qué pasa si aumento ColMap? 

- El mapa se mueve de a 16 píxeles hacia la izquierda, teniendo un Scroll brusco.

- ¿Y si uso MapDspCol para desplazar el mapa de a píxeles hasta llegar a los 16, y después   incremento ColMap?

- En los primeros 16 píxeles funcionaria, pero después el mapa se empezaría a ir a la izquierda de la pantalla cada vez mas y dejaríamos de verlo,

-¿Y si aumento MapDspCol y cuando llega a los 16 píxeles aumento ColMap y vuelvo a poner MapDspCol en 0?

- Bien ahí, es así como funciona.

 

Tal vez con una explicación grafica se entendería mejor, lo mejor puede ser jugar con MapDspCol y ColMap en el código fuente de demo, anulando MapDspCol y viendo que pasa, por ejemplo.

Bien, y ahora la implementacion del Parallax Scroll: son dos piezas de código, una para cuando voy a la izquierda, y otra cuando voy a la derecha.

 

'//////////// Muevo la "camara" a la derecha /////////////////

If ColMap <= 58 Then

MapDspCol = MapDspCol + 1

If MapDspCol > 15 Then

MapDspCol = 0

ColMap = ColMap + 1

End If

End If

'/////////////////////////////////////////////////////////////

 

'//////////// Muevo la "camara" a la izquierda ///////////////

If ColMap > 1 Then

MapDspCol = MapDspCol - 1

If MapDspCol < 0 Then

MapDspCol = 15

ColMap = ColMap - 1

End If

End If

'////////////////////////////////////////////////////////////

 

Las líneas en verde son para impedir que no nos vayamos fuera del mapa.

 

3.0 Game Loop

 

Bien, creo que hemos terminado de implementar todos los features del demo, ahora, falta algo.

Tenemos que ver el orden en que viene todo implementado, el pseudo-código del game loop del demo se encuentra a continuación.

              

GAME LOOP

{

  .Dibujo el fondo

 

  BARRIDO DEL ARRAY

  {

    .Me fijo que tipo de bloque es

    .Armo el RECT para elegir el sprite

    .Clipping

    .Dibujo el bloque

  }

 

  .Movimiento de la cámara

}

 

4.0 Conclusión

 

Hemos aprendido a manejar, dibujar, y desplazar un mapa para un tile based game, este tipo de manejos de mapa puede ser usado para una amplia gama de juegos, de todas formas, para algo mas sofisticado, podría convenir usar una lista enlazada, por ejemplo (con lo cual nos tendríamos que trasladar a otro lenguaje, C++ preferentemente), pero de todas formas, podemos aprender mucho con este tipo de técnicas. Además, con Visual Basic no vamos a apuntar a hacer algo extremadamente sofisticado.

 

4.1 mpdv

 

Personalmente siempre me gustaron los tutoriales, encontrar algo que necesitaba y aprender de ahí, de todas formas, siempre lo he hecho, y recomiendo, antes de buscar un tutorial*, intentar pensar, e idear un método propio, se aprende mucho experimentando técnicas propias, sobre todo cuando es 2D, en 3D puede complicarse un poco mas. Además, me ha pasado, y nada se compara a hacer algo con un método, y después ver que un programador en la otra punta del mundo, implemento algo de forma similar.

 

*Solo se aplica a lo que son técnicas, puede resultar imposible aprender DirectX sin documentación, por ejemplo.

 

5.0. Contactando al autor.

 

Pueden contactarme con ICQ o MSN a las direcciones que aparecen debajo, para realizar cualquier tipo de preguntas, comentarios, sugerencias, criticas, o simplemente hablar, me gusta conocer gente nueva.

 

ICQ# 115506483

MSN: silencer_ar@hotmail.com

 

Pueden también escribirme a mi dirección de e-mail (la misma de MSN), o encontrarme en el canal #programadores de irc.ciudad.com.ar (un abrazo a todos mis amigos allí!)

 

GreetingZ a todo el equipo de HammerHood Development Team

 

Espero que esto les haya sido de ayuda, y ansío escuchar noticias de ustedes, cuídense, suerte.

 

Assembly [Rodrigo Pelorosso]

 8/7/2002

Bs.As. Argentina