Sprites

 
Hardware
Modo 13h
Retrazado Vertical
Primitivas
La Paleta
Pantallas Virtuales
Sprites
Tablas Prefedifinidas
Efectos
Texto
Formatos gráficos
Periféricos
Optimizaciones
Herramientas
Cursos
Links
Foro
Correo Electrónico


¿Qué son y para qué sirven  los sprites?

Un sprite es simplemente una figura o imagen de dimensiones no muy grandes, que puede desplazarse por la pantalla, es decir que posee movimiento. Hay que tener claro que tambien existen los bitmaps, que tambien son como los sprites, pero no poseen movimiento, es decir es una agrupación de pixeles que forma una figura, y por lo tanto es estática. 

Un sprite está formado por varios frames o cuadros, lo cuales forman una animación, por ejemplo nuestro protagonista de un juego de plataforma, o un juego de naves, etc. Así tenemos que un frame o cuadro es simplemente uno de los dibujos que se muestran en la pantalla.

Los sprites pueden tener cualquier tamaño, y normalmente tendrán asociadas unas variables, las cuales por ejemplo pueden representar su posición en la pantalla, el ancho y el alto del sprite, etc. Algunas veces pueden tener factores como la velocidad, el estado del sprite. Por ejemplo en un juego un sprite tendrá varias variables, como por ejemplo una para saber si colisionó o choco con otro sprite, etc.

Los sprites siempre son rectangulares, aunque al verlos en un programa no lo parezcan, esto se debe a técnicas que se utilizan una de ellas es el uso de mascaras.

La principal función de los sprites es la de las animaciones, ya sea en una presentación, juego, etc.

Los sprites son muy usados en los juegos 2-D, ya sean de plataformas, aventuras gráficas, etc. Aunque hay excepciones donde los sprites pueden ser utilizados en juegos 3-D, un ejemplo de ello es el conocidisimo Wolfenstein 3D, de la empresa ID Software, aunque no lo crean este juego utilizaba sprites, a diferencia de los ultimos juegos tridimensionales como el Quake, Duke Nukem, etc.

Ejemplo de juegos que han utilizado sprites son el Street Fighter, Mortal Kombat, Killer Instinct, y clásicos como The Prince of Persia, la saga Keen Commander, etc. 


Volver


Creando un sprite

Antes de crear un sprite, tendremos que crearnos una estructura que almacene todas las variables necesarias y los frames que formarán el sprite. Esto en C se puede hacer así:
 

typedef struct
{
    int x, y;                                                    // Posición del sprite en la pantalla 
    unsigned char ancho, alto;                     // Ancho y alto del sprite 
    unsigned char num_frames;                   // Número de frames que tiene el sprite
    unsigned char *frame[MAX_FRAMES];   // Cada uno de los frames que forma el sprite
} t_sprite;

Hemos creado un tipo de dato llamado t_sprite, que contiene las variables básicos para trabajar con un sprite.

Dentro de la estructura definimos a los frames de esta forma:

unsigned char *frame[MAX_FRAMES];

lo cual es un arreglo de punteros, la gracia de hacer esto que podemos crear un sprite de cualquier tamaño. MAX_FRAMES es una constante que puede ser definida así:

#define MAX_FRAMES 10

Para crear cada frame deberemos reservar la memoria necesaria.

Si de dan cuenta, hay solo un inconveniente. Imaginemos que tenemos un sprite de solo 3 frames, y nosotros definimos ese arreglo de tamaño 10, por lo que se desperdiciaran 7 posiciones. Esto se puede solucionar usando listas enlazadas, pero para no complicarnos lo haremos así no mas.

Ya tenemos una estructura para guardar nuestros sprites, pero ahora ¿cómo almaceno los sprites en el disco duro para poder cargarlos después desde mi programa? Para esto hay varias posibilidades todas basadas en el uso de archivos, una es tener archivos independientes, es decir un archivo para cada frame. Pero esto puede resultar incomodo si tenemos un sprite por ejemplo de 50 frames, tendríamos que tener 50 archivos. En todo caso nunca tendremos tantos. Para tener archivos independientes podríamos usar los típicos archivos *.CEL del Autodesk Animator o bien crearnos un tipo de archivo a nuestra medida y esto será lo que haremos nosotros. Otra forma sería guardar todos los frames en un mismo archivo de un formato propio, o en formatos tradicionales como el GIF, PCX, BMP. De estos tres el más recomendable es el PCX, ya que es mas fácil de trabajar y comprime las imágenes. 


Volver


Creando nuestro propio formato de sprite

Realizar esto no es complicado, simplemente hay que conocer el manejo de archivos en C. Lo que haremos será crearnos un archivo que tendrá la extensión SPR, y tendrá la siguientes estructura:

CABECERA:

0 byte: ancho del sprite
1 byte: alto del sprite

IMAGEN: resto del archivo desde el byte 2.

Los 2 primeros bytes contendrán el ancho y alto de nuestro sprites y el resto del archivo será la información de cada pixel que forma el frame.
Así el tamaño de nuestro archivo se podrá calcular de la siguiente forma:

Tamaño = ancho * alto + 2;

La pregunta ahora es como creamos nuestro archivo, esto se puede hacer de varias formas, las más simple es tener nuestro sprite en un formato tradicional como el PCX, entonces crearnos un “programita” que transforme el archivo PCX en un archivo con nuestro formato. En la sección herramientas podrás encontrar un programa muy simple, que realice hace un tiempo, y hace esto. Su funcion es leer un archivo PCX, luego lo muestra en pantalla, luego guarda el ancho y el alto de la imagen y lee cada pixel y lo va guardando en un archivo. El programa también guarda la paleta. Esta última es muy importante para conocer la información de los colores que usa nuestro sprite.

Veamos como nos quedaría la funcion que lee un simple frame desde un archivo:
 

int LoadSpr(char *filename, unsigned char **frame)
{
    enum{ARCHIVO_NO_ENCONTRADO=0, NO_HAY_MEMORIA=-1, MUY_GRANDE=-2, OK=1};
    int ancho, alto;

    FILE *f;

    if((f=fopen(filename, "rb"))==NULL)  return ARCHIVO_NO_ENCONTRADO;

    // Leemos el ancho y alto del frame
    ancho=fgetc(f);
    alto=fgetc(f);

    if((ancho>255) || (alto>255))
    {
        fclose(f);
        return MUY_GRANDE;
    }

    // Reservamos memoria para la imagen, el ancho y el alto
    if((*frame=(unsigned char *)malloc((ancho*alto)+2))==NULL)
    {
        fclose(f);
         return NO_HAY_MEMORIA;
    }

    // Copiamos el ancho y el alto
    memcpy(*frame, &ancho, 1);
    memcpy(*frame+1, &alto, 1);

    // Leemos la imagen
    fread(*frame+2, sizeof(char), (ancho*alto), f);

    fclose(f);
    return OK;
}

Lo que hace esta funcion es recibir como parámetro el nombre del archivo y un puntero a un puntero a un dato de tipo byte. Luego abrimos el archivo y vemos si existe o no. Si no existe devolvemos un error. En caso que exista leemos la cabecera para conocer las dimensiones de nuestro frame si es mas grande que 55 devolvemos un error. Si el tamaño del frame es menor o igual a 255,  reservamos la memoria necesaria, así nuestro puntero contendrá en sus dos primeros bytes el ancho y el alto, del byte 2 en adelante estará la información de la imagen. Luego leemos los datos de la imagen del archivo y listo.

Como vemos ya podemos leer un archivo con nuestro formato y guardar los datos en memoria. Al final de nuestro programa deberemos liberar la memoria reservada, para hacer esto simplemente llamamos a la funcion free con el puntero al frame como parámetro.

Ahora veremos como crearnos una función que abra varios archivos de tipo spr, pero con la condición que todas tengan el mismo nombre y todos se encuentren enumerados. 

Veamos:
 

int LoadSprite(char *filename, t_sprite *sprite, unsigned char num_frames)
{
    unsigned char i, num;
    int r;
    char *nombre, *numero;

    // Guardamos total de frames
    sprite->num_frames=num_frames;
    // Inicializamos a cero las coordenadas del sprite
    sprite->x=sprite->y=0;

    // Cargaremos cada uno de los frames que forma el sprite
    for(i=0, num=1; i<num_frames; i++, num++)
    {
        // Creamos el nombre del archivo
        strcpy(nombre, filename);
        itoa(num, numero, 10);
        strcat(nombre, numero);
        strcat(nombre, ".spr");
        // Lo cargamos
        r=LoadSpr(nombre, &(sprite->frame[i]));
        // Si hubo algun error salimos del for
        if(r!=1) break;
    }

    // Si hubo algun error al cargar algun un frame, liberamos la memoria reservada hasta 
    // ese momento
    if(r!=1)
    {
        if (i!=0) // Si se cargo en memoria al menos un frame...
        {
             for(num=0; num<i; num++)
                 free(sprite->frame[num]);
        }
        return r; // Retornamos el error producido
     }

     // Guardamos el ancho y el alto del sprite
    memcpy(&(sprite->ancho), &(sprite->frame[0][0]), 1);
    memcpy(&(sprite->alto), &(sprite->frame[0][1]), 1);

     return 1; // Todo OK!
}

Espero que hayan entendido la función, aunque no es de lo mejor, lo ideal sería usar listas enlazadas. Tal vez más adelante en otra sección incluya como hacer esto último.
La secuencia de archivos para un sprite debería tener los nombres de esta forma, por ejemplo si los archivos son de un auto y tenemos 3, los archivos deberían llamarse: auto1.spr, auto2.spr y auto3.spr. Así al llamar a la funcion LoadSprite lo haríamos de la siguiente forma:

r=LoadSpr(“auto”, &sprite, 3);

suponiendo que sprite se definió así:

t_sprite sprite;

donde r, será una variable de tipo entero que guardará el valor que regresa la funcion y 3 corresponde al numero de frames que conforma el sprite.

Ojo que la funcion puede devolver los siguientes valores:

 0:  Archivo no encontrado
 1:  OK!
-1:  Memoria insuficiente
-2: Tamaño de la imagen muy grande

Pero esto no es todo debemos crearnos una funcion que elimine todos los frames de la memoria:
 

void FreeSprite(t_sprite *sprite, unsigned char num_frames)
{
    unsigned char i;

    sprite->x=sprite->y=0;
    sprite->num_frames=0;
    sprite->ancho=sprite->alto=0;

    for(i=0; i<num_frames; i++)
         free(sprite->frame[i]);
}

De esta forma si por ejemplo queremos eliminar los frames del auto lo haríamos así:

FreeSprite(&sprite, 3); 


Volver


Dibujando sprites en la pantalla

Ya sabemos como dejar un sprite en memoria, pero ahora ¿cómo lo colocamos en la pantalla? Para hacer esto podemos tomar el sprite y leer fila a fila la imagen y visualizarlas. Lo ideal sería que esta funcion que pone un sprite en la pantalla estuviera hecha en ensamblador. Pero por ahora nosotros veremos solo un funcion en C:
 

void PutSprite(int x, int y, unsigned char *frame, unsigned char *where)
{
    unsigned char ancho=frame[0], alto=frame[1];
    int col, fil, inc=2;

    for(fil=0; fil<alto; fil++)
        for(col=0; col<ancho; col++)
            where[((fil+y)<<8)+ ((fil+y)<<6)+col+x]=frame[inc++];
}

Si se dan cuenta, esta función hará que nuestro sprite aparezca como un rectángulo, lo cual no resulta muy bonito, a continuación veremos como solucionar esto. 


Volver


Sprites transparentes

Un sprite transparente es un sprite que tiene asociado un color específico como fondo, por ejemplo el color 0 (negro). Así cuando queramos dibujar un sprite, simplemente tendremos que “preguntar” si el color que vamos a dibujar es el color de la mascara, si es así no lo dibujaremos y pasaremos al otro. Cuando encontremos un color distinto de 0 dibujaremos dicho color. 

Casi siempre se utiliza el color 0 como máscara, ya que al realizar la función en ensamblador para colocar un sprite, es mucho más fácil verificar si una variable tiene el valor 0.

Entonces nuestra rutina para colocar sprites transparentes se haría así:
 

void PutSprite(int x, int y, unsigned char *frame, unsigned char *where)
{
    unsigned char ancho=frame[0], alto=frame[1], color;
    int col, fil, inc=2;

    for(fil=0; fil<alto; fil++)
        for(col=0; col<ancho; col++)
        {
             color=frame[inc++];
             if(color!=0)
                 where[((fil+y)<<8)+ ((fil+y)<<6)+col+x]=color; 
        }
}

De ahora en adelante nosotros utilizaremos esta función, ya que sería muy “feo” que nuestro sprite se viera rectangular. 


Volver


Animaciones

Realizar una animación de un sprite no es nada difícil.  Imaginemos que tenemos un “sprite” que comprende varios frames y queremos animarla y desplazarla sobre un fondo estático.

Para hacer esto tenemos tres formas de hacerlo. No son las únicas ni las mejores, pero si son las más comunas y sencillas, veamos:

Método 1:“Utiliza harta memoria”

1) Crear e inicializar dos pantallas virtuales (pv1 y pv2).
2) Dibujar el fondo en pv2.
3) Copiar pv2 a pv1.
4) Dibujar sprites en pv1.
5) Esperar el retrazado vertical.
6) Copiar pv1 a la VGA.
7) Si es necesario, modificar la posición de los sprites y de los frames.
8) Repetir desde el paso 3.

Ventajas

El mas fácil de implementar.
Es el mejor método cuando se trata de muchos sprites.
Reduce al mínimo o a nada el parpadeo.

Desventajas:

- Necesita mucha memoria, es decir dos pantallas virtuales más los datos de los sprites,
  lo cual puede ser mas de 130 Kb.
- No es el más rápido cuando son pocos sprites.
 

Método 2: “No necesita mucha memoria”

1) Dibujar el fondo en la VGA o copiarlo de alguna pantalla virtual.
2) Almacenar la parte del fondo donde se va dibujar el sprite.
3) Dibujar el sprite.
4) Modificar posicion del sprite
5) Reponer la parte del fondo que se obtuvo en el paso 2.
6) Repetir desde el paso 2.

Ventajas:

- No necesita ninguna pantalla virtual.
- Funciona para pocos y pequeños sprites.

Desventajas:

- Dibujar en la VGA es más lento que dibujar en una pantalla virtual.
- Puede causar mucho parpadeo con sprites grandes.
 

Método 3: “Un poco de memoria”

1) Crear e inicializar una pantalla virtual (pv).
2) Dibujar el fondo en pv.
3) Copiar pv a la VGA.
4) Dibujar sprite en la VGA.
5) Modificar posicion del sprite
6) Copiar la porción de fondo de pv a la VGA.
7) Repetir desde el paso 4.

Ventajas:

- No necesita tanta memoria, solamente una pantalla virtual.
- Es más rápido cuando se trata de pocos sprites.
- Hay menos parpadeo que en el método 2.

Desventajas:

- Es un poco más complejo que lo otros métodos.
- Con muchos sprites puede haber parpadeos.
 

Estos tres son los métodos más comunes, pero pueden haber más. Lo que deben hacer ustedes es adaptar el método que más se ajuste a su programa, haciendo un balance entre la memoria, velocidad y calidad. Incluso pueden combinar dos o más métodos, por ejemplo, en el método 3, en lugar de dibujar el fondo y los sprites en la VGA, pueden dibujarlos en otra pantalla virtual y copiar todo después a la VGA como en el método 1. Eso reduce mucho el parpadeo e incremente la velocidad, pero consume más memoria.

Ya sabemos como crear una animación, pero se nos queda algo importante en el tintero, que pasa si cargo un sprite, ¿cómo se cuales son sus colores? Bueno muy simple, conociendo su paleta de colores, para esto debemos tener en un archivo la paleta de colores correspondiente al sprite que visualizaremos. Si recuerdan una paleta tiene las valores RGB de los 256 colores que pueden estar mostrándose en la pantalla simultáneamente en la pantalla. Entonces lo que haremos será leer desde un archivo la paleta de colores, luego almacenarla en un arreglo t_paleta y colocarla en el sistema con SetPal(). Para esto crearemos una funcion que nos lea la información de una paleta desde un archivo, es muy simple. Veamos:
 

int LoadPal(char *filename, t_paleta pal)
{
    enum{ARCHIVO_NO_ENCONTRADO=0, ARCHIVO_NO_VALIDO=-1, OK=1};
    FILE *f;

    if((f=fopen(filename, "rb"))==NULL)  return ARCHIVO_NO_ENCONTRADO;

    if(filesize(f)!=768)
    {
        fclose(f);
        return ARCHIVO_NO_VALIDO;
    }

    fread(pal, sizeof(char), 768, f);
    fclose(f);

    return OK;
}

Esta función recibe como parámetros el nombre de la paleta, que generalmente tiene la extensión pal, tambien recibe una variable de tipo t_paleta para guardar los datos leídos. Se abre el archivo y se verifica que el archivo tenga el tamaño 768 (recuerden: 256*3=768), y al final se lee la paleta. La funcion filesize la podrás encontrar en el archivo vgalib.h

Ahora veremos como haríamos una animación utilizando el método 1. Lo primero que se hará, será cargar todos los sprites en memoria y cargar la paleta correspondiente. Por ejemplo imaginemos que tenemos la toda la secuencia de frames de un “mono” caminando. Supongamos que el sprite está formado por 5 frames. Entonces si queremos mostrar la animación ser haría de esta forma:
 

t_sprite mono;            // Variable que guarda los datos del sprite
t_pvirtual pv1, pv2;    // Pantallas virtuales
t_paleta pal;              // Para guardar la paleta del sprite
int num=0;                 // Número del frame a mostrar

SetPVirtual(&pv1);     // Reservamos memoria para las pantallas virtuales
SetPVirtual(&pv2);

LoadSprite(“mono”, &mono, 5);   // Cargamos el sprite 
LoadPal(“mono.pal”, pal);           // Cargamos la paleta

mono.x=0;  // Inicializamos variables de posicion del sprite
mono.y=100;

SetMode(GRAFICO);     // Inicializamos el modo 13h (320x200)
SetPal(pal);                  // Colocamos nuestra paleta

do
{
    FlipTo(pv1, pv2); // Copiamos pv2 a pv1
    // Dibujamos el sprite 
    PutSprite(mono.x, mono.y, mono.frame[num], DIR_VGA);
    // Cambiamos el frame del sprite
    num++;
    if(num>2) num=0;
    mono.x+=2;    // Modificamos posicion
    // Verificamos si se sale de la pantalla 
    if(mono.x+mono.ancho>320) mono.x=0;
    WaitRetrace();
    // Copiamos pv1 a la VGA
    Flip(pv1);
} while(!kbhit());

SetMode(TEXTO);          // Volvemos al modo texto

FreePVirtual(&pv1);       // Liberamos memoria de las pantallas virtuales
FreePVirtual(&pv2);
FreeSprite(&mono, 5);   // Liberamos la memoria del sprite

Como ven no es difícil crear animaciones. Tengan en cuenta que en el ejemplo no verificamos ningún error, como por ejemplo si no hay memoria o si no existe algún archivo. 


Volver


Ejemplo

Ahora podrán encontrar un ejemplo similar al anterior, pero más completo y con los archivos de los sprites y paleta.

Ver Librería gráfica: vgalib.h
 

Ver ejemplo
 
 

Bajar todo Bajar todo (35.7 Kb)
Volver
Anterior
Página principal
Siguiente
1
Hosted by www.Geocities.ws

1