Este sitio web usa cookies de terceros para analizar el tráfico y personalizar los anuncios. Si no está de acuerdo, abandone el sitio y no siga navegando por él. ×


Tema 16 - Sprites

Los "Sprites" son figuras multicolor, capaces de moverse por la pantalla sin alterar la imagen de fondo.

Algunos ordenadores de los años 80 tenían hardware preparado para manejar sprites de forma eficiente (aunque la mayoría de las veces no eran sencillos de programar desde Basic, y había que recurrir a código máquina).

En el caso de los Amstrad CPC, no existía ese soporte de sprites por hardware, así que veremos alguna de las ideas básicas de cómo imitarlos desde software.

 

16.1 Primer acercamiento: un recuadro que rebota

Como primer acercamiento, vamos a hacer que un recuadro rebote en la pantalla. Todavía no habrá imagen de fondo, ni múltiples colores, pero al menos se tratará de una figura "no demasiado pequeña" que se mueva de forma suave.

Sprites CPC 1

El tamaño habitual de un sprite es (era) de 16x16 píxeles o 32x32 píxeles. Como un carácter en un CPC está formado por 8x8 puntos, podemos crear una figura sencilla usando cuatro caracteres (dos filas de dos caracteres). Hay que tener presente que en los CPC la anchura de los caracteres depende del modo de pantalla: son cuadrados en modo 1, pero más anchos que altos en modo 0 (que son los dos modos más habituales para juegos). En este ejemplo usaremos modo 0, que permite más colores (a cambio de menos resolución), por lo que era el más habitual en juegos. Para que el movimiento sea suave, nos moveremos en coordenadas de pantalla gráfica, y sincronizaremos con el barrido de la pantalla antes de dibujar.

10 ' Sprites, primera aproximacion
100 ' Figuras que forman el sprite
110 s1$=chr$(150)
120 s2$=chr$(156)
130 s3$=chr$(147)
140 s4$=chr$(153)
300 ' Resto de inicializacion
310 mode 0
320 x = 100
330 y = 100
340 incrX = 4
350 incrY = 4
360 tag
500 ' Parte repetitiva
510 while inkey$ = ""
520   ' Dibujar figura
530   frame
540   move x,y
550   print s1$;s2$;
560   move x,y-8
570   print s3$;s4$;
580   ' Pausa entre fotogramas
590   for i = 1 to 10: next
600   ' Calcular prox posicion
610   x = x + incrX
620   y = y + incrY
630   if (x > 580) or (x < 8) then incrX = -incrX
640   if (y > 340) or (y < 24) then incrY = -incrY
700 wend
2000 ' Finalizacion
2010 tagoff
2020 mode 1
 

 

16.2 Una figura algo más compleja

Esta figura estaba formada por varios símbolos predefinidos, pero eso no es "lo habitual". Generalmente, los personajes de un juego tendrán formas más irregulares, que deberemos definir nosotros. La forma más sencilla, cuando se trata de personajes en un solo color, es redefinir caracteres usando SYMBOL.

110 symbol 240, 1,2,12,48,64,64,64,64
120 symbol 241, 128,64,48,12,2,2,2,2
130 symbol 242, 64,64,64,128,128,128,128,255
140 symbol 243, 2,2,2,1,1,1,1,255
150 s1$=chr$(240)
160 s2$=chr$(241)
170 s3$=chr$(242)
180 s4$=chr$(243)

Sprites CPC 2

Además, esta figura tenía "truco": todos los puntos exteriores eran "blancos", de modo que no hacía falta borrarla entre un fotograma y otro: si la desplazamos poco, cuando dibujábamos, borrábamos a la vez la anterior... pero eso tampoco es lo habitual...

Si ahora movemos la figura sin borrar la anterior, dejará "un rastro". Por eso deberemos borrarla antes de volver a dibujar. La forma más simple de borrar es borrar toda la pantalla con un CLS. No será un método útil cuando haya mucho contenido en la pantalla, porque nos obligaría a redibujar toda la pantalla. Sería más eficiente borrar sólo la zona de pantalla que hemos modificado, pero eso lo dejamos para un poco más adelante. Por ahora, nos limitaremos a hacer:

535   cls

El fuente completo podría ser:

10 ' Sprites, segunda aproximacion
100 ' Figuras que forman el sprite
110 symbol 240, 1,2,12,48,64,64,64,64
120 symbol 241, 128,64,48,12,2,2,2,2
130 symbol 242, 64,64,64,128,128,128,128,255
140 symbol 243, 2,2,2,1,1,1,1,255
150 s1$=chr$(240)
160 s2$=chr$(241)
170 s3$=chr$(242)
180 s4$=chr$(243)
300 ' Resto de inicializacion
310 mode 0
320 x = 100
330 y = 100
340 incrX = 4
350 incrY = 4
360 tag
500 ' Parte repetitiva
510 while inkey$ = ""
520   ' Borrar y dibujar figura
530   frame
535   cls
540   move x,y
550   print s1$;s2$;
560   move x,y-8
570   print s3$;s4$;
580   ' Pausa entre fotogramas
590   for i = 1 to 10: next
600   ' Calcular prox posicion
610   x = x + incrX
620   y = y + incrY
630   if (x > 580) or (x < 8) then incrX = -incrX
640   if (y > 340) or (y < 24) then incrY = -incrY
700 wend
2000 ' Finalizacion
2010 tagoff
2020 mode 1
 

 

16.3 Un fondo real que no se borra

Hay dos formas sencillas de hacer que la imagen parezca "transparente" sobre cualquier fondo: definir un color como transparente o usar el "truco del XOR".

  • El color transparente consiste en crear un "array" de puntos que define nuestro sprite, y comprobar el color de cada punto antes de dibujarlo. Si ese color es el que hemos marcado como transparente, no se dibuja, para que el fondo se vea a través de él. Cuando nuestro sprite se mueve a otra posición, tenemos que volver a dibujar el fondo como estaba, por lo que deberemos memorizarlo antes de dibujar el sprite.
  • Una forma mucho más simple, aunque también más limitada, es hacer una operación XOR entre el color del sprite y el color del fondo. La operación XOR es reversible, así que para borrar basta con dibujar el objeto otra vez en su antigua posición: si el fondo era de color 2 y dibujamos un punto visible (de color 1) de nuestro sprite, el resultado será un punto de color 2 XOR 1 = 3; cuando volvemos a dibujar por segunda vez, el color pasará a ser 3 XOR 1 = 2, el antiguo color del fondo. La limitación viene del hecho de que nuestro fondo habitualmente será de varios colores (por ejemplo, color 0 y color 2), así que al dibujar nuestro objeto obtendremos puntos de colores distintos (por ejemplo 0 XOR 1 = 1 y 2 XOR 1 = 3), así que el efecto visual puede ser "feo", salvo que hagamos que esos colores distintos correspondan a un mismo color de la paleta (por ejemplo, que tanto el color 2 como el 3 correspondan a color 6 de la paleta, así):

380 ink 0,0: ' Fondo = negro
390 ink 2,21: ' Adorno del fondo = verde lima
400 ink 1,6: ' Nave sobre fondo = rojo
410 ink 3,6: ' Nave sobre adorno = rojo

Crearemos un patrón geométrico de fondo, por ejemplo con el efecto que vimos en el apartado 5.2 del curso.

210 mode 0
220 for i = 1 to 640 step 4
230   move 0,0: draw i,400,2,1
240 next
250 for i = 1 to 400 step 2
260   move 0,0: draw 640,i,2,1
270 next

El resultado podría ser algo como:

Sprites CPC 3

Para borrar el sprite, usaremos las mismas órdenes que para dibujarlo. En el primer fotograma de la animación, dibujaremos la imagen, y en los fotogramas siguientes primero borraremos y luego dibujaremos, así:

535   ' Borro si no es primer fotograma
536   if primerFotograma = 1 then primerFotograma = 0:goto 600
540   move x,y
550   print s1$;s2$;
560   move x,y-8
570   print s3$;s4$;
...
600   ' Calcular prox posicion
610   x = x + incrX
620   y = y + incrY
622   ' Dibujo en la nueva posicion
623   move x,y
624   print s1$;s2$;
625   move x,y-8
626   print s3$;s4$;
 

El fuente completo podría quedar así

10 ' Sprites, segunda aproximacion
100 ' Figuras que forman el sprite
110 symbol 240, 1,2,12,48,64,64,64,64
120 symbol 241, 128,64,48,12,2,2,2,2
130 symbol 242, 64,64,64,128,128,128,128,255
140 symbol 243, 2,2,2,1,1,1,1,255
150 s1$=chr$(240)
160 s2$=chr$(241)
170 s3$=chr$(242)
180 s4$=chr$(243)
200 ' Dibujamos el fondo
210 mode 0
220 for i = 1 to 640 step 4
230   move 0,0: draw i,400,2,1
240 next
250 for i = 1 to 400 step 2
260   move 0,0: draw 640,i,2,1
270 next
280 graphics pen 1
300 ' Resto de inicializacion
320 x = 100
330 y = 100
340 incrX = 4
350 incrY = 4
360 tag
370 primerFotograma = 1
380 ink 0,0: ' Fondo = negro
390 ink 2,21: ' Adorno del fondo = verde lima
400 ink 1,6: ' Nave sobre fondo = rojo
410 ink 3,6: ' Nave sobre adorno = rojo
500 ' Parte repetitiva
510 while inkey$ = ""
520   ' Borrar y dibujar figura
530   frame
535   ' Borro si no es primer fotograma
536   if primerFotograma = 1 then primerFotograma = 0:goto 600
540   move x,y
550   print s1$;s2$;
560   move x,y-8
570   print s3$;s4$;
580   ' Pausa entre fotogramas
590   for i = 1 to 10: next
600   ' Calcular prox posicion
610   x = x + incrX
620   y = y + incrY
622   ' Dibujo en la nueva posicion
623   move x,y
624   print s1$;s2$;
625   move x,y-8
626   print s3$;s4$;
622   ' Cambio incremento si llega a un borde
630   if (x > 580) or (x < 8) then incrX = -incrX
640   if (y > 340) or (y < 24) then incrY = -incrY
700 wend
2000 ' Finalizacion
2010 tagoff
2020 mode 1
 

 

16.4 ¿Y las figuras multicolores?

Si queremos dibujar figuras multicolor en Basic, la alternativa que puede parecer más natural es también la más lenta: usar un Array de dos dimensiones para cada sprite, en el que indiquemos el color de cada punto.

110 dim nave(16,16)
120 dim fondo(16,16)
130 for j = 1 to 16
140   for i = 1 to 16 
150     read nave(17-i,17-j)
160   next i
170 next j
...
3000 ' Datos de la nave
3010 data 0,0,0,0,0,0,0,1,3,0,0,0,0,0,0,0
3020 data 0,0,0,0,0,0,1,3,1,3,0,0,0,0,0,0
3030 data 0,0,0,0,1,1,3,0,0,1,1,3,0,0,0,0
3040 data 0,0,1,1,3,0,0,0,0,0,0,1,1,3,0,0
3050 data 0,1,3,0,0,0,0,0,0,0,0,0,0,1,3,0
3060 data 0,1,3,0,0,0,0,0,0,0,0,0,0,1,3,0
3070 data 0,1,3,0,0,0,0,0,0,0,0,0,0,1,3,0
3080 data 0,1,3,0,0,0,0,0,0,0,0,0,0,1,3,0
3090 data 0,1,3,0,0,0,0,0,0,0,0,0,0,1,3,0
3100 data 0,1,3,0,0,0,0,0,0,0,0,0,0,1,3,0
3110 data 0,1,3,0,0,0,0,0,0,0,0,0,0,1,3,0
3120 data 1,3,0,0,0,0,0,0,0,0,0,0,0,0,1,3
3130 data 1,3,0,0,0,0,0,0,0,0,0,0,0,0,1,3
3140 data 1,3,0,0,0,0,0,0,0,0,0,0,0,0,1,3
3150 data 1,3,0,0,0,0,0,0,0,0,0,0,0,0,1,3
3160 data 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3
 

Para dibujar el sprite de forma transparente, recorreríamos con un doble FOR, comprobando si un punto no es cero (color transparente) antes de dibujar:

4000 ' Dibujar la nave en pantalla    
4010 for i = 1 to 16
4020   for j = 1 to 16 
4030     if nave(i,j)<>0 then plot x+i*2,y+j*2,nave(i,j)
4040   next j
4050 next i
4060 return
 

Esto tiene un serio problema de lentitud: para esta figura de 16x16, comprobamos 256 posiciones y las dibujamos una a una usando PLOT, que no es una orden rápida, lo que puede suponer tardar casi un segundo para cada sprite, incluso aunque declaremos i y j como números enteros (con "defint" para ganar algo de velocidad).

El fragmento de programa que se encarga de redibujar el fondo sería casi idéntico, pero sin el IF, ya que habría que dibujar todos los puntos en cualquier caso. para memorizar el fondo antes de dibujar es muy similar, pero usando TEST en vez de PLOT:

4100 ' Guardar el fondo antes de dibujar la nave
4110 for i = 1 to 16
4120   for j = 1 to 16 
4130     fondo(i,j)=test(x+i*2,y+j*2)
4140   next j
4150 next i
4160 return
 

Hay varias formas posibles de mejorar la lentitud de este ejemplo, que mencionaré pero no desarrollaré:

  • La menos razonable: Usar figuras más pequeñas. Si la figura es de 8x8 píxeles, en vez de 16x16, se dibujará 4 veces más rápido.
  • Una más razonable si queremos usar figuras grandes, que podría hacer ganar cerca de un 20% de velocidad, pero que haría el programa más complicado, es no memorizar siempre todo el fondo, sino sólo la parte que ha cambiado (por ejemplo, si la nave se desplaza a la derecha un píxel, no será necesario guardar un bloque de 16x16 píxeles de fondo, sino una "columna" de 1x16, que es la parte de fondo que ha cambiado.
  • Una alternativa más eficiente, pero más difícil de programar, es no dibujar punto a punto con la orden PLOT, y no leer punto a punto con la orden TEST, que son relativamente lentas, sino acceder directamente a la memoria de pantalla, dibujando por filas del sprite. Pero aquí surgen problemas: es necesario saber cómo se guardan en memoria los puntos contiguos de distintos colores (que no es algo evidente), y cómo se dibuja una figura (que tampoco es evidente, porque la memoria gráfica de los CPC está entrelazada)...

Como el resultado no es bueno, podemos mejorarlo "no usando sólo Basic"...

 

16.5 La alternativa mixta: RSX

Hacer sprites multicolor desde Basic puro resulta muy lento. Hacerlos desde código máquina puede ser mucho más rápido, pero cae fuera de los contenidos de este curso. Eso sí, podemos buscar una alternativa "intermedia": alguna extensión RSX al Basic, que permita combinar la rapidez del código máquina con la sencillez de Basic. Una de estas librerías es Sprites Alive.

La página oficial, desde la que se puede descargar el fichero DSK con la librería y el manual original en inglés, la tienes en www.cpcwiki.eu/index.php/Sprites_Alive. Si esta página falla, lo podrás descargar desde www.amstrad-esp.com/programas/amsdos.

Si prefieres un manual en español, tienes una versión traducida por MiguelSky en el apartado de Tutoriales de Amstrad-Esp.com.

El manual son más de 70 páginas, y realmente son necesarias tantas páginas, porque esta herramienta las rutinas de manejo de sprites, pero también permite características más avanzadas, como la detección de colisiones, incluye un editor de sprites, y es capaz de automatizar tareas como que un sprite rebote automáticamente en los límites de la pantalla (en horizontal y/o vertical), o que un sprite se mueva automáticamente cuando pulsamos el teclado o movamos el joystick. Eso hace que se puedan crear programas muy breves. Por ejemplo, una de las demostraciones es un juego tipo "Breakout" (romper ladrillos con una pelota) en sólo 24 líneas de Basic.

Sprites Alive demo

Nosotros no profundizaremos tanto como el manual. Veremos sólo un ejemplo básico...

(Todavía sin completar)

 

Otras rutinas alternativas, más sencillas, son las creadas por Sean McManus, que puedes encontrar en www.sean.co.uk/books/amstrad/amstrad8.shtm.

Sprites Sean Demo

(Todavía sin completar)