¡MIS SPRITES SE MUEVEN!
En el artículo anterior aprendimos cómo definir sprites y dibujarlos en pantalla
utilizando la librería z88dk sin recurrir a ninguna otra herramienta externa (a menos
que quisiéramos dibujar los sprites utilizando algún programa como los mostrados en el
número anterior). En esta entrega vamos a ir un poco más allá y vamos a permitir que el
usuario pueda mover algún sprite por la pantalla, y que este sprite pueda interaccionar
con otros elementos, colisionando con ellos.
¿Qué vamos a hacer?
Para aprender los conceptos que necesitamos vamos a programar lo que podría ser el código
inicial de un juego de carreras de coches, tipo Super Sprint, en los que desde una vista
cenital podemos ver el circuito y los distintos participantes. Crearemos un coche
utilizando diversos sprites, según hacia donde se esté moviendo el mismo, y crearemos
una pista por donde el coche podrá moverse. Evidentemente, esta pista marcará el límite
de movimiento de nuestro bólido, por lo que deberemos implementar también algún
mecanismo para colisiones, de tal manera que al vehículo le pase algo al contactar con
los límites del circuito. Sin más, empecemos.
Definiendo los sprites
Hasta ahora hemos visto como crear sprites de un tamaño límite de 8x8. Sin embargo, por
mucho que lo intentemos, va a ser un poco difícil crear un sprite que represente algo
aproximado a un coche con una rejilla tan pequeña. Si nos fijamos en la siguiente
imagen, observaremos que tanto para representar al coche en todas las posibles
orientaciones, así como los neumáticos que van a formar parte de los límites de la
pista, necesitamos utilizar sprites de 10x10. ¿Cómo creamos sprites mayores de 8x8
usando z88dk?
|
Figura 1: Los sprites que vamos a utilizar en
nuestro juego. Representan todas las posibles orientaciones del coche, excepto
el último de todos, que representa un neumático. Todos ellos tienen un tamaño de
10x10
|
Según vimos anteriormente, un sprite se definía en z88dk como un array de char del
tipo:
char sprite0[] = { 8, 8, 0x18 , 0x24 , 0x42 , 0x81 , 0xA5 , 0x81 , 0x7E , 0x00 };
|
donde los dos primeros valores indicaban la altura y la anchura, respectivamente, y los
siguientes valores representaban a cada una de las filas del sprite. Si cada columna
tenía asignado un valor hexadecimal (estos valores eran, para una anchura de 8, y de
derecha a izquierda, los siguientes: 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80), el
valor para cada fila era la suma de los valores hexadecimales para los que el pixel
correspondiente valía 1.
Para sprites de más de anchura 8, y hata una anchura de 16, en ese array de caracteres
cada fila se va a representar por un par de números hexadecimales. El primer valor de
esa pareja se va a corresponder con los 8 primeros píxeles empezando por la izquierda de
la columna, y el segundo con el resto.
En el caso de los sprites que vemos en la figura anterior, donde cada columna tiene una
anchura de diez píxeles, cada fila se representaría también por una pareja de valores
hexadecimales. Con la primera pareja codificamos los ocho píxeles de la izquierda, de la
misma forma en que lo hacemos con sprites de anchura hasta 8, y con la segunda pareja
codificamos los otros dos pixeles que quedan a la derecha (siendo el valor 0x80 el
correspondiente al primero y el valor 0x40 el correspondiente al segundo. Si hubiese más
de dos, seguiríamos asignando los valores 0x20, 0x10, 0x08, 0x04 y etc. a los
siguientes). En la siguiente imagen vemos un ejemplo.
|
Figura 2: cálculo de los valores hexadecimales
correspondientes a cada una de las filas del sprite 0
|
Podríamos ser malvados y dejar como ejercicio al lector que calcule cómo se codificarían
el resto de sprites, pero vamos a ahorrarle el mal trago. A continuación incluímos el
código que codifica todos los sprites anteriores, que deberemos introducir dentro de un
archivo de cabecera llamado coches.h:
char sprite0[] = { 10, 10, 0x00 , 0x00 , 0x73 , 0x80 , 0xFF , 0x40 , 0xB9 ,
0xC0 , 0xB9 , 0xC0 , 0xB9 , 0xC0 , 0xB9 , 0xC0 , 0xFF , 0x40 , 0x73 ,
0x80 , 0x00 , 0x00 };
char sprite1[] = { 10, 10, 0x3F , 0x00 , 0x5E , 0x80 , 0x7F , 0x80 , 0x61 ,
0x80 , 0x21 , 0x00 , 0x3F , 0x00 , 0x7F , 0x80 , 0x7F , 0x80 , 0x61 ,
0x80 , 0x3F , 0x00 };
char sprite2[] = { 10, 10, 0x00 , 0x00 , 0x73 , 0x80 , 0xBF , 0xC0 , 0xE7 ,
0x40 , 0xE7 , 0x40 , 0xE7 , 0x40 , 0xE7 , 0x40 , 0xBF , 0xC0 , 0x73 ,
0x80 , 0x00 , 0x00 };
char sprite3[] = { 10, 10, 0x3F , 0x00 , 0x61 , 0x80 , 0x7F , 0x80 , 0x7F ,
0x80 , 0x3F , 0x00 , 0x21 , 0x00 , 0x61 , 0x80 , 0x7F , 0x80 , 0x5E ,
0x80 , 0x3F , 0x00 };
char sprite4[] = { 10, 10, 0x0F , 0x00 , 0x1D , 0x80 , 0x13 , 0xC0 , 0x79 ,
0x40 , 0xFC , 0xC0 , 0xBE , 0xC0 , 0xDF , 0x80 , 0x6E , 0x00 , 0x36 ,
0x00 , 0x1C , 0x00 };
char sprite5[] = { 10, 10, 0x1C , 0x00 , 0x36 , 0x00 , 0x6E , 0x00 , 0xDF ,
0x80 , 0xBE , 0xC0 , 0xFC , 0xC0 , 0x79 , 0x40 , 0x13 , 0xC0 , 0x1D ,
0x80 , 0x0F , 0x00 };
char sprite6[] = { 10, 10, 0x3C , 0x00 , 0x6E , 0x00 , 0xF2 , 0x00 , 0xA7 ,
0x80 , 0xCF , 0xC0 , 0xDF , 0x40 , 0x7E , 0xC0 , 0x1D , 0x80 , 0x1B ,
0x00 , 0x0E , 0x00 };
char sprite7[] = { 10, 10, 0x0E , 0x00 , 0x1B , 0x00 , 0x1D , 0x80 , 0x7E ,
0xC0 , 0xDF , 0x40 , 0xCF , 0xC0 , 0xA7 , 0x80 , 0xF2 , 0x00 , 0x6E ,
0x00 , 0x3C , 0x00 };
char sprite8[] = { 10, 10, 0x1E , 0x00 , 0x7F , 0x80 , 0x7F , 0x80 , 0xF3 ,
0xC0 , 0xE1 , 0xC0 , 0xE1 , 0xC0 , 0xF3 , 0xC0 , 0x7F , 0x80 , 0x7F ,
0x80 , 0x1E , 0x00 };
|
Y a continuación indicamos el código de un pequeño programa, que llamaremos
coches.c, que lo único que hará será mostrar por pantalla todos los sprites para
comprobar que quedan bonitos. Para ello hacemos uso de la función putsprite, cuya
sintaxis se explicó en el artículo anterior:
#include "games.h"
#include "coches.h"
void main(void)
{
putsprite(spr_or,1,41,sprite0);
putsprite(spr_or,21,41,sprite1);
putsprite(spr_or,41,41,sprite2);
putsprite(spr_or,61,41,sprite3);
putsprite(spr_or,1,61,sprite4);
putsprite(spr_or,21,61,sprite5);
putsprite(spr_or,41,61,sprite6);
putsprite(spr_or,61,61,sprite7);
putsprite(spr_or,1,81,sprite8);
}
|
El archivo de cabecera games.h debe ser incluido si queremos utilizar
putsprite. En la siguiente imagen vemos la salida de este programa.
|
Figura 3. Los sprites mostrándose en la pantalla
de nuestro Spectrum (o de nuestro emulador)
|
Añadiendo movimiento
Hacer que un sprite se mueva por pantalla es tan sencillo como borrarlo de donde se
encontraba anteriormente y volver a dibujarlo en una nueva posición. Lo interesante será
hacer que nuestro coche cambie de aspecto según la orientación en la que se esté
moviendo.
Para conseguir esto último, es decir, que se nos muestre un sprite distinto del coche
según se esté moviendo hacia arriba, hacia abajo, etc., debemos añadir un par de cambios
al archivo coches.h. Lo primero que vamos a hacer es almacenar todos los sprites
en un único array de arrays de chars (es decir, en un único array de sprites). Esto lo
hacemos colocando después de la definición de los diferentes sprites en coches.h
una línea como la siguiente:
char *sprites[9] = { sprite0 , sprite1 , sprite2 , sprite3 , sprite4 , sprite5 ,
sprite6 , sprite7 , sprite8 };
|
De tal forma que podremos acceder al sprite i utilizando, por ejemplo, sprites[i]
en lugar de spritei, lo cual nos va a ser de mucha utilidad. Justo después de
esta línea, introducimos las dos líneas siguientes en coches.h:
int izquierda[] = {4,6,7,5,1,0,2,3};
int derecha[] = {5,4,6,7,0,3,1,2};
|
Estos dos arrays los utilizaremos para saber, cada vez que giramos, cuál es el sprite que
debemos dibujar en la pantalla. Si los sprites están numerados según la primera figura
de este artículo, y ahora mismo se nos muestra en la pantalla el sprite 0, si giramos a
la izquierda el que se debería mostrar es el 4. Si volvemos a girar a la izquierda, el
que debería dibujarse entonces en la pantalla es el 1, etc. Si por el contrario, estamos
mostrando el sprite 0 en la pantalla y giramos a la derecha, se nos debería mostrar el
sprite 5. Si volvemos a girar a la derecha, se nos debería mostrar el sprite 3,
etc.
¿Cómo representamos esto con los arrays indicados anteriormente? Para cada uno de estos
dos arrays, la posición i representa, para el sprite i, cual debería ser
el sprite que se debería dibujar si giramos hacia la izquierda, en el caso del primer
array, o hacia la derecha, en el caso del segundo. Así, por ejemplo, si estamos
mostrando el sprite 1 (el coche hacia arriba) y giramos a la izquierda, el valor para la
posición 1 (recordemos que en C la primera posición de los arrays es el 0) en el array
izquierda es 6, por lo que deberemos mostrar el sprite 6, correspondiente al coche
dirigiéndose en diagonal hacia arriba a la izquierda. Sin embargo, si lo que hacemos es
girar hacia la derecha, el valor almacenado en la posición 1 del array derecha es el 4,
por lo que deberemos mostrar el sprite 4, correspondiente al coche dirigiéndose hacia
arriba a la derecha.
Habiendo hecho estas dos modificaciones, ya podemos incluir aquí el código de un programa
que llamaremos movimiento.c, que nos pondrá a los mandos de un coche que se
mostrará en la pantalla, el cual podremos mover hacia donde queramos, sin ninguna
restricción.
#include "stdio.h"
#include "ctype.h"
#include "games.h"
#include "coches.h"
void main(void)
{
int x = 100;
int y = 100;
int posicion = 0;
putsprite(spr_xor,x,y,sprites[0]);
while(1)
{
switch(toupper(getk()))
{
case 'O':
putsprite(spr_xor,x,y,sprites[posicion]);
posicion = izquierda[posicion];
putsprite(spr_xor,x,y,sprites[posicion]);
break;
case 'P':
putsprite(spr_xor,x,y,sprites[posicion]);
posicion = derecha[posicion];
putsprite(spr_xor,x,y,sprites[posicion]);
break;
}
}
}
|
De momento vamos a comenzar con los giros, y ya surcaremos después la pantalla a toda
velocidad. El código anterior nos dibuja el coche en una zona cercana al centro de la
pantalla, y nos permite girarlo hacia la izquierda y hacia la derecha empleando las
teclas o y p respectivamente. Explicamos el código y más adelante
indicamos un problema que se muestra durante la ejecuciñón y cómo solucionarlo.
Lo primero que hacemos es incluir algunos archivos de cabecera correspondientes a la
librería z88dk para poder hacer uso de algunas funcionalidades de la librería. El
segundo de ellos, ctype.h permite utilizar la función toupper, que
devuelve la cadena que se le pasa como parámetro pasada a mayúsculas. El tercero que
incluimos, games.h, es el que hemos utilizado hasta ahora para poder utilizar putsprite,
que ya sabemos de sobra lo que hace. Y finalmente, el primero de ellos, stdio.h,
lo incluimos para poder leer de teclado con la función getk. Evidentemente,
también debemos incluir coches.h, pues es donde se definen nuestros sprites, el
array que los contiene a todos ellos, y los arrays izquierda y derecha
cuyo funcionamiento hemos explicado anteriormente.
Una vez que comienza el programa principal, empezamos a definir variables. En el caso de
x e y, su cometido es tan simple como indicarnos en que posición de la
pantalla se va a encontrar el coche. Ahora que vamos a tener el coche fijo girando a lo
mejor no parece de utilidad, pero luego, más tarde, cuando lo movamos, sí que va a
serasí. La última variable que definimos es posicion encargada de indicarnos que
sprite estamos dibujando en pantalla. Si el array sprites contiene todos los
sprites definidos, al hacer un putsprite de sprites[posicion], lo que
estaremos haciendo es dibujar por pantalla el sprite almacenado en la posición posicion
de dicho array. En nuestro caso, esta variable comienza valiendo 0, por lo que cuando
ejecutemos el programa, veremos que el coche, nada más empezar, se encuentra preparado
para correr orientado hacia la derecha.
Y una vez dibujamos el coche por primera vez, utilizando putsprite, ya estamos
preparados para entrar en el bucle principal. Es muy importante fijarse que para el modo
de dibujado (primer parámetro de putsprite) estamos utilizando spr_xor,
correspondiente a la operación lógica OR EXCLUSIVA. Usamos este modo para poder
borrar sprites de una forma muy cómoda. De forma intuitiva, lo único que debemos saber
es que si dibujamos un sprite usando or exclusiva en una zona de la pantalla que esté
vacía, el sprite se dibujará tal cual en la pantalla. Si dibujamos el sprite usando or
exclusiva en una zona de la pantalla donde ese sprite ya estuviera dibujado, se borrará
sin modificar el resto de los píxeles de por alrededor, solo se borrarán los píxeles que
formen parte del coche.
Por lo tanto, para mover, lo que haremos será dibujar el sprite usando or exclusiva en la
misma posición en la que se encontrara anteriormente, y después vovler a dibujar usando
or exclusiva en su nueva posición.
El bucle principal se va a ejecutar de forma indefinida (while (1)). En el mismo,
leeremos constantemente lo que el usuario introduzca por el teclado utilizando
getk. Hemos de tener en cuenta dos cosas: la función getk es no
bloqueante, lo cual quiere decir que si el usuario no pulsa ninguna tecla, el programa
no se queda detenido en esa instrucción, y utilizamos getk combinado con toupper,
que transforma lo que se le pasa como parámetro a mayúsculas, para que el movimiento
funcione igual tanto si el usuario pulsa las teclas con el bloqueo mayúsculas activado o
sin el. Por eso, dentro del switch, a la hora de comprobar que tecla se ha pulsado,
comparamos la entrada con 'O' y 'P' en lugar de con 'o' y 'p'.
Si la tecla que se ha pulsado es la 'o', deseamos girar hacia la izquierda. Dibujamos el
coche en la posición actualusando or exclusiva, de tal forma que se borra. Con la línea
posicion = izquierda[posicion] lo que hacemos es averiguar que sprite es el que
tenemos que dibujar a continuación, según la orientación en la que nos encontremos.
Finalmente volvemos a dibujar el coche con las mismas coordenadas (x,y), pero utilizando
un sprite distinto, correspondiente a haber girado el coche a la izquierda. En el caso
de que se hubiera pulsado la tecla 'p' se realizaría la misma operación, pero utilizando
el array derecha.
Ahora ya podemos compilar y ejecutar y comprobaremos como podemos girar nuestro coche
pulsando las teclas 'o' y 'p'- Sin embargo, veremos que se produce un efecto extraño. Al
mantener una tecla pulsada durante un tiempo, es como si estuviéramos escribiendo en
BASIC: se gira una vez, y tras un breve tiempo, ya se gira sin interrupción. Más tarde
averiguaremos como resolver este pequeño contratiempo. Antes, veamos como podemos hacer
que nuestro coche acelere y frene.
Queremos que pulsando la tecla 'q' el coche vaya acelerando hasta una velocidad máxima (y
por lo tanto, que se vaya moviendo conforme a esa velocidad) y que al pulsar la tecla
'a', el coche vaya frenando hasta detenerse. A continuación vemos como lo hemos
resuelto, en el siguiente programa, que sustituirá a nuestro anterior
movimiento.c (el código en rojo es nuevo):
#include "stdio.h"
#include "ctype.h"
#include "games.h"
#include "coches.h"
#define CICLOS_BASE 300;
void main(void)
{
int x = 100;
int y = 100;
int posicion = 0;
int velocidad = 0;
int ciclos = CICLOS_BASE;
putsprite(spr_xor,x,y,sprites[0]);
while(1)
{
switch(toupper(getk()))
{
case 'Q':
if (velocidad < 50)
{
velocidad = velocidad + 5;
}
break;
case 'A':
if (velocidad > 0)
{
velocidad = velocidad - 5;
}
break;
case 'O':
putsprite(spr_xor,x,y,sprites[posicion]);
posicion = izquierda[posicion];
putsprite(spr_xor,x,y,sprites[posicion]);
break;
case 'P':
putsprite(spr_xor,x,y,sprites[posicion]);
posicion = derecha[posicion];
putsprite(spr_xor,x,y,sprites[posicion]);
break;
}
if (velocidad > 0)
{
ciclos = ciclos - velocidad;
if (ciclos < 0)
{
ciclos = CICLOS_BASE;
putsprite(spr_xor,x,y,sprites[posicion]);
switch(posicion)
{
case 0:
x = x + 1;
break;
case 1:
y = y - 1;
break;
case 2:
x = x - 1;
break;
case 3:
y = y + 1;
break;
case 4:
x = x + 1;
y = y - 1;
break;
case 5:
x = x + 1;
y = y + 1;
break;
case 6:
x = x - 1;
y = y - 1;
break;
case 7:
x = x - 1;
y = y + 1;
break;
}
putsprite(spr_xor,x,y,sprites[posicion]);
}
}
}
}
|
Lo más evidente es que necesitaremos una variable, en este caso llamada velocidad,
que nos permita almacenar la velocidad del coche en un momento dado. Es importante tener
en cuenta que en un principio el coche estará parado, por lo que esta velocidad valdrá
cero. Hemos añadido también código que hará que nuestra velocidad aumente o disminuya,
hasta una velocidad máxima o mínima, al pulsar las teclas 'q' y 'a', respectivamente
(este código es el que se ha añadido dentro del switch).
Lo que a lo mejor queda un poco más esotérico es ver cómo hacemos que el coche, una vez
que adquiere velocidad, pueda moverse. La parte del código encargada de esto es la que
se encuentra al final. Este código, como es obvio, solo se ejecutará si el coche tiene
velocidad. El coche se moverá cuando una variable, llamada ciclos, y a la que se
le va restando el valor de la velocidad en cada iteración del bucle principal, llegue a
cero. Evidentemente, cuanta mayor sea la velocidad, más rápido llegará ciclos a 0
y cada menos iteraciones del bucle principal se moverá el coche.
Si los ciclos llegan a cero, nos disponemos a mover el coche. Lo primero es volver a
hacer que ciclos valga su valor inicial, para volver a comenzar el proceso al terminar
de mover. Lo siguiente es borrar el coche de su posición anterior, dibujándolo en dicha
posición usando el modo or exclusiva. Y después, con un switch, cambiamos el valor de la
coordenada x y/o y según la orientación del coche, dada por la variable
posicion. Así, por ejemplo, si posicion vale 0, eso quiere decir que el
coche está orientado hacia la derecha, por lo que aumentamos el valor de la coordenada
x. Si posicion valiera 4, el coche estaría orientado hacia arriba a la derecha,
por lo que disminuiríamos el valor de y y aumentaríamos el de x. Y así con
el total de las 8 orientaciones. Finalmente, dibujamos el coche en su nueva posición x
e y.
Aquí encontramos varias ventajas con respecto al BASIC. Primero, que los gráficos
definidos por el usuario (en este caso, sprites) no tienen por qué estar limitados a un
tamaño de 8 por 8, y segundo, que estos sprites pueden moverse utilizando incrementos
que sean cualquier múltiplo de un pixel, mientras que en basic debemos mover los UDGs de
8 en 8.
Al ejecutar este programa, veremos como podemos acelerar y frenar, y el coche se moverá,
sin ninguna restricción. Si el coche sale de la pantalla, observaremos como este entra
por el extremo opuesto y el programa seguirá funcionando sin problemas. Sin embargo, se
sigue produciendo el mismo efecto que comentábamos antes, el teclado parece comportarse
como si estuviéramos en BASIC... vamos a solucionarlo dándole valor a dos variables del
sistema, ¡pero que no se asuste nadie!.
Las variables del sistema se podrían entender como determinadas direcciones de memoría
que modifican el comportamiento del sistema según su valor (más información en la
Microhobby especial nº2). En nuestro caso concreto, las variables del sistema que vamos
a modificar son REPDEL y REPPER, correspondientes a las direcciones de memoria 23561 y
23562. La primera de ellas indica el tiempo en cincuentavos de segundo que se debe tener
pulsada una tecla para que esta se comience a repetir, y la segunda indica el tiempo en
cincuentavos de segundo que tarda en producirse esta repetición una vez que se comienza.
Si le damos un valor de 1 a estas variables, conseguiremos una respuesta dle teclado
rápida, y nos desharemos del efecto tan fastidioso que hemos comentado antes.
En el siguiente código se muestran, en rojo, los cambios que deberíamos realizar al
comienzo del programa movimiento.c:
#include "stdio.h"
#include "ctype.h"
#include "games.h"
#include "coches.h"
#define CICLOS_BASE 300;
void main(void)
{
int x = 100;
int y = 100;
int posicion = 0;
char *puntero1 = (char *) 23561;
char *puntero2 = (char *) 23562;
int velocidad = 0;
int ciclos = CICLOS_BASE;
putsprite(spr_xor,x,y,sprites[0]);
while(1)
{
*puntero1 = 1;
*puntero2 = 1;
switch(toupper(getk()))
{
|
Al mejorar la respuesta del teclado nos ha surgido un nuevo problema... ¡el coche gira
demasiado rápido!.
Será conveniente añadir un contador para que el coche no gire nada más pulsar la tecla
correspondiente; mejor que gire cuando la tecle lleve un rato pulsada. A continuación
mostramos, en rojo, el código que deberíamos añadir:
int girando = 0;
int contador_izquierda = 0;
int contador_derecha = 0;
int velocidad = 0;
int ciclos = CICLOS_BASE;
putsprite(spr_xor,x,y,sprites[0]);
while(1)
{
*puntero1 = 1;
*puntero2 = 1;
girando = 0;
switch(toupper(getk()))
{
case 'Q':
if (velocidad < 50)
{
velocidad = velocidad + 5;
}
break;
case 'A':
if (velocidad > 0)
{
velocidad = velocidad - 5;
}
break;
case 'O':
contador_derecha = 0;
contador_izquierda = contador_izquierda + 1;
girando = 1;
if (contador_izquierda == 3)
{
putsprite(spr_xor,x,y,sprites[posicion]);
posicion = izquierda[posicion];
putsprite(spr_xor,x,y,sprites[posicion]);
contador_izquierda = 0;
}
break;
case 'P':
contador_izquierda = 0;
contador_derecha = contador_derecha + 1;
girando = 1;
if (contador_derecha == 3)
{
putsprite(spr_xor,x,y,sprites[posicion]);
posicion = derecha[posicion];
putsprite(spr_xor,x,y,sprites[posicion]);
contador_derecha = 0;
}
break;
}
if (girando == 0)
{
contador_izquierda = 0;
contador_derecha = 0;
}
if (velocidad > 0)
{
|
Nos vamos a basar en tres nuevas variables, girando, contador_izquierda y
contador_derecha. Las dos últimas son las que nos van a indicar cuándo hacer el
giro. Cada vez que le demos a la tecla de giro a la izquierda, aumentará el valor de contador_izquierda,
y cada vez que le demos a la tecla de giro a la derecha, aumentará el valor de contador_derecha.
Cuando alguna de ellas valga 3, giraremos a la izquierda o a la derecha,
respectivamente.
Una medida que tomamos es que al girar hacia la izquierda, ponemos a cero la variable contador_derecha,
que nos dice cuanto tiempo hemos estado girando a la derecha, y viceversa, cuando
giramos a la derecha, ponemos a cero la variable contador_izquierda, que nos dice
cuánto hemos girado hasta la izquierda hasta el momento. Así evitamos que, en el caso de
haber estado girando hacia la izquierda durante un tiempo, por ejemplo, empecemos a
girar a la derecha, y que tan solo haga falta rozar la tecla de giro izquierda para
volver a girar a la izquierda, lo cual no es demasiado real.
Otra medida que tomamos está relacionada con la variable girando. Si dejamos de
pulsar las teclas de giro, no es demasiado real que si volvemos a pulsarlas otra vez
pasado un rato y durante muy poco tiempo, giremos. Para solucionar esto, lo que hacemos
simplemente es: darle a la variable girando el valor 0 cada vez que entramos en
el bucle, darle el valor 1 en el caso de que pulsemos giro izquierda o giro derecha, y
por último, poner contador_izquierda y contador_derecha a cero en el caso
de que no se haya girado en esa iteración, y por lo tanto, en el caso de que
girando valga cero... es bastante sencillo de comprender.
¿Y ya está? ¡No! Más problemas se interponen entre nosotros y un sprite en movimiento. Si
ejecutamos el código anterior veremos como el coche... ¡no gira! ¿Es que hemos hecho
algo mal? No, la algoritmia está bien, pero tenemos un problema de interrupciones. El
programa no espera a que pulsemos una tecla, por lo que a lo mejor nosotros estamos
pulsando la tecla P todo el rato, pero el programa no llega a leer el teclado a tiempo y
girando vuelve a valer 0.
Vamos a hacer que el bucle principal solo avance cuando se produzca una interrupción.
Dichas interrupciones se producirán en dos casos, cuando pulsemos una tecla (lo cual es
lo que queremos) y cuando vaya a comenzar el refresco de la pantalla (cuando el haz de
electrones se sitúe de nuevo en el punto 0,0 de la pantalla para empezar a dibujar, de
izquierda a derecha y de arriba a abajo). Para ello tendremos que utilizar...
¡ensamblador!. Pero no hay que preocuparse, porque lo solucionaremos todo con una línea
de código.
La instrucción en ensamblador que detiene la ejecución hasta que se produce una
interrupción es HALT. Y en z88dk, ejecutar una instrucción en ensamblador en
cualquier momento es tan sencillo como hacer uso de la función asm, que recibe
como parámetro una cadena conteniendo la instrucción que deseamos ejecutar. Por lo
tanto, resolver de una vez por todas nuestros problemas de movimiento es tan simple como
colocar la instrucción asm("HALT") en los lugares adecuados. A continuación se
muestra todo el código de movimiento.c tal como quedaría después de los cambios
(marcados en rojo).
#include "stdio.h"
#include "ctype.h"
#include "games.h"
#include "coches.h"
#define CICLOS_BASE 20;
void main(void)
{
int x = 100;
int y = 100;
int posicion = 0;
char *puntero1 = (char *) 23561;
char *puntero2 = (char *) 23562;
int girando = 0;
int contador_izquierda = 0;
int contador_derecha = 0;
int velocidad = 0;
int ciclos = CICLOS_BASE;
putsprite(spr_xor,x,y,sprites[0]);
while(1)
{
*puntero1 = 1;
*puntero2 = 1;
asm("halt");
girando = 0;
switch(toupper(getk()))
{
case 'Q':
if (velocidad < 30)
{
velocidad = velocidad + 1;
}
break;
case 'A':
if (velocidad > 0)
{
velocidad = velocidad - 1;
}
break;
case 'O':
contador_derecha = 0;
contador_izquierda = contador_izquierda + 1;
girando = 1;
if (contador_izquierda == 3)
{
asm("halt");
putsprite(spr_xor,x,y,sprites[posicion]);
posicion = izquierda[posicion];
putsprite(spr_xor,x,y,sprites[posicion]);
contador_izquierda = 0;
}
break;
case 'P':
contador_izquierda = 0;
contador_derecha = contador_derecha + 1;
girando = 1;
if (contador_derecha == 3)
{
asm("halt");
putsprite(spr_xor,x,y,sprites[posicion]);
posicion = derecha[posicion];
putsprite(spr_xor,x,y,sprites[posicion]);
contador_derecha = 0;
}
break;
}
if (girando == 0)
{
contador_izquierda = 0;
contador_derecha = 0;
}
if (velocidad > 0)
{
ciclos = ciclos - velocidad;
if (ciclos < 0)
{
ciclos = CICLOS_BASE;
asm("halt");
putsprite(spr_xor,x,y,sprites[posicion]);
switch(posicion)
{
case 0:
x = x + 1;
break;
case 1:
y = y - 1;
break;
case 2:
x = x - 1;
break;
case 3:
y = y + 1;
break;
case 4:
x = x + 1;
y = y - 1;
break;
case 5:
x = x + 1;
y = y + 1;
break;
case 6:
x = x - 1;
y = y - 1;
break;
case 7:
x = x - 1;
y = y + 1;
break;
}
putsprite(spr_xor,x,y,sprites[posicion]);
}
}
}
}
|
Como se puede comprobar, se ha incluido un halt antes de la lectura de teclado,
para detener el programa hasta que se pulse una tecla, y después se ha incluido un halt
antes de borrar y volver a dibujar el sprite, para esperar el refresco de la pantalla.
Como consecuencia, el coche irá más lento, es por ello que también hemos modificado el
valor de CICLOS_BASE, la velocidad máxima, y el incremento y decremento de la
velocidad. ¿Y os habeis fijado? Gracias a que hemos sincronizado el movimiento del coche
con el refresco de la pantalla... ¡este ha dejado de parpadear al moverse!
Por último, una cosa curiosa: si mientras movemos el coche pasamos por encima de donde
pone Bytes: movimiento (correspondiente a la carga desde cinta) veremos como el
borrado y la escritura del sprite del coche no afecta en lo mas mínimo a los píxeles que
forman parte de ese texto; esto es sin duda otra de las grandes ventajas de usar el modo
de dibujado de sprites or exclusiva.
Y ahora, pongamos el coche en una pista
Por último vamos a dibujar una pista de neumáticos para limitar un poco el movimiento de
nuestro bólido. Vamos además a hacerlo de tal forma que sea muy fácil modificar el
trazado, y así la diversión se multiplique durante meses. Si recordamos, el último de
los sprites que habíamos definido era el correspondiente a un neumático. Este sprite,
com todos los anteriores, tiene un tamaño de 10x10 (quizá un poco grande, para hacer
circuitos más complicados quizas no hubiera estado mal utilizar sprites de neumáticos
más pequeños).
Dentro de coches.h es donde definiremos el trazado de la pista. Si consideramos la
pantalla como un array de 18x24 casillas de tamaño 10x10 (el mismo que los neumáticos),
podemos indicar el recorrido de la pista creando un array, donde colocaremos ceros en
aquellas casillas donde no vaya a haber neumático, y unos en las casillas en las que sí.
Al principio de coches.h definimos unas constantes para el tamaño del circuito:
#define ALTURA_CIRCUITO 18
#define ANCHURA_CIRCUITO 24
|
y al final del mismo archivo incluimos el siguiente array:
short circuito1[] = { 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0 };
short circuito2[] = { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 0, 0, 0, 0 };
short circuito3[] = { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 1, 0, 0 };
short circuito4[] = { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0 };
short circuito5[] = { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1 };
short circuito6[] = { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1 };
short circuito7[] = { 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 1 };
short circuito8[] = { 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0, 1 };
short circuito9[] = { 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0, 1 };
short circuito10[] = { 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0,
0, 0, 0, 0, 0, 1, 0 };
short circuito11[] = { 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0,
0, 0, 0, 0, 1, 0, 0 };
short circuito12[] = { 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0 };
short circuito13[] = { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0 };
short circuito14[] = { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0 };
short circuito15[] = { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0 };
short circuito16[] = { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0 };
short circuito17[] = { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0 };
short circuito18[] = { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0 };
short int *circuito[ALTURA_CIRCUITO] = {circuito1, circuito2, circuito3, circuito4,
circuito5, circuito6, circuito7, circuito8, circuito9, circuito10, circuito11,
circuito12, circuito13, circuito14, circuito15, circuito16, circuito17,
circuito18};
|
Con un poco de imaginación podemos vislumbrar en ese array un circuito cerrado en forma
de O. Si ahora creamos un fichero llamado juego.c, que contenga el mismo código
que movimiento.c, pero añadiéndole el que aperece en color rojo a continuación,
podremos por fin ver nuestor circuito en la pantalla, tal como se ve en la siguiente
imagen:
void main(void)
{
int x = 100;
int y = 140;
int posicion = 0;
short int i;
short int j;
char *puntero1 = (char *) 23561;
char *puntero2 = (char *) 23562;
int girando = 0;
int contador_izquierda = 0;
int contador_derecha = 0;
int velocidad = 0;
int ciclos = CICLOS_BASE;
for (i=0;i<30;i++)
printf("\n");
for (i=0;i<ALTURA_CIRCUITO;i++)
for (j=0;j<ANCHURA_CIRCUITO;j++)
if (circuito[i][j] == 1)
putsprite(spr_xor,j*10+1,i*10+1,sprites[8]);
putsprite(spr_xor,x,y,sprites[0]);
while(1)
{
*puntero1 = 1;
*puntero2 = 1;
asm("halt");
|
|
Figura 4. Nuestro coche dispuesto a ser el rey de
la pista
|
Hay que tener en cuenta que hemos cambiado el valor inicial de la coordenada y del coche
para que éste quede dentro de la pista. También hemos creado dos variables, i y
j que nos van a servir de contadores en un par de bucles.
Este par de bucles son los dos que se muestran en rojo. En el primero lo único que
hacemos es escribir en la pantalla 30 líneas en blanco para borrarla (es por eso que en
la captura anterior ya no se ve lo de Bytes: juego.tap que molestaba tanto). Es
en el segundo en el que dibujamos el circuito.
Si hemos decidido anteriormente que el array circuito nos iba a indicar si en cada
celda de 10x10 píxeles en las que dividíamos la pantalla había o no un neumático, lo que
haremos para dibujar la pista no es más que recorrer todas las posiciones del array y
dibujar en la pantalla un neumático en la posición i*10+1, j*10+1, siendo
i y j las coordenadas dentro del array donde hemos leído un 1.
De acuerdo, ya tenemos un coche moviéndose y un circuito... pero cuando el coche llega
hasta alguno de los neumáticos, en lugar de producirse una espectacular explosión, lo
atraviesa como si nada y sigue su camino. Tenemos, sin duda, que añadir colisiones.
Para detectar cuando nuestro coche colisiona con uno de los neumáticos que forman parte
de los límites de la pista, deberemos añadir una condición if antes de cada
desplazamiento del mismo, de tal forma que si ese desplazamiento va a provocar que coche
y neumático entren en contacto, el coche se pare totalmente (dada la poca velocidad a la
que se ha programado el coche en nuestro programa, y a nuestra incapacidad de momento
para crear grandes explosiones usando z88dk en el Spectrum, vamos a suponer que al
contactar con un neumático el coche solamente queda parado).
A continuación mostrmos lo que sería el código completo de juego.c, con las
partes nuevas en color rojo, como siempre:
#include "stdio.h"
#include "ctype.h"
#include "games.h"
#include "coches.h"
#define CICLOS_BASE 20;
void main(void)
{
int x = 100;
int y = 140;
int posicion = 0;
short int i;
short int j;
char *puntero1 = (char *) 23561;
char *puntero2 = (char *) 23562;
int girando = 0;
int contador_izquierda = 0;
int contador_derecha = 0;
int velocidad = 0;
int ciclos = CICLOS_BASE;
for (i=0;i<30;i++)
printf("\n");
for (i=0;i<ALTURA_CIRCUITO;i++)
for (j=0;j<ANCHURA_CIRCUITO;j++)
if (circuito[i][j] == 1)
putsprite(spr_xor,j*10+1,i*10+1,sprites[8]);
putsprite(spr_xor,x,y,sprites[0]);
while(1)
{
*puntero1 = 1;
*puntero2 = 1;
asm("halt");
girando = 0;
switch(toupper(getk()))
{
case 'Q':
if (velocidad < 30)
{
velocidad = velocidad + 1;
}
break;
case 'A':
if (velocidad > 0)
{
velocidad = velocidad - 1;
}
break;
case 'O':
contador_derecha = 0;
contador_izquierda = contador_izquierda + 1;
girando = 1;
if (contador_izquierda == 3)
{
asm("halt");
putsprite(spr_xor,x,y,sprites[posicion]);
posicion = izquierda[posicion];
putsprite(spr_xor,x,y,sprites[posicion]);
contador_izquierda = 0;
}
break;
case 'P':
contador_izquierda = 0;
contador_derecha = contador_derecha + 1;
girando = 1;
if (contador_derecha == 3)
{
asm("halt");
putsprite(spr_xor,x,y,sprites[posicion]);
posicion = derecha[posicion];
putsprite(spr_xor,x,y,sprites[posicion]);
contador_derecha = 0;
}
break;
}
if (girando == 0)
{
contador_izquierda = 0;
contador_derecha = 0;
}
if (velocidad > 0)
{
ciclos = ciclos - velocidad;
if (ciclos < 0)
{
ciclos = CICLOS_BASE;
asm("halt");
putsprite(spr_xor,x,y,sprites[posicion]);
switch(posicion)
{
case 0:
if (circuito[y/10][(x + 9)/10] == 1 ||
(y%10 != 0 && circuito[(y+7)/10][(x+9)/10] == 1))
velocidad = 0;
else
x = x + 1;
break;
case 1:
if (circuito[(y-2)/10][x/10] == 1 ||
(x%10 != 0 && circuito[(y-2)/10][(x+7)/10] == 1))
velocidad = 0;
else
y = y - 1;
break;
case 2:
if (circuito[y/10][(x-2)/10] == 1 ||
(y%10 != 0 && circuito[(y+7)/10][(x-2)/10] == 1))
velocidad = 0;
else
x = x - 1;
break;
case 3:
if (circuito[(y+9)/10][x/10] == 1 ||
(x%10 != 0 && circuito[(y+9)/10][(x+7)/10] == 1))
velocidad = 0;
else
y = y + 1;
break;
case 4:
if (circuito[y/10][(x + 9)/10] == 1 ||
(y%10 != 0 && circuito[(y+7)/10][(x+9)/10] == 1) ||
circuito[(y-2)/10][x/10] == 1 ||
(x%10 != 0 && circuito[(y-2)/10][(x+7)/10] == 1))
velocidad = 0;
else
{
x = x + 1;
y = y - 1;
}
break;
case 5:
if (circuito[y/10][(x + 9)/10] == 1 ||
(y%10 != 0 && circuito[(y+7)/10][(x+9)/10] == 1) ||
circuito[(y+9)/10][x/10] == 1 ||
(x%10 != 0 && circuito[(y+9)/10][(x+7)/10] == 1))
velocidad = 0;
else
{
x = x + 1;
y = y + 1;
}
break;
case 6:
if (circuito[y/10][(x-2)/10] == 1 ||
(y%10 != 0 && circuito[(y+7)/10][(x-2)/10] == 1) ||
circuito[(y-2)/10][x/10] == 1 ||
(x%10 != 0 && circuito[(y-2)/10][(x+7)/10] == 1))
velocidad = 0;
else
{
x = x - 1;
y = y - 1;
}
break;
case 7:
if (circuito[y/10][(x-2)/10] == 1 ||
(y%10 != 0 && circuito[(y+7)/10][(x-2)/10] == 1) ||
circuito[(y+9)/10][x/10] == 1 ||
(x%10 != 0 && circuito[(y+9)/10][(x+7)/10] == 1))
velocidad = 0;
else
{
x = x - 1;
y = y + 1;
}
break;
}
putsprite(spr_xor,x,y,sprites[posicion]);
}
}
}
}
|
Simplemente, antes de mover el coche, en la última parte del bucle principal, comprobamos
si va a haber una colisión con alguno de los neumáticos. En el caso de que sea así
ponemos la velocidad a cero, y en caso contrario realizamos el movimiento de forma
normal. En todas las comprobaciones miramos a ver si en la posición del array circuito
correspondiente a donde estaría mas o menos situado el coche (recordemos que en circuito
cada posición se corresponde con 10x10 píxeles de la pantalla) hay un 1.
Analicemos, por ejemplo, para el case 0, que se corresponde con el coche
moviéndose hacia la derecha, y el resto de condiciones se podrá sacar de la misma forma
(hemos de aclarar que en los cuatro últimos case, al tratarse de movimientos
diagonales, la comprobación debe ser en los dos sentidos, eje x y eje y):
if (circuito[y/10][(x + 9)/10] == 1 ||
(y%10 != 0 && circuito[(y+7)/10][(x+9)/10] == 1))
|
Básicamente la comprobación se compone de dos partes, la que está a la izquierda del OR
(que en C, recordemos, se codifica con ||) y la que está a la derecha. Si en el array
circuito decíamos que cada posición se correspondía con una región de 10x10
píxeles de la pantalla, cuando el coche está en las coordenadas (y,x), tendríamos
que comprobar en la posición (y/10,x/10) del array circuito (en C, las
divisiones enteras se redondean hacia abajo).
La anchura y la altura del coche es 10, por lo tanto, si queremos movernos hacia la
derecha, aumentando el valor de x en 1, debemos comprobar si el pixel situado en
el extremo derecho, es decir, el pixel situado en x+11 , estará en contacto con
algún pixel de algún neumático. Esto es equivalente a comprobar si en la posición [y/10][(x+11)/10]
de circuito hay almacenado un 1. Si es así habrá colisión, por lo que no nos
interesará avanzar, se cumplirá la condición, y velocidad pasará a valer 0. Lo
que ocurre es que en nuestro caso, en lugar de x+11 hemos usado x+9 ya
que, por ensayo y error hemos comprobado que con este valor el coche solo colisionara
cuando este totalmente pegado al neumático (nuestros sprites son de 10x10, pero algunos
de ellos no ocupan toda la anchura o toda la altura).
Esto estaría bien si el coche solo pudiera moverse en el eje y en múltiplos de 10,
pero esto no siempre es así. Si el coche estuviera en (12,12) (valiendo 12 la
coordenada y, por lo tanto), al movernos a la derecha, podríamos colisionar con
un neumático almacenado en el array circuito en (1,2), pero también con el
que estuviera en (2,2) (el de abajo). Por lo tanto, la segunda condición
comprueba si, en el caso de que la coordenada y no tenga un valor múltiplo de 10
(es decir, cuando el resultado de la operación resto, especifacada en C con % y que
calcula el resto de la división entera, devuelva un valor distinto de cero), habría
colisión con un neumático situado abajo a la derecha. Como en el caso de la x, en
lugar de sumar 11 a y, añadimos un valor calculado a ojo que nos asegura que el
coche quedaría pegadito en el caso de una colisión vertical.
Se podrían cumplir las dos condiciones a la vez, pero nos es indiferente, con que se
cumpla una de las dos ya hay colisión.
En el caso de movernos hacia la izquierda, en lugar de sumar a x restamos, y en el
caso de movernos hacia arriba o hacia abajo, hacemos lo mismo pero cambiando lo que
sumamos o restamos a x por lo que sumamos o restamos a y y viceversa.
¿Y eso es todo? Bueno... hemos de ser sinceros y admitir que debido a la inclusión del
código que detecta las colisiones, ha vuelto el parpadeo del coche al moverse, sobre
todo por la parte superior de la pantalla. No le da tiempo al programa a sincronizar el
movimiento con el refresco de la pantalla, debido a lo complejo de los cálculos. Esto, a
grandes rasgos, se puede mejorar, aunque solo sea un poco, creando dos nuevas variables,
x_anterior e y_anterior, y dejando el código de detección de colisiones de
la siguiente manera (calculando las colisiones antes de esperar el refresco de la
pantalla y borrar y dibujar el sprite):
if (velocidad > 0)
{
ciclos = ciclos - velocidad;
if (ciclos < 0)
{
ciclos = CICLOS_BASE;
y_anterior = y;
x_anterior = x;
switch(posicion)
{
case 0:
if (circuito[y/10][(x + 9)/10] == 1 ||
(y%10 != 0 && circuito[(y+7)/10][(x+9)/10] == 1))
velocidad = 0;
else
x = x + 1;
break;
case 1:
if (circuito[(y-2)/10][x/10] == 1 ||
(x%10 != 0 && circuito[(y-2)/10][(x+7)/10] == 1))
velocidad = 0;
else
y = y - 1;
break;
case 2:
if (circuito[y/10][(x-2)/10] == 1 ||
(y%10 != 0 && circuito[(y+7)/10][(x-2)/10] == 1))
velocidad = 0;
else
x = x - 1;
break;
case 3:
if (circuito[(y+9)/10][x/10] == 1 ||
(x%10 != 0 && circuito[(y+9)/10][(x+7)/10] == 1))
velocidad = 0;
else
y = y + 1;
break;
case 4:
if (circuito[y/10][(x + 9)/10] == 1 ||
(y%10 != 0 && circuito[(y+7)/10][(x+9)/10] == 1) ||
circuito[(y-2)/10][x/10] == 1 ||
(x%10 != 0 && circuito[(y-2)/10][(x+7)/10] == 1))
velocidad = 0;
else
{
x = x + 1;
y = y - 1;
}
break;
case 5:
if (circuito[y/10][(x + 9)/10] == 1 ||
(y%10 != 0 && circuito[(y+7)/10][(x+9)/10] == 1) ||
circuito[(y+9)/10][x/10] == 1 ||
(x%10 != 0 && circuito[(y+9)/10][(x+7)/10] == 1))
velocidad = 0;
else
{
x = x + 1;
y = y + 1;
}
break;
case 6:
if (circuito[y/10][(x-2)/10] == 1 ||
(y%10 != 0 && circuito[(y+7)/10][(x-2)/10] == 1) ||
circuito[(y-2)/10][x/10] == 1 ||
(x%10 != 0 && circuito[(y-2)/10][(x+7)/10] == 1))
velocidad = 0;
else
{
x = x - 1;
y = y - 1;
}
break;
case 7:
if (circuito[y/10][(x-2)/10] == 1 ||
(y%10 != 0 && circuito[(y+7)/10][(x-2)/10] == 1) ||
circuito[(y+9)/10][x/10] == 1 ||
(x%10 != 0 && circuito[(y+9)/10][(x+7)/10] == 1))
velocidad = 0;
else
{
x = x - 1;
y = y + 1;
}
break;
}
asm("halt");
putsprite(spr_xor,x_anterior,y_anterior,sprites[posicion]);
putsprite(spr_xor,x,y,sprites[posicion]);
}
}
|
¿Y ahora qué?
Bueno, parece que al final lo hemos conseguido: tenemos un coche moviéndose por la pista,
que se choca con los bordes del circuito y puede acelerar y frenar... tomando este
código como base, podríamos crear nuestro fantástico juego de coches, añadir nuevas
pistas, contrincantes, etc. Sin embargo, hemos comprobado ciertas limitaciones en el
código de dibujado de sprites que se suministra con z88dk, sobre todo en juego.c,
donde se produce un ligero parpadeo del coche cuando este se mueve por la parte
superior. En próximos artículos estudiaremos alguna librería que nos puede ayudar a
superar el trance... pero mientras, ¡a conducir!.
Me gustaría agradecer tanto a Horace como a NoP por los comentarios realizados y la ayuda
que me han prestado durante la realización de este artículo.
LINKS
|
SIEW |
|