Documentación de MiMotor

Anuncio
Documentación de MiMotor
Fernando Chamizo
14 de octubre de 2008
Índice
1. Información rápida
2
2. Parámetros
7
3. Variables del sistema
8
4. Variables globales
9
5. Imágenes
10
6. Sonido
10
7. Teclado y ratón
11
8. Interrupciones
11
9. Colisiones
12
10.Funcionamiento interno
13
11.Versiones y posibles extensiones
15
12.Más ejemplos
16
13.Apéndice: Instalar y compilar con SDL
22
1
MiMotor1.1
2
El rapidı́simo desarrollo de la informática y la dura competencia han hecho que se preste
menos atención a la documentación: ¿para qué leer cientos de páginas del manual de uso de
un programa si mañana, pasado o en unos meses tendré otro mejor? Esta tendencia ha tenido
sus beneficios, principalmente que los desarrolladores han mirado más por el usuario general
dotando a los programas de interfaces “para tontos” que no requieren apenas documentación.
A menudo ya no hay fı́sicamente un libro de instrucciones sino información diseminada en unas
cuantas páginas web o ayuda en lı́nea. Por supuesto hay también una parte negativa pero no
entraremos aquı́ en ello.
El motor gráfico que vamos a documentar es sinceramente malo, no hay que esperar a
mañana o pasado para encontrar algo mejor sino que ya existen multitud de motores libres
completı́simos que tienen nivel suficiente para desarrollar videojuegos profesionales actuales,
mientras que con el nuestro sólo se llegarı́a a poco más del nivel de los años 80. Por ello hay
que cuidar todavı́a más al posible lector y, además de escribir bien claras desde el principio la
advertencia anterior, darle una primera seción de información rápida para que pueda saber a
qué atenerse sin perder mucho tiempo.
Después de la publicidad negativa ¿por qué alguien va a querer emplear este motor gráfico?
Su uso es fundamentalmente académico y su gran ventaja la simplicidad. Es suficientemente
breve para poder leer y entender todo el código y es multiplataforma. Además en la actualidad
hay un gran revival de aquellos sencillos juegos de los 80 a través de los teléfonos celulares
y aunque el lenguaje es diferente (Java en su versión J2ME en vez de C) todavı́a se pueden
conservar algunas ideas fundamentales. Los requisitos son mı́nimos: el motor ha funcionado
correctamente sin modificaciones en una reliquia a 135 Mhz.
1.
Información rápida
MiMotor es un minimotor gráfico multiplataforma basado en SDL enteramente escrito
en C y extremadamente simple. La versión en curso es MiMotor1.1.c con fichero de cabecera
MiMotor1.1.h. Un requisito básico para emplearlo es tener un conocimiento al menos somero
de la biblioteca SDL (en http://www.loosersjuegos.com.ar/referencia/libros/libros.php se puede
encontrar material completo), que debe estar instalada en la máquina en uso (véase §13) y
naturalmente el lenguaje C se da por supuesto.
Los comandos para activar y desactivar el motor son respectivamente motoron() y motoroff().
El programa (inoperante) más breve que lo emplea es:
#include "MiMotor1.1.h"
int main(int argc, char *argv[]){
motoron();
motoroff();
return EXIT_SUCCESS;
}
Para compilar este programa o cualquier de los sucesivos deben estar instaladas las bibliotecas de desarrollo de SDL (como se indica en §13). En Linux con gcc si el código anterior es
programa.c y los ficheros MiMotor1.1.c y MiMotor1.1.h están en el mismo directorio el comando
serı́a gcc programa.c MiMotor1.1.c -o mi ejecutable.out `sdl-config --cflags --libs` -lSDL image
-lSDL mixer -lm.
El motor utiliza una nomenclatura “orgánica” con la que a cada unidad básica, sprite, se
le llama ser, a la imagen asociada cara y al conjunto de reglas que lo gobiernan cerebro.
La función escena es la más importante, la única a través de la cual se pueden comunicar el
motor gráfico con el programa que lo usa. Contiene al bucle principal y conecta el motor con
MiMotor1.1
3
las rutinas donde se inician los seres, se gestionan las interrupciones y se tratan las colisiones.
Su prototipo es el siguiente:
void escena(
void (*creaseres)(void),
void (*leetec)(void),
Uint32 (*interrup)(struct Ente *ser,Uint32 ahora),
void (*colision)(struct Ente *ser)
);
/*
/*
/*
/*
inicio de seres */
lectura del teclado */
gestión de interrupciones */
gestión de colisiones */
Consideremos en primer lugar un ejemplo en el que no se lee el teclado, no hay colisiones y la
gestión de interrupciones es una simple rutina por defecto. Todo esto se indica asignando NULL
a los tres últimos argumentos. Pretendemos mostrar una bola que se mueva horizontalmente en
la pantalla rebotando en los bordes y para ello usaremos escena(creaunabolita,NULL,NULL,NULL).
En la rutina creaunabolita cargaremos una imagen (la de la bolita) y asignaremos a un ser
ciertos parámetros, entre ellos la imagen. Empleamos:
void creaunabolita(void){
cargaspri("./images/bolita.png",0,NULL); /* Carga imagen */
// nace(tipo,cont,(x,y),(vx,vy),cerebro,cara)
nace(0,200,320.0,400.0,9.0,0.0,c_rebota,album[0]);
}
La primera función, cargaspri, sirve para cargar la imagen indicada como primer argumento (en
este caso bolita.png que está en el directorio images, los formatos .gif, .jpg y .bmp también son
válidos) y almacenarla en el primer hueco libre de la matriz de imágenes album[]. Más adelante
veremos que los otros dos argumentos permiten cargar diferentes fotogramas. Los parámetros
del ser se podrı́an asignar directamente a los miembros de la estructura Ente a la que responde
cada ser pero es más sencillo emplear nace. En la lı́nea comentada se indica el significado de
cada argumento. Los dos primeros son enteros de utilidad general y en principio el primero
está pensado para indicar el tipo de ser o sus propiedades y el segundo como contador. Los
cuatro siguientes son float que indican la posición en la pantalla y la velocidad, aunque la
velocidad puede usarse para otros fines. El siguiente apunta a una función, el cerebro, que
indica el comportamiento del ser y el último es la cara, la imagen asignada al ser. Con nuestra
instrucción tendremos un ser que inicialmente tiene su contador a 200, y está en la posición
(320.0,400.0) con velocidad (9.0,0.0).
A continuación debemos definir el cerebro correspondiente de este ser que viene dado por
la función c_rebota
void c_rebota(Ente *ser){
ser->xa=ser->x; /* Actualiza coordenada */
ser->x+=ser->vx; /* A~
nade la velocidad */
if(ser->x>ANCHO-20){ ser->x=ANCHO-20; ser->vx*=-1;} /* Rebotes */
if(ser->x<0){ ser->x=0; ser->vx*=-1;}
if(!--ser->cont) V2_FLAGSS|=FESC; /* Termina */
}
La primera lı́nea asigna a la coordenada x anterior, ser->xa, la de la última posición, ser->x, que
va a ser modificada. Esta actualización de las coordenadas antiguas es necesaria siempre que
vayan a sufrir cambios. La siguiente lı́nea cambia la posición añadiendo la velocidad. Si estamos
cerca del borde la velocidad cambia de signo, lo que produce el rebote. En MiMotor1.1.h están
definidas las constantes ANCHO y ALTO que dan el ancho y el alto de la pantalla como 640 y 480
pixels. El 20 que se resta proviene de que la imagen bolita.png es de 16 × 16 pixels. Finalmente
si el contador del ser llega a cero entonces en, V2 FLAGSS se activa el bit que corresponde al flag
de escape. La rutina por defecto de gestión de interrupciones lo único que hace es salir cuando
este bit está activado.
Si queremos emplear una imagen de diferente tamaño podemos escribir en la primera de
las lı́neas que gestionan los rebotes
if(ser->x>ANCHO-ser->cara->w){ ser->x=ANCHO-ser->cara->w; ser->vx*=-1;} /* Rebotes */
MiMotor1.1
4
porque ser->cara es la cara (la imagen) del ser y ser->cara->w es su ancho (width), mientras que
ser->cara->h es su alto (height).
En total el programa serı́a:
/* /ficheros/rebotes.c */
#include "MiMotor1.1.h"
#define DIRECI "./images/"
#define DIRECS "./sounds/"
/* Directorio de imágenes */
/* Directorio de sonidos */
void c_rebota(Ente *ser)
{
ser->xa=ser->x; /* Actualiza coordenada */
ser->x+=ser->vx; /* A~
nade la velocidad */
/* Rebotes */
if(ser->x>ANCHO-ser->cara->w){ ser->x=ANCHO-ser->cara->w; ser->vx*=-1;}
if(ser->x<0){ ser->x=0; ser->vx*=-1;}
if(!--ser->cont)
V2_FLAGSS|=FESC; /* Termina */
}
void creaunabolita(void){
cargaspri(DIRECI "bolita.png",0,NULL);
/* Carga imagen */
// nace(tipo,cont,(x,y),(vx,vy),cerebro,cara)
nace(0,200,320.0,400.0,9.0,0.0,c_rebota,album[0]);
}
int main(int argc, char *argv[]){
motoron();
escena(creaunabolita,NULL,NULL,NULL);
motoroff();
return EXIT_SUCCESS;
}
Modificando el cerebro c rebota el comportamiento será otro. Por ejemplo, si quisiéramos
que el movimiento fuera oblicuo rebotando en todas las paredes el nuevo cerebro serı́a:
void c_rebota(Ente *ser)
{
ser->xa=ser->x; ser->ya=ser->y; /* Actualiza coordenadas */
ser->x+=ser->vx; ser->y+=ser->vy; /* A~
nade la velocidad */
/* Rebotes */
if(ser->x>ANCHO-ser->cara->w){ ser->x=ANCHO-ser->cara->w; ser->vx*=-1;}
if(ser->x<0){ ser->x=0; ser->vx*=-1;}
if(ser->y>ALTO-ser->cara->h){ ser->y=ALTO-ser->cara->h; ser->vy*=-1;}
if(ser->y<0){ ser->y=0; ser->vy*=-1;}
if(!--ser->cont)
V2_FLAGSS|=FESC; /* Termina */
}
Cambiemos también la llamada a nace a
nace(0,400,320.0,400.0,9.0,7.0,c_rebota,album[0])
duplicando la duración y añadiendo una velocidad vertical. Aproximadamente cada cerebro
funciona FPSM veces por segundo donde FPSM está definido en MiMotor1.1.h como 20, entonces un
valor inicial de n para el contador equivaldrá a una duración aproximada de n/20 segundos.
Si ahora quisiéramos una bandada de 40 bolitas con posiciones iniciales aleatorias y velocidad 5.0 en direcciones aleatorias deberı́amos cambiar creaunabolita por ejemplo de la siguiente
forma:
void creaunabolita(void){
int i=40;
MiMotor1.1
//
5
float th;
cargaspri(DIRECI "bolita.png",0,NULL); /* Carga imagen */
while(i--){
th=M_PI*(rand() % 20)/10.0;
nace(tipo,cont,(x,y),(vx,vy),cerebro,cara)
nace(0, 400,
rand() % (ANCHO-album[0]->w), rand() % (ALTO-album[0]->h),
5.0*cos(th), 5.0*sin(th),c_rebota,album[0]);
}
}
El número máximo de seres por defecto es 128 pero se puede modificar cambiando el valor de
la constante NMAXENTES en MiMotor1.1.h.
Importante: Forzando la velocidad de los seres, el tamaño de sus caras o su número
la animación empeora. Es el precio que hay que pagar por emplear un motor tan
simple. Una imagen 48 × 48 a 200 pixels por segundo pierde bastante nitidez y a
400 pixels por segundo es difı́cilmente usable, por otro lado a 100 pixels por segundo
sólo se nota una pequeña vibración. SDL es una biblioteca muy eficiente para
imprimir imágenes rápido en la pantalla pero la actualización para que realmente
se vean esas imágenes y la dificultad para sincronizar lleva a problemas con las
animaciones rápidas o de imágenes grandes.
Ahora partiremos del programa anterior con las modificaciones indicadas incluyendo las 40
bolitas, y vamos a dotarle de las rutinas para leer el teclado, gestionar las interrupciones y
gestionar las colisiones que antes habı́amos indicado con NULL en los argumentos de escena.
Veamos primero la lectura del teclado. El propio motor incluye alguna función que permite
leer cierto número de teclas almacenando el resultado en la variable del sistema V2_ESTTEC. Para
practicar con SDL aquı́ crearemos nuestro propio código pero todavı́a usando esta variable para
almacenar el resultado. La instrucción SDL_PollEvent(&event) analiza si hay algún evento (cambio
en el ratón, teclado, etc.) y en su caso almacena sus propiedades en su argumento, aquı́ en
un puntero a una estructura de tipo SDL_Event. La forma en que esta estructura codifica la
información es un poco larga de explicar. Aquı́ sólo utilizaremos el miembro type para saber si
se ha pulsado una tecla, SDL_KEYDOWN, y la función SDL_GetKeyState(NULL) que apunta a una matriz
que en la posición SDLK_[tecla] tiene un valor (de tipo Uint8) nulo si la tecla no está pulsada. El
siguiente ejemplo permite salir del programa si se pulsa la tecla Esc y alterna los valores 1 y
0 para V2_ESTTEC cuando se pulsa la barra espaciadora.
void espacioyescape(void){
Uint8 *keystate = SDL_GetKeyState(NULL);
SDL_Event event;
/* Si hay eventos */
if(SDL_PollEvent(&event)){
if((event.type==SDL_KEYDOWN)&&(keystate[SDLK_SPACE])) /* espacio => flip */
V2_ESTTEC=1- V2_ESTTEC;
if((event.type==SDL_KEYDOWN)&&(keystate[SDLK_ESCAPE])) /* escape => sale */
V2_FLAGSS|=FESC; /* Activa flag de escape */
}
}
Debemos modificar consecuentemente la llamada a escena añadiendo el nuevo argumento,
y en el cerebro cambiaremos el signo de la velocidad si V2_ESTTEC es 1, también podemos suprimir if(!--ser->cont) V2_FLAGSS|=FESC; para que
el programa funcione indefinidamente hasta que se pulse Esc. El nuevo listado completo será:
escena(creaunabolita,espacioyescape,NULL,NULL);
MiMotor1.1
6
/* /ficheros/rebmlee.c */
#include "MiMotor1.1.h"
#define DIRECI "./images/"
#define DIRECS "./sounds/"
/* Directorio de imágenes */
/* Directorio de sonidos */
void c_rebota(Ente *ser)
{
ser->xa=ser->x; ser->ya=ser->y; /* Actualiza coordenada */
/* Velocidad con signo */
ser->x+=(1-2*V2_ESTTEC)*ser->vx; ser->y+=(1-2*V2_ESTTEC)*ser->vy;
/* Rebotes */
if(ser->x>ANCHO-ser->cara->w){ ser->x=ANCHO-ser->cara->w; ser->vx*=-1;}
if(ser->x<0){ ser->x=0; ser->vx*=-1;}
if(ser->y>ALTO-ser->cara->w){ ser->y=ALTO-ser->cara->h; ser->vy*=-1;}
if(ser->y<0){ ser->y=0; ser->vy*=-1;}
if(!--ser->cont)
V2_FLAGSS|=FESC; /* Termina */
}
void creaunabolita(void){
int i=40;
float th;
cargaspri(DIRECI "bolita.png",0,NULL); /* Carga imagen */
while(i--){
th=M_PI*(rand() % 20)/10.0;
//
nace(tipo,cont,(x,y),(vx,vy),cerebro,cara)
nace(0, 400,
rand() % (ANCHO-album[0]->w), rand() % (ALTO-album[0]->h),
5.0*cos(th), 5.0*sin(th),c_rebota,album[0]);
}
}
/************/
/* LEETEC */
/************/
void espacioyescape(void){
Uint8 *keystate = SDL_GetKeyState(NULL);
SDL_Event event;
/* Si hay eventos */
if(SDL_PollEvent(&event)){
/* Termina si se cierra la ventana */
if((event.type==SDL_KEYDOWN)&&(keystate[SDLK_SPACE]))
V2_ESTTEC=1- V2_ESTTEC;
if((event.type==SDL_KEYDOWN)&&(keystate[SDLK_ESCAPE]))
V2_FLAGSS|=FESC; /* Activa flag de escape */
}
/* espacio => flip */
/* escape => sale */
}
int main(int argc, char *argv[]){
motoron();
escena(creaunabolita,espacioyescape,NULL,NULL);
motoroff();
return EXIT_SUCCESS;
}
El tercer argumento de escena es una función que gestiona las interrupciones cuyo prototipo es Uint32funcion(Ente*ser,Uint32ahora). El primer argumento se emplea si fuera necesario
efectuar manipulaciones con los seres, mientras que el segundo indica el tiempo al entrar en
la función. No vamos a utilizar ninguno por ahora. Lo fundamental es saber que un cero a la
MiMotor1.1
7
salida implica una salida de escena y que la salida tı́pica es el tiempo de entrada. Para hacer
algo original, pintaremos de rojo la pantalla antes de salir con Esc, para ello emplearemos:
Uint32 escapa(Ente *ser,Uint32 ahora){
/* Gestión de escape */
if( V2_FLAGSS&FESC ){
SDL_FillRect(screen,NULL,ROJO);
SDL_Flip(screen);
SDL_Delay(1000); /* Espera 1s */
return 0; /* Sale de escena */
}
return ahora; /* Por ahora innecesario */
}
conectado a escena con escena(creaunabolita,espacioyescape,escapa,NULL); y habiendo definido
con #define ROJO SDL_MapRGB(screen->format,223,47,47) que es la manera de escribir en SDL
el color RGB=(223,47,47) en el formato usado en la pantalla. El comando SDL_FillRect(screen,
NULL,ROJO) se explica por sı́ solo, SDL_Flip(screen) actualiza la pantalla y SDL_Delay(1000) espera
1 s=1000 ms (el argumento de SDL_Delay tiene una imprecisión, “granularidad”, de unos 10 ms).
Finalmente vamos a gestionar las colisiones. Para ello cambiemos un poco creaunabolita
sustituyendo la lı́nea cargspri por
ROJO
cargaspri(DIRECI "bolita.png",0,NULL); /* Carga imagen */
cargaspri(DIRECI "bolitaa.png",0,NULL); /* Carga imagen */
cargaspri(DIRECI "bolitar.png",0,NULL); /* Carga imagen */
nace(0,0, 0.0,0.0, 6.39,4.79, c_rebota, album[1]);
donde bolitaa.png y bolitar.png son las imágenes de una bolita azul y otra roja que se almacenarán en album[1] y album[2]. Queremos que cuando la única bolita azul (el ser que se crea con
el nace anterior) choque con alguna verde la convierta en roja. En el motor al primer ser de la
lista se le llama adan. Una posible rutina de colisión con este cometido es la siguiente, que debe
ponerse como último argumento de escena:
void chocaroja(Ente *ser){
float vx=adan->x - ser->x,vy=adan->y - ser->y; /* Radiovector */
/* Si es verde y está cerca */
if( ( ser->cara==album[0] ) && (vx*vx+vy*vy <16*16) ) ser->cara= album[2];
}
La constante 16 ∗ 16 es el cuadrado de la distancia para que se contabilice una colisión. Si se
quisiera variar el tamaño de las bolitas el valor exacto para que se produzca la colisión serı́a el
cuadrado de (album[0]->w + album[1]->w)/2.
Si el tipo de un ser tiene el bit de mayor peso activado entonces desaparece inmediatamente.
En la cabecera MUER está definido como la máscara 32768 = 215 . Por ejemplo, cambiando el
condicional de chocaroja por
if( ( ser->cara==album[0] ) && (vx*vx+vy*vy <16*16) ) ser->tipo|=MUER;
2.
Parámetros
El fichero de cabecera MiMotor1.1.h comienza cargando diversas bibliotecas con las siguientes
lı́neas:
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#include <SDL/SDL_mixer.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
MiMotor1.1
8
A continuación hay una definición de diferentes parámetros modificables que permiten
ajustar el motor:
/* Formato de pantalla */
#define ANCHO 640 /* Ancho de pantalla */
#define ALTO 480 /* Alto de pantalla */
#define BPP 4 /* Bytes por pixel */
/* Compilación */
#define PROBANDO 0 /* Modo de prueba */
/* Definiciones del motor */
#define DESCANSO 0 /* Milisegundos de descanso de la CPU */
#define FPSM 20 /* Frames lógicas por segundo divididas por 1.024 (20 ¿mejor 30?)*/
#define NMAXENTES 128 /* Número máximo de seres */
#define NMAXIMAG 128 /* Número máximo de imágenes */
#define NMAXSON 64 /* Número máximo de sonidos */
Todos quedan explicados excepto quizá PROBANDO, que cuando está activado a 1 da información
adicional acerca de la carga de imágenes y sonidos.
También en la cabecera está definida la estructura principal Ente
typedef struct Ente {
float xa,ya; /* Coordenadas antiguas */
float x,y; /* Coordenadas actuales */
float xi,yi; /* Coordenadas interpoladas */
float vx,vy; /* Vector velocidad */
Uint16 tipo; /* Tipo de ser (y flags) */
Uint16 cont; /* Contador de propósito general */
SDL_Rect pisada; /* Rectángulo donde se imprime */
void (*cerebro)(struct Ente *ser); /* Cerebro */
SDL_Surface *cara; /* Imagen */
} Ente;
que representa un sprite básico regulado por una función cerebro, lo que llamamos un ser.
3.
Variables del sistema
Los parámetros variables del motor no son miembros de una gran estructura, como serı́a
natural, sino que se almacenan en direcciones de memoria consecutivas. Constituyen las variables del sistema (un tributo a los tiempos del Spectrum). Hay de dos tipos, de 2 bytes y de 4
bytes distribuidos en dos tablas. Los inicios de cada tabla son variables globales
Uint16 *varsis2; /* Las variables del sistema de 16 bits */
Uint32 *varsis4; /* Las variables del sistema de 32 bits */
asignadas con malloc para que se reserve espacio.
varsis2=(Uint16*)calloc(NUMVS2,2);
varsis4=(Uint32*)calloc(NUMVS4,4);
Se accede al valor de una variable del sistema mediante V2_nombre o V4_nombre. La definición
interna de V2_nombre es en general *(varsis2+nombre) y lo mismo para V4_nombre. Es decir, nombre
indica el desplazamiento respecto a las variables globales anteriores. Las excepciones son FOTOS
y TECLAS, definidas como punteros a 16 y a 10 posiciones libres de tipo Uint16, respectivamente,
que sirven en el primer caso para marcar posiciones del almacén de imágenes album[], y en el
segundo para indicar el comienzo de la lista de teclas definidas. Las constantes NUMVS2 y NUMVS4
indican el número de posiciones de memoria ocupadas por variables del sistema de dos y cuatro
bytes, es decir (contando FOTOS por 16 y TECLAS por 10) el número de variables del sistema. Por
ahora NUMVS2 es 33 y NUMVS4 es 21.
Las variables del sistema de 16 bits son:
MiMotor1.1
9
Definición
V2_NSERAC
V2_ESTTEC
TECLAS
V2_FLAGSS
V2_NVIDAS
V2_NITEMS
V2_MUNICI
V2_NNIVEL
FOTOS
Posiciones
0
1
2–11
12
13
14
15
16
17–33
Descripción
NÚMERO DE SERES ACTIVOS
ESTADO DEL TECLADO
TECLAS DEFINIDAS
FLAGS DEL SISTEMA
NÚMERO DE VIDAS
NÚMERO DE ITEMS
RESERVA DE MUNICIÓN
NÚMERO DE NIVEL
MARCADOR DE IMÁGENES EN album[]
Los nombres son indicativos y pueden usarse con otros propósitos, excepto V2_NSERAC y V2_FLAGSS
que se usan internamente.
Las variables del sistema de 32 bits son:
Definición
V4_TLOGIC
V4_NFRLOG
V4_PUNTUA
V4_MARCAD
V4_CFONDO
V4_ALMACXn
V4_ALMACYn
Posiciones
0
1
2
3
4
5–12
13–20
Descripción
TIEMPO LÓGICO
NÚMERO DE FRAMES LÓGICAS
PUNTUACIÓN
NÚMERO EN EL MARCADOR
COLOR DE FONDO
ALMACEN DE 8 VALORES ’X’
ALMACEN DE 8 VALORES ’Y’
Internamente se usan V4_TLOGIC, V4_NFRLOG y V4_CFONDO. La notación de las últimas variables
indica que éstas son V4_ALMACX0,. . . V4_ALMACX7 y V4_ALMACY0,. . . V4_ALMACY7.
La variable V2_FLAGSS almacena los flags del sistema utilizados en las interrupciones. Por
ahora sólo hay cuatro. Su valor indica la máscara del bit que se activa. Con la estructura
actual del bucle principal sólo los 8 bits de menor peso de V2_FLAGSS activan la gestión de
interrupciones.
FPAU=1. Modo pausa. Lo gestiona la interrupción correspondiente y devuelve SDL_GetTicks()
para engañar al bucle principal señalado que no ha transcurrido tiempo. Esta técnica se
sigue en todos los flags excepto para FESC.
FACT=2. Indica que se debe hacer alguna actualización especial (por ejemplo el marcador).
FRIP=64. Muerte del personaje principal. Tı́picamente la interrupción ejecuta una “danza
final”, disminuye V2_NVIDAS y en caso de que sea cero activa FESC.
FESC=128. Escape. Provoca una interrumpción inmediata saliendo del bucle principal.
La interrupción debiera tratarlo devolviendo inmediatamente el valor cero. Con ello se
saldrá del bucle principal.
4.
Variables globales
Aparte de varsis2 y de varsis4 hay otras seis variables globales:
Ente *adan; /* Primer ser */
SDL_Surface *album[NMAXIMAG+1]; /* Álbum de imágenes */
SDL_Surface *fondo; /* Fondo de pantalla */
Mix_Chunk *gramola[NMAXSON+1]; /* Álbum de sonidos */
void *mapa; /* Origen de datos utilizables por el motor */
SDL_Surface *screen; /* La pantalla */
MiMotor1.1
10
La más importante es la pantalla, screen. Las matrices album[] y gramola[] almacenan las imágenes y sonidos que se van a utilizar en escena. A veces es conveniente saber la dirección del primer
ser que se ha creado, por ejemplo para averiguar el orden del ser en curso, éste es el propósito del puntero adan. La superficie fondo es diferente de NULL sólo cuando se quiere establecer
una imagen de fondo. Por último el puntero mapa está pensado con el propósito de apuntar a
cualquier tipo de colección de datos que se quierea emplear en escena.
5.
Imágenes
La función básica para cargar una imagen es cargaspri. Como hemos visto, cargaspri(
hace que la primera posición de album[] apunte a la imagen cargada desde el fichero "nombre". La función cargaspri llama a cargimag cuya prototipo es SDL_Surface
*cargimag(char *fichero) y que devuelve un puntero a la imagen cargado desde fichero.
A veces un mismo fichero contiene n fotogramas de un sprite. En ese caso utilizaremos
cargaspri("nombre",n,xy) para que n posiciones de album[] apunten a las n imágenes. Respecto
al último argumento, xy[] es una matriz de 2n enteros que indican las coordenadas de la esquina
inferior derecha de cada uno de los fotogramas.
Por ejemplo, en el programa al comienzo de esta documentación, si empleamos el fichero
bolita4.png donde se alternan dos bolitas de tamaño 16 con otras un 25 % mayor y un 25 %
menor, las cargarı́amos en creaunabolita mediante
"nombre",0,NULL)
int xy[2*4]={16,16,16+20,20,36+26,16,52+12,12};
cargaspri(DIRECI "bolita4.png",4,xy);
Si al comienzo de c_rebota incluimos
ser->cara=album[((int)ser->x/27)%4];
entonces la bolita va latiendo según avanza hinchándose y deshinchándose.
Cuando se tienen varios grupos de imágenes cargadas en album[] no es fácil recordar la
posición de cada una de ellas. La matriz entera FOTOS[] en conjunción con la función nimenal()
que da el número de imágenes ya cargadas en album[] es útil para establecer marcadores. Por
ejemplo
FOTOS[i]=nimenal();
cargaspri("nombre",n,xy);
permite referirnos con album[FOTOS[i]] al puntero del primer fotograma de "nombre" sin necesidad
de recordar cuántas posiciones de album[] se han ocupado con imágenes anteriores.
6.
Sonido
La carga de sonidos contenidos en los ficheros fichero0.wav, fichero1.wav,. . . se lleva a cabo
con
char sonidos[]= "fichero0.wav#fichero0.wav#fichero0.wav#...ficheroN.wav";
cargason(sonidos);
Los punteros a los sonidos quedan almacenados en gramola[0], gramola[1], etc. Para hacerlos
sonar se utiliza la instrucción Mix_PlayChannel. El significado de sus tres argumentos está en
la documentación de la biblioteca SDL_mixer. Por regla general, para ejecutar el sonido al que
apunta gramola[i] se empleará Mix_PlayChannel(-1,gramola[i],0).
Por ejemplo, si en el programa inicial dentro de creaunabolita cargamos blip.wav con
char sonidos[]= "blip.wav";
cargason(sonidos);
y en c_rebota añadimos
MiMotor1.1
11
if( (ser->x-ANCHO/2)*(ser->xa-ANCHO/2)<0 ) Mix_PlayChannel (-1,gramola[0],0);
entonces sonará blip.wav cada vez que la bolita pase por el punto medio.
Cuando se sale de escena se llama a la función liberaimagyson() que libera todo el espacio
ocupado por las imágenes y sonidos a los que apuntan los elementos de album[] y gramola[]. Lo
mismo se hace con la imagen a la que apunta fondo, si es que hubiera un fondo definido.
7.
Teclado y ratón
El segundo argumento de escena es una función de tipo void sin argumentos que tı́picamente
se utiliza para cargar en la variable V2_ESTTEC las acciones sobre el teclado y el ratón. En el
fichero de cabecera están predefinidas las máscaras TARR, TABA, TIZQ, TDCH y TSAL como 1, 2,
4, 8 y 16 respectivamente que son coherentes con la función cincotec incluida en el motor,
la cual lee las teclas cuyo código SDL (en general coincide con el ASCII) está almacenado
en TECLAS[2], TECLAS[3], TECLAS[1], TECLAS[0], TECLAS[4] y activa los bits correspondientes de
V2_ESTTEC correspondientes a las máscaras anteriores. También activa el flag FPAU si la tecla de
Pausa (o F12) está pulsada, y sale del motor si se cierra la ventana o se pulsa F7.
Por ejemplo, si antes de llamar a escena se definen las teclas como
TECLAS[2]=SDLK_UP;
TECLAS[3]=SDLK_DOWN;
TECLAS[1]=SDLK_LEFT;
TECLAS[0]=SDLK_RIGHT;
TECLAS[4]=SDLK_SPACE;
entonces utilizando cincotec como argumento de escena, con el siguiente cerebro se moverá con
los cursores un ser por la pantalla de 8 en 8 pixels y la barra espaciadora permite abandonar.
void c_rebota(Ente *ser)
{
ser->xa=ser->x;
ser->ya=ser->y; /* Actualiza coordenada */
if(V2_ESTTEC&TARR)
if(V2_ESTTEC&TABA)
if(V2_ESTTEC&TIZQ)
if(V2_ESTTEC&TDCH)
ser->y-=
ser->y+=
ser->x-=
ser->x+=
8.0;
8.0;
8.0;
8.0;
if(ser->y<0){ ser->y=0; }
if(ser->y>ALTO-ser->cara->h){ ser->y=ALTO-ser->cara->h; }
if(ser->x<0){ ser->x=0; }
if(ser->x>ANCHO-ser->cara->w){ ser->x=ANCHO-ser->cara->w; }
if(V2_ESTTEC&TSAL)
V2_FLAGSS|=FESC; /* Termina */
}
Ya hemos visto en la información rápida cómo leer el teclado con la biblioteca SDL. El
siguiente ejemplo asigna V2_ESTTEC=1 cuando está pulsado el botón del ratón y en ese caso
asigna a las variables V4_ALMACX0 y V4_ALMACY0 las coordenadas del cursor.
void leeraton(void){
SDL_Event event;
V2_ESTTEC= 0;
if(!SDL_PollEvent(&event)||(event.type!=SDL_MOUSEBUTTONDOWN)) return;
V2_ESTTEC= 1;
V4_ALMACX0= event.button.x; V4_ALMACY0= event.button.y;
}
8.
Interrupciones
El tercer argumento de escena es una función que se ajusta al prototipo
MiMotor1.1
12
Uint32 nombre_interrup(Ente *ser, Uint32 ahora);
La variable ahora contiene el tiempo en el que se accede a la función. Si la gestión de interrupciones conlleva una duración inapreciable su valor será también la cantidad de salida pero en otro
caso, y especialmente en la gestión de FPAU, como ya hemos indicado se devuelve SDL_GetTicks()
para que el bucle principal actúe como si no hubiera pasado el tiempo.
El siguiente ejemplo gestiona la pausa esperando la pulsación de la tecla Intro o Escape;
en el primer caso continúa como ha entrado y en el segundo caso interrumpe con FESC
/* Auxiliar */
char escapa(void){ /* Vuelve 1 si se ha pulsado enter o esc, cero en otro caso */
Uint8 *keystate = SDL_GetKeyState(NULL);
SDL_Event event;
/* Si no hay tecla accionada, vuelve con cero*/
if(SDL_PollEvent(&event)){
if( event.type==SDL_QUIT ) /* Fin si cierra ventana */
exit(EXIT_FAILURE);
if((event.type!=SDL_KEYDOWN)&&(event.type!=SDL_KEYUP))
if(keystate[SDLK_ESCAPE]){ /* Sale */
V2_FLAGSS|=FESC;
return 1; /* Escape */
}
if(keystate[SDLK_RETURN]) return 1; /* Pausa */
}
return 0;
return 0;
}
Uint32 pausayescape(Ente *ser, Uint32 ahora){
if(V2_FLAGSS&FPAU){
while(!escapa());
V2_FLAGSS^=FPAU; /* Lo quita */
return SDL_GetTicks();
}
if(V2_FLAGSS&FESC) return 0;
return ahora;
}
9.
Colisiones
El último argumento de escena, cuando no es NULL, es una función de cada ser que trata las
colisiones. Tı́picamente una colisión conlleva una activación de la máscara MUER en ser->tipo o
un cambio en la velocidad.
El siguiente código es una función colisión que incorporada al programa de las 40 bolas que
rebotan provoca que al chocar salgan a velocidad 5.0 en sentidos opuestos en la dirección del
vector que los une.
void chocarebota(Ente *ser){
int i;
int nser=ser-adan; /* Número de ser */
float vx,vy; /* Radiovector */
float t;
for(i=1; i<V2_NSERAC-nser; ++i){
/* Estudia si ser y ser+i se han chocado */
vx=(ser+i)->x - ser->x;
vy=(ser+i)->y - ser->y;
t=vx*vx+vy*vy; /* Norma al cuadrado */
if( t<16*16 ){
MiMotor1.1
13
if( (t=sqrt(t)) == 0 ) t=vx=1.0; /* Por si coinciden */
t/=5.0;
/* Direcciones opuestas al radiovector */
ser->vx= -vx/t; ser->vy= -vy/t;
(ser+i)->vx= vx/t; (ser+i)->vy= vy/t;
return; /* Impide colisión múltiple */
}
}
}
10.
Funcionamiento interno
El motor utiliza “ritmo lógico constante” (fixed rate logic), es decir, intenta que la lógica
se ejecute un número prefijado de veces por segundo, que está definido por el parámetro FPSM.
La ventaja frente a la técnica de ∆-tiempos es que se tiene control total sobre las velocidades
y posiciones intermedias de un sprite. La pantalla se compone usando “rectángulos sucios”
(dirty rectangles): sólo se actualizan los rectángulos correspondientes a la posición de los seres.
Para calcular las posiciones se emplea interpolación lineal. Todas estas ideas están en el juego
pig-1.0 de D. Olofson http://olofson.net/mixed.html.
El bucle principal, casi todo el contenido de escena, es el siguiente:
Ente ser[NMAXENTES];
SDL_Rect rastro[NMAXENTES];
Uint32 ahora,antes; /* Variables auxiliares de tiempo */
Uint32 i; /* Variable auxiliar */
adan=ser; /* apunta al primer ser */
fondo=NULL; /* Para posibles rutinas que comprueben si hay fondo */
/* Aquı́ el fondo debe estar liberado */
V2_FLAGSS&=0xFF00; /* Quita los flags locales */
if(interrup==NULL) interrup=fescape; /* Interrupción por defecto */
creaseres();
/* BUCLE PRINCIPAL */
SDL_Delay(10); /* Para que no sea el tiempo inicial nulo */
antes=SDL_GetTicks(); /* Inicio de tiempo */
logica(ser,colision);
V4_TLOGIC=0;
while(antes){
ahora=SDL_GetTicks();
i=-(V4_TLOGIC>>10);
V4_TLOGIC+=(ahora-antes)*FPSM;
i+=(V4_TLOGIC>>10);
while(i--)
logica(ser,colision);
borpinact(ser,rastro); /* Borra, pinta, actualiza */
/* Lee teclado */
if( leetec!=NULL ) leetec();
#if DESCANSO!=0
SDL_Delay(DESCANSO);
#endif
if( V2_FLAGSS&255 ) ahora=interrup(ser,ahora);
antes=ahora; /* Actualiza tiempo */
}
Y la rutina borpinact que borra pinta y actualiza es:
void borpinact(Ente *ser,SDL_Rect *rastro)
{
MiMotor1.1
/* Parámetro de interpolación */
float h=(float)(V4_TLOGIC&0x3ff)/1024.0;
Uint16 j=0;
Uint16 i=V2_NSERAC;
/* BORRA */
if(fondo!=NULL)
while(i--){
*rastro=(ser++)->pisada; /* Lo imprimido antes */
SDL_BlitSurface(fondo,rastro,screen,rastro);
++rastro;
}
else
while(i--){
*rastro=(ser++)->pisada; /* Lo imprimido antes */
SDL_FillRect(screen,rastro++,V4_CFONDO);
}
/* PINTA */
i=V2_NSERAC;
while(i--){
--rastro; --ser; /* Siguientes rastro y ser */
if((ser->tipo&MUER)&&!j){ /* Salta el primer muerto */
/* Parche para que el primero pueda
morir y se acabe todo */
if(!i){ /* Si es el primero */
SDL_UpdateRects(screen, V2_NSERAC,rastro); /* Actualiza */
V2_FLAGSS|=FESC; /* Y sale con flag de escape */
return;
}
j=i--; --rastro; --ser;
}
/* Interpola coordenadas */
ser->xi=(1.0-h)*ser->xa+h*ser->x;
ser->yi=(1.0-h)*ser->ya+h*ser->y;
/* Lo que se imprime ahora */
ser->pisada.x=ser->xi; ser->pisada.y=ser->yi;
SDL_BlitSurface(ser->cara, NULL,screen,&(ser->pisada));
/* Calcula rectángulo unión de ser->rastro */
if(rastro->x > ser->pisada.x){
if(rastro->x+rastro->w <
ser->pisada.x+ser->pisada.w)
rastro->w=ser->pisada.w;
else rastro->w+=rastro->x-ser->pisada.x;
rastro->x=ser->pisada.x;
} else if(rastro->x+rastro->w<ser->pisada.x+ser->pisada.w)
rastro->w=ser->pisada.x-rastro->x+ser->pisada.w;
if(rastro->y > ser->pisada.y){
if(rastro->y+rastro->h <
ser->pisada.y+ser->pisada.h)
rastro->h=ser->pisada.h;
else rastro->h+=rastro->y-ser->pisada.y;
rastro->y=ser->pisada.y;
} else if(rastro->y+rastro->h<ser->pisada.y+ser->pisada.h)
rastro->h=ser->pisada.y-rastro->y+ser->pisada.h;
}
/* ACTUALIZA */
SDL_UpdateRects(screen, V2_NSERAC,rastro);
if(j){ /* Si habı́a algún muerto suprimirlo */
ser[j]=ser[--V2_NSERAC]; /* Sustituye último */
rastro[j]=rastro[V2_NSERAC];
j=0; /* Ya no hay muerto */
}
14
MiMotor1.1
15
}
El motor tiene una pequeña rutina merror que se usa internamente para gestionar algunos
errores. También está disponible para el usuario. Su código es:
void merror(int nerr,char* fich,int lin, char* mens, char* arg){
printf("Error en el fichero %s, lı́nea %d.\n",fich,lin);
if(arg==NULL)
printf("%s.\n",mens);
else
printf("%s%s\n",mens,arg);
if(nerr<4){
printf("Éste es un error fatal.\n");
exit(EXIT_FAILURE);
}
}
y las definiciones incluidas en la cabecera son:
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
E_VIDEO 0,__FILE__,__LINE__,SDL_GetError(),NULL
E_SCREE 1,__FILE__,__LINE__,SDL_GetError(),NULL
E_BPERP 2,__FILE__,__LINE__,"Número de bytes por pixel no válido",NULL
E_CAIMA 3,__FILE__,__LINE__,"Error al cargar la imagen "
E_CASON 4,__FILE__,__LINE__,"Error al cargar el sonido "
E_AUDIO 5,__FILE__,__LINE__,Mix_GetError(),". Problemas con el sonido."
E_MUIMA 6,__FILE__,__LINE__,"Demasiadas imágenes en album[]",NULL
E_MUSON 7,__FILE__,__LINE__,"Demasiados sonidos en gramola[]",NULL
E_MUSER 8,__FILE__,__LINE__,"Demasiados seres",NULL
E_GENER 9,__FILE__,__LINE__
E_DATNUM ,__FILE__,__LINE__,NULL,
Por ejemplo merror(E_MUSER) aparece en nace cuando se intenta crear el ser número NMAXENTES
y merror(E_CASON,nombre) aparece en cargason cuando hay problemas para cargar el fichero de
sonido nombre.
11.
Versiones y posibles extensiones
es el motor del juego “Traspapelon” que se puede descargar en http://www.uam.
salvo un pequeño cambio (la función fescape que establece
la interrupción por defecto). Hasta que se acabe la documentación no se prevé hacer cambios
en MiMotor1.1.c.
MiMotor1.0.c
es/fernando.chamizo/infor/infor.html
Posibilidades TODO:
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
Todas las variables Uint32
Más flags para actualización FACT1, FACT2, etc.
mapa, mapai (integer), mapaf (float).
Modos de motor MODO0, normal; 1, sin actualización (imprime el fondo y todos los sprites y después
actualiza toda la pantalla); 2, 3D (con estructuras especiales de Ente).
Define en cabecera para suprimir sonido #define SOUND.
Módulos en ficheros separados: argumentos de escena tı́picos, GUI, texto, etc.
Interpolación tres nodos (cuadrática).
Variable global con el estado, incluyendo fullscreen, sonido desactivado, modo, cursor desactivado... Al
entrar a escena se podrı́a consultar estas variables y proceder en consecuencia (Toggle o ShowCursor).
Sepodrı́an hacer llamadas vacı́as escena con este propósito.
Añadir imprim.
Añadir fondoactual.
Nuevas variables V4_RATONX, V4_RATONY, para las coordenadas de la posición del ratón.
Creación automática de capas trasparentes.
MiMotor1.1
12.
16
Más ejemplos
Nombre: gravedad.c
Descripción: Caı́da libre de una bolita con rebote en el suelo y pérdida de velocidad por el
rozamiento. Se termina cuando se alcanza el borde derecho o la velocidad horizontal es menos
que una décima. Ajustando los valores ACEL y ROZA ası́ como la velocidad inicial, en este ejmplo
(5.0,0.0), se consiguen diferentes comportamientos.
/* /ficheros/gravedad.c */
#include "MiMotor1.1.h"
#define DIRECI "./images/"
#define DIRECS "./sounds/"
/* Directorio de imágenes */
/* Directorio de sonidos */
void c_rebota(Ente *ser)
{
#define ACEL 1.0
#define ROZA 0.75
ser->xa=ser->x; ser->ya=ser->y; /* Actualiza coordenadas */
ser->vy+=ACEL; /* Suma aceleración */
ser->x+=ser->vx; ser->y+=ser->vy; /* A~
nade la velocidad */
if(ser->y>ALTO-ser->cara->h ){ /* Choque contra el suelo */
ser->y=ALTO-ser->cara->h;
ser->vy*=-ROZA; ser->vx*=ROZA; /* Rozamiento en rebote */
}
if( (ser->x>ANCHO-ser->cara->w) || (ser->vx<0.1) ) V2_FLAGSS|=FESC;
}
void creaunabolita(void){
cargaspri(DIRECI "bolita.png",0,NULL);
/* Carga imagen */
nace(0,0,0.0,0.0,5.0,0.0,c_rebota,album[0]);
}
int main(int argc, char *argv[]){
motoron();
escena(creaunabolita,NULL,NULL,NULL);
motoroff();
return EXIT_SUCCESS;
}
Nombre: mueve.c
Descripción: Permite mover una bolita con las teclas de los cursores. Pulsando la barra
espaciadora se termina.
/* /ficheros/mueve.c */
#include "MiMotor1.1.h"
#define DIRECI "./images/"
#define DIRECS "./sounds/"
/* Directorio de imágenes */
/* Directorio de sonidos */
void c_rebro(Ente *ser)
{
ser->xa=ser->x;
ser->ya=ser->y; /* Actualiza coordenada */
if(V2_ESTTEC&TARR) ser->y-= 8.0;
MiMotor1.1
17
if(V2_ESTTEC&TABA) ser->y+= 8.0;
if(V2_ESTTEC&TIZQ) ser->x-= 8.0;
if(V2_ESTTEC&TDCH) ser->x+= 8.0;
if(ser->y<0){ ser->y=0; }
if(ser->y>ALTO-ser->cara->h){ ser->y=ALTO-ser->cara->h; }
if(ser->x<0){ ser->x=0; }
if(ser->x>ANCHO-ser->cara->w){ ser->x=ANCHO-ser->cara->w; }
if(V2_ESTTEC&TSAL)
V2_FLAGSS|=FESC; /* Termina */
}
void creaunabolita(void){
cargaspri(DIRECI "bolita.png",0,NULL);
/* Carga imagen */
nace(0,200,320.0,400.0,9.0,0.0,c_rebro,album[0]);
}
int main(int argc, char *argv[]){
motoron();
TECLAS[2]=SDLK_UP;
TECLAS[3]=SDLK_DOWN;
TECLAS[1]=SDLK_LEFT;
TECLAS[0]=SDLK_RIGHT;
TECLAS[4]=SDLK_SPACE;
escena(creaunabolita,cincotec,NULL,NULL);
motoroff();
return EXIT_SUCCESS;
}
Nombre: colmul.c
Descripción: Bolitas que chocan rebotando. La animación se para pulsando la tecla de
pausa. Para continuar se usa a continuación Intro y para salir Escape.
/* /ficheros/colmul.c */
#include "MiMotor1.1.h"
#define DIRECI "./images/"
#define DIRECS "./sounds/"
/* Directorio de imágenes */
/* Directorio de sonidos */
#define ROJO SDL_MapRGB(screen->format,223,47,47)
/*************/
/* cerebro */
/************/
void c_rebota(Ente *ser)
{
ser->xa=ser->x; ser->ya=ser->y; /* Actualiza coordenada */
/* Velocidad con signo */
ser->x+=(1-2*V2_ESTTEC)*ser->vx; ser->y+=(1-2*V2_ESTTEC)*ser->vy;
/* Rebotes */
if(ser->x>ANCHO-ser->cara->w){ ser->x=ANCHO-ser->cara->w; ser->vx*=-1;}
if(ser->x<0){ ser->x=0; ser->vx*=-1;}
if(ser->y>ALTO-ser->cara->w){ ser->y=ALTO-ser->cara->h; ser->vy*=-1;}
if(ser->y<0){ ser->y=0; ser->vy*=-1;}
}
/***************/
/* CREASERES */
/***************/
void creaunabolita(void){
int i=40;
float th;
MiMotor1.1
18
cargaspri(DIRECI "bolita.png",0,NULL); /* Carga imagen */
while(i--){
th=M_PI*(rand() % 20)/10.0;
nace(1, 0,
rand() % (ANCHO-album[0]->w), rand() % (ALTO-album[0]->h),
5.0*cos(th), 5.0*sin(th),c_rebota,album[0]);
}
}
/**************/
/* INTERRUP */
/**************/
/* Auxiliar */
char escapa(void){ /* Vuelve 1 si se ha pulsado enter o esc, cero en otro caso */
Uint8 *keystate = SDL_GetKeyState(NULL);
SDL_Event event;
/* Si no hay tecla accionada, vuelve con cero*/
if(SDL_PollEvent(&event)){
if((event.type!=SDL_KEYDOWN)&&(event.type!=SDL_KEYUP))
if(keystate[SDLK_ESCAPE]){ /* Sale */
V2_FLAGSS|=FESC;
return 1; /* Escape */
}
if(keystate[SDLK_RETURN]) return 1; /* Pausa */
}
return 0;
return 0;
}
Uint32 pausayescape(Ente *ser, Uint32 ahora){
if(V2_FLAGSS&FPAU){
while(!escapa());
V2_FLAGSS^=FPAU; /* Lo quita */
return SDL_GetTicks();
}
if(V2_FLAGSS&FESC) return 0;
return ahora;
}
/**************/
/* COLISION */
/**************/
void chocarebota(Ente *ser){
int i;
int nser=ser-adan; /* Número de ser */
float vx,vy; /* Radiovector */
float t;
for(i=1; i<V2_NSERAC-nser; ++i){
/* Estudia si ser y ser+i se han chocado */
vx=(ser+i)->x - ser->x;
vy=(ser+i)->y - ser->y;
t=vx*vx+vy*vy; /* Norma al cuadrado */
if( t<16*16 ){
if( (t=sqrt(t)) == 0 ) t=vx=1.0; /* Por si coinciden */
t/=5.0;
/* Direcciones opuestas al radiovector */
ser->vx= -vx/t; ser->vy= -vy/t;
(ser+i)->vx= vx/t; (ser+i)->vy= vy/t;
return; /* Impide colisión múltiple */
}
}
}
int main(int argc, char *argv[]){
motoron();
escena(creaunabolita,cincotec,pausayescape,chocarebota);
motoroff();
MiMotor1.1
19
return EXIT_SUCCESS;
}
Nombre: araton.c
Descripción: Al pinchar sobre la pantalla con el ratón, una bolita se acerca al cursor. Hay
un efecto de deceleración según se acerca al destino. Se sale cerrando la pantalla.
/* /ficheros/araton.c */
#include "MiMotor1.1.h"
#define DIRECI "./images/"
#define DIRECS "./sounds/"
/* Directorio de imágenes */
/* Directorio de sonidos */
void c_araton(Ente *ser)
{
ser->xa=ser->x;
ser->ya=ser->y; /* Actualiza coordenada */
if( V2_ESTTEC )
{
ser->x+= (V4_ALMACX0-ser->x)/7.0;
ser->y+= (V4_ALMACY0-ser->y)/7.0;
}
if(ser->y<0){ ser->y=0; }
if(ser->y>ALTO-ser->cara->h){ ser->y=ALTO-ser->cara->h; }
if(ser->x<0){ ser->x=0; }
if(ser->x>ANCHO-ser->cara->w){ ser->x=ANCHO-ser->cara->w; }
}
void creaunabolita(void){
cargaspri(DIRECI "bolita.png",0,NULL);
/* Carga imagen */
nace(0,0,ANCHO/2,ALTO/2,0.0,0.0,c_araton,album[0]);
}
void leeraton(void){
SDL_Event event;
if( !SDL_PollEvent(&event) ) return;
if (event.type==SDL_QUIT) V2_FLAGSS|=FESC; /* Activa flag de escape */
if( event.type!=SDL_MOUSEBUTTONDOWN ){ V2_ESTTEC= 0; return;}
V2_ESTTEC= 1;
V4_ALMACX0= event.button.x; V4_ALMACY0= event.button.y;
}
int main(int argc, char *argv[]){
motoron();
escena(creaunabolita,leeraton,NULL,NULL);
motoroff();
return EXIT_SUCCESS;
}
Nombre: adeborra.c
Descripción: Ejemplo jugable. Hay que mover la burbuja grande con las teclas Q, A, O,
P para recoger las gotatias que hay por la pantalla evitando los cristales de sal. La burbuja se
moverá por inercia, para detenerla hay que pulsar la barra espaciadora.
/* /ficheros/adeborra.c */
#include "MiMotor1.1.h"
MiMotor1.1
20
#define DIRECI "./images/"
#define DIRECS "./sounds/"
#define
#define
#define
#define
/* Directorio de imágenes */
/* Directorio de sonidos */
AN 520
AL 320
NSAL 10
NGOT 20
/**************/
/* cerebros */
/*************/
void c_prota(Ente *ser){
ser->xa=ser->x; ser->ya=ser->y; /* Actualiza coordenadas */
/* Nueva posición */
if( V2_ESTTEC&TDCH ) ser->x+=ser->vx;
if( V2_ESTTEC&TIZQ ) ser->x-=ser->vx;
if( V2_ESTTEC&TARR ) ser->y-=ser->vy;
if( V2_ESTTEC&TABA ) ser->y+=ser->vy;
/* Impide que se salga del recuadro */
if( ser->x<(ANCHO-AN)/2 ) ser->x=(ANCHO-AN)/2;
if( ser->x>(ANCHO+AN)/2-ser->cara->w ) ser->x=(ANCHO+AN)/2-ser->cara->w;
if( ser->y<(ALTO-AL)/2 ) ser->y=(ALTO-AL)/2;
if( ser->y>(ALTO+AL)/2-ser->cara->w ) ser->y=(ALTO+AL)/2-ser->cara->w;
/* Si está muerto,
if( V2_FLAGSS&FRIP
/* Si está sólo se
if( V2_NSERAC==1 )
se rompe */
) ser->cara= album[3];
acaba */
V2_FLAGSS|=FESC;
}
void c_rebota(Ente *ser){
ser->xa=ser->x; ser->ya=ser->y; /* Actualiza coordenadas */
ser->x+=ser->vx;
ser->y+=ser->vy;
/* A~
nade la velocidad */
/* Rebotes */
if(ser->x>(ANCHO+AN)/2-ser->cara->w){ ser->x=(ANCHO+AN)/2-ser->cara->w; ser->vx*=-1;}
if(ser->x<(ANCHO-AN)/2){ ser->x=(ANCHO-AN)/2; ser->vx*=-1;}
if(ser->y>(ALTO+AL)/2-ser->cara->w){ ser->y=(ALTO+AL)/2-ser->cara->h; ser->vy*=-1;}
if(ser->y<(ALTO-AL)/2){ ser->y=(ALTO-AL)/2; ser->vy*=-1;}
/* Si ya no hay gotas mueren todas las sales */
if( V2_NSERAC <= NSAL+1 ) ser->tipo|=MUER;
}
/***************/
/* CREASERES */
/***************/
void creagotas(void){
int i=NSAL; /*Número de gotitas */
float th;
cargaspri(DIRECI "bola.png",0,NULL);
/* imagen protra */
cargaspri(DIRECI "sal.png",0,NULL);
/* imagen sales*/
cargaspri(DIRECI "gota.png",0,NULL);
/* imagen gotitas */
cargaspri(DIRECI "bolarota.png",0,NULL);
/* imagen protra roto */
/* nace(tipo,cont,(x,y),(vx,vy),cerebro,cara) */
/* protra */
nace(0,200,(ANCHO-AN)/2,(ALTO-AL)/2,6.2,6.2,c_prota,album[0]);
/* sales y gotitas*/
while(i--){
th=M_PI*(rand() % 100)/50.0;
nace(1,0,ANCHO/2,ALTO/2,5.1*cos(th),5.1*sin(th),c_rebota,album[1]);
}
i=NGOT;
while(i--){
th=M_PI*(rand() % 100)/50.0;
nace(2,0,(ANCHO-(AN-31))/2+(rand() % (AN-31)),
(ALTO-(AL-31))/2+(rand() % (AL-31)),
MiMotor1.1
21
0.03*cos(th),0.03*sin(th),c_rebota,album[2]);
}
}
/************/
/* LEETEC */
/************/
void lee5tec(void){
Uint8 *keystate = SDL_GetKeyState(NULL);
SDL_Event event;
/* Si hay eventos */
if(SDL_PollEvent(&event)){
/* Termina si se cierra la ventana */
if (event.type==SDL_QUIT) V2_FLAGSS|=FESC; /* Activa flag de escape */
/* Vuelve si no hay teclas pulsadas (sigue como estaba) */
if((event.type!=SDL_KEYDOWN)&&(event.type!=SDL_KEYUP))
return;
if(keystate[TECLAS[0]]){
if(keystate[TECLAS[1]]){
if(keystate[TECLAS[2]]){
if(keystate[TECLAS[3]]){
if(keystate[TECLAS[4]]){
V2_ESTTEC&= ~TIZQ;
V2_ESTTEC|=
V2_ESTTEC&= ~TDCH;
V2_ESTTEC|=
V2_ESTTEC&= ~TABA;
V2_ESTTEC|=
V2_ESTTEC&= ~TARR;
V2_ESTTEC|=
V2_ESTTEC= 0;} /* Parado */
TDCH;}
TIZQ;}
TARR;}
TABA;}
}
}
/**************/
/* INTERRUP */
/**************/
Uint32 escymarcador(Ente *ser,Uint32 ahora){
int i=V4_PUNTUA;
/* Punto base de impresión del marcador */
SDL_Rect r={153,438,0,0};
if(V2_FLAGSS&FACT){
V2_FLAGSS^=FACT; /* Lo quita */
/* Posición gota */
while(i--){
r.x+= 19;
SDL_BlitSurface(album[2],NULL,screen,&r); /* Copia a pantalla */
SDL_UpdateRect(screen,r.x,r.y,r.w,r.h); /* Actualiza */
}
}
if( (V2_FLAGSS&FESC) || (V2_FLAGSS&FRIP) ){
SDL_Delay(2000); /* Espera 2s */
return 0; /* Sale de escena */
}
return ahora;
}
/**************/
/* COLISION */
/**************/
void coli(Ente *ser){
int i,j,dc;
if( ser->tipo ) return; /* Si no es el prot., vuelve */
for(i=1;i<V2_NSERAC;++i){
/* Distancia al cuadrado centro prot - centro otro ser */
dc = ser->x + ser->cara->w/2 - (ser+i)->x - (ser+i)->cara->w/2;
j = ser->y + ser->cara->h/2 - (ser+i)->y - (ser+i)->cara->h/2;
dc*= dc;
dc+= j*j;
if( (ser+i)->tipo == 1 && (dc<324) ){ /* Enemigo */
V2_FLAGSS|= FRIP; /* Activa interrupción */
MiMotor1.1
22
}
if( ((ser+i)->tipo == 2) && (dc<121) ){ /* Gotita */
/* 121 aprox (radio prot - radio gotita)^2 */
(ser+i)->tipo|= MUER;
++V4_PUNTUA; /* Incrementa puntos */
V2_FLAGSS|= FACT; /* Activa interrupción */
}
}
}
void imprim(char *fich,Uint32 x, Uint32 y){
SDL_Rect r={x,y,0,0};
SDL_Surface *imag=cargimag(fich);
SDL_BlitSurface(imag,NULL,screen,&r);
SDL_FreeSurface(imag);
}
int main(int argc, char *argv[]){
motoron();
/* Decoración */
imprim(DIRECI "decor.jpg",0,0);
SDL_Flip(screen);
/* Teclas */
TECLAS[0]=SDLK_p; /* Derecha */
TECLAS[1]=SDLK_o; /* Izquierda */
TECLAS[2]=SDLK_a; /* Arriba */
TECLAS[3]=SDLK_z; /* Abajo */
TECLAS[4]=SDLK_SPACE; /* Abajo */
/* Puntos a cero */
V4_PUNTUA=0;
/* Color de fondo */
V4_CFONDO=SDL_MapRGB(screen->format,4,0,77);
escena(creagotas,lee5tec,escymarcador,coli);
motoroff();
return EXIT_SUCCESS;
}
13.
Apéndice: Instalar y compilar con SDL
Compilación: En Linux se cuenta con el bien conocido compilador gcc y los proyectos en
C de un solo fichero se compilan en la lı́nea de comandos con gcc fuente.c -o ejecutable.out
-lm (lo último es necesario si se usa <math.h> ). Para compilar empleando las bibliotecas SDL
(incluyendo las de imágenes, sonido y texto) se puede usar gcc fuente.c -o ejecutable.out `
sdl-config --cflags --libs` -lSDL image -lSDL mixer -lSDL ttf -lm (es importante que las comillas sean las izquierdas), en particular para usar fuente.c con el motor gcc fuente.c MiMotor1.1.c
-o ejecutable.out ‘sdl-config --cflags --libs‘ -lSDL image -lSDL mixer -lm. Si se quieren añadir
las bibliotecas de OpenGL hay que incluir también -lGL -lGLU. En Windows hay varios IDE’s
y compiladores. Más abajo se indica cómo emplear las bibliotecas SDL con los IDE gratuitos
y libres Kdevelop (Linux) y DevCpp (Windows).
MiMotor1.1
23
Ejecutables windows y .dll para SDL: Los ejecutables windows que utilicen la biblioteca
SDL necesitan incluir las .dll correspondientes en el directorio en el que esté el ejecutable. Varios
ejecutables pueden compartir las mismas .dll (de hecho si las añadimos en la carpeta del sistema
todos lo harán). Las .dll asociadas a SDL y sus bibliotecas asociadas más comunes son SDL.dll,
SDL image.dll, jpeg.dll, libpng12.dll, zlib1.dll, SDL mixer.dll, SDL ttf.dll, libfreetype-6.dll. La
primera corresponde a la biblioteca básica, las cuatro siguientes a SDL image, SDL mixer.dll
sólo se emplea si hay sonido y las dos últimas corresponden a la biblioteca TTF.
Editores: kwrite y kate son dos buenos editores para escribir código bajo el entorno KDE.
El último permite dividir la ventana en dos o más marcos de forma que la copia y revisión de
código es más sencilla. Para proyectos largos una buena opción es KDevelop. Serı́a conveniente
al emplear SDL activar el completado de código en KDevelop pero la documentación no se ha
mostrado clara en este punto. Parece que con plug-ins adecuados es posible hacerlo también
en kwrite y kate. Es un poco molesto cuando se aprovecha código anterior que el indentado
por tabulación en KDevelop no coincida con el de kwrite y kate.
Emplear la biblioteca SDL con Kdevelop: Lo siguiente quizá no sea la forma más
ortodoxa pero funciona, al menos en la máquina en uso (bajo SuSE Linux 10.0). Seguir los
siguientes pasos:
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
Proyecto>nuevo.
C>programa hola mundo.
Nombre: nomfich, Localización: mipath.
Opciones (adelante).
Sistema de control de versiones ninguno (adelante).
Plantilla cabeceras (cambiar lo que se quiera, por ejemplo YEAR por DATE) (adelante).
Plantilla de .c, lo mismo (adelante).
Finalizar.
Cambiar el código por nuestro programa SDL.
Opciones del proyecto>C>parámetros del compilador CFLAGS: -O0 -g3 y lo que salga
de ‘sdl-config --cflags --libs‘ y las librerı́as que se quieran usar, en la máquina en
uso -I/usr/include/SDL -D REENTRANT -L/usr/lib64 -lSDL -lpthread -lSDL image -lSDL mixer
-lSDL ttf -lm.
Construir proyecto y ejecutar.
Emplear la biblioteca SDL con DevCpp: Los ficheros se toman del sitio SDL http:
//www.libsdl.org/ y puede haber versiones más modernas.
Biblioteca SDL
1. Descomprimir SDL-devel-1.2.13-mingw32
2. Copiar \SDL-devel-1.2.13-mingw32\SDL-1.2.13\bin\SDL.dll en el directorio del sistema C:
\WINDOWS\system
3.
4.
5.
6.
Copiar el contenido de \SDL-devel-1.2.13-mingw32\SDL-1.2.13\lib en C:\Dev-Cpp\lib
Copiar en C:\Dev-Cpp\include la carpeta \SDL-devel-1.2.13-mingw32\SDL-1.2.13\include
Se abre Devcpp y se crea console application>sdl1 (elegir C como lenguaje)
Herramientas → Opciones del Compilador
MiMotor1.1
24
7. |x| Añadir estos comandos a la lı́nea de comandos del linker -lmingw32 -lSDLmain -lSDL
-mwindows
8. Probar con sdl1.c incluido más abajo.
Bibliotecas SDL image y SDL mixer
1. Descomprimir SDL_image-devel-1.2.6-VC8 y SDL_mixer-devel-1.2.8-VC8
2. El contenido de SDL_image-devel-1.2.6-VC8\SDL_image-1.2.6\include se copia a a C:\Dev-Cpp\
include\SDL (el fichero SDL image.h) y lo mismo con el contenido de \SDL_mixer-devel-1.2.
8-VC8\SDL_mixer-1.2.8\include (el fichero SDL mixer.h)
3. El contenido de SDL_image-devel-1.2.6-VC8\SDL_image-1.2.6\lib se copia a C:\Dev-Cpp\lib y
lo mismo con el de SDL_mixer-devel-1.2.8-VC8\SDL_mixer-1.2.8\lib
4. Copiar a C:\WINDOWS\system las dll de \SDL_image-devel-1.2.6-VC8\SDL_image-1.2.6\lib y las
dll de SDL_mixer-devel-1.2.8\lib
5. Herramientas → Opciones del Compilador
6. |x| Añadir estos comandos a la lı́nea de comandos del linker -lmingw32 -lSDLmain -lSDL
-lSDL image -lSDL mixer -mwindows
7. Probar con sdl2.c incluido más abajo usando blip.wav y bolita.png
Fichero de prueba sdl1.c
/* /ficheros/SDL/sdl1.c */
#include <stdio.h>
#include <SDL/SDL.h>
void imprimir_rectangulo (SDL_Surface *screen, int x, int y);
int main(int argc, char *argv[])
{
SDL_Surface *screen;
SDL_Event event;
if (SDL_Init(SDL_INIT_VIDEO) == -1)
{
printf("Error: %s\n", SDL_GetError());
return 1;
}
screen = SDL_SetVideoMode(320, 240, 16, SDL_SWSURFACE);
if (screen == NULL)
{
printf("Error: %s\n", SDL_GetError());
return 1;
}
SDL_WM_SetCaption("Ejemplo básico", NULL);
imprimir_rectangulo(screen, 20, 30);
while (SDL_WaitEvent(&event))
{
if (event.type == SDL_QUIT)
break;
}
SDL_Quit();
return 0;
}
void imprimir_rectangulo (SDL_Surface *screen, int x, int y)
{
SDL_Rect rect = {x, y, 50, 50};
Uint32 a = SDL_MapRGB(screen->format, 255, 200, 100);
MiMotor1.1
SDL_FillRect(screen, &rect, a);
SDL_UpdateRect(screen, rect.x, rect.y, rect.w, rect.h);
}
Fichero de prueba sdl2.c
/* /ficheros/SDL/sdl2.c */
#include <stdio.h>
#include <stdlib.h>
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#include <SDL/SDL_mixer.h>
#define ANCHO 320 /* Ancho de pantalla */
#define ALTO 240 /* Alto de pantalla */
SDL_Surface *screen;
void suena(void){
Mix_Chunk *sonido;
sonido = Mix_LoadWAV("blip.wav");
if(sonido==NULL) printf("error al cargar sonido\n");
Mix_PlayChannel (-1, sonido, 0);
SDL_Delay(500);
Mix_FreeChunk(sonido);
Mix_CloseAudio();
}
/*.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.*/
/*| Nombre: cargaimag
|*/
/*. Descripción: Carga una imagen
.*/
/*| Entra: Nombre de un fichero
|*/
/*. Sale: Imagen (superficie SDL)
.*/
/*-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-*/
SDL_Surface *cargimag(char *fichero){
SDL_Surface *temp,*ima;
char *ficht;
ficht=fichero;
while(*ficht) ++ficht; /*Busca el final*/
if(*(--ficht)==’p’) /* Si la última letra es una "p" es bmp*/
temp=SDL_LoadBMP(fichero);
else
temp=IMG_Load(fichero);
if (temp == NULL){
printf("Error al cargar %s\n", fichero);
exit(EXIT_FAILURE);
}
if(*(--ficht)==’n’) /* Si la penúltima letra es una "n" es png */
ima=SDL_DisplayFormatAlpha(temp);
else
ima=SDL_DisplayFormat(temp);
SDL_FreeSurface(temp);
return ima;
}
void dibujabolas(void){
SDL_Surface *bolita;
SDL_Rect r;
int i;
srand( time( NULL ));
25
MiMotor1.1
//
for(i=0;i<1000;++i){
r.x=(rand()%(ANCHO-bolita->w));
r.y=(rand()%(ALTO-bolita->h));
bolita=cargimag("bolita.png");
SDL_BlitSurface(bolita,NULL,screen,&r);
SDL_Flip(screen);
SDL_Delay(10);
}
}
int main(int argc, char *argv[])
{
SDL_Event event;
if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) == -1)
{
printf("Error: %s\n", SDL_GetError());
return 1;
}
screen = SDL_SetVideoMode(ANCHO, ALTO, 32, SDL_SWSURFACE);
if (screen == NULL)
{
printf("Error: %s\n", SDL_GetError());
return 1;
}
//
Mix_OpenAudio (44100, AUDIO_S16, 2, 4096);
//
Mix_OpenAudio(11025, AUDIO_U8, 1, 1024);
//
Mix_OpenAudio(22050, AUDIO_S16, 1, 2048);
if(Mix_OpenAudio (44100, MIX_DEFAULT_FORMAT, 2, 1024))
{
printf("No se puede abrir el audio %s\n", Mix_GetError());
exit(1);
}
atexit(Mix_CloseAudio);
SDL_WM_SetCaption("Ejemplo básico 2", NULL);
dibujabolas();
suena();
while (SDL_WaitEvent(&event))
{
if (event.type == SDL_QUIT)
break;
}
//
//
}
SDL_Quit();
return 0;
system("PAUSE");
return 0;
26
Descargar