Mostrando entradas con la etiqueta deteccion colision. Mostrar todas las entradas
Mostrando entradas con la etiqueta deteccion colision. Mostrar todas las entradas

jueves, 21 de enero de 2016

ZX Spectrum, pasando un "misil" por en medio de la pantalla en BASIC

Como hemos visto en la entrada que ejemplifica como pasar de coordenadas de caracteres a coordenadas de alta resolución en pantalla del Spectrum, se pueden mezclar instrucciones que usan coordenadas de alta resolución como PLOT e instrucciones que usan las coordenadas de modo "caracter" como PRINT AT en BASIC.

¿Que pasa si queremos, por ejemplo, pasar un "misil" (linea recta de 8 bits) por en medio de caracteres o UDG's que ya están en pantalla ?. Es decir lo que queremos conseguir es una animación que se solape por encima de lo que hay en pantalla. 

Bueno, lo primero que tenemos que tener en cuenta son cuatro particularidades del Spectrum, que condicionarán nuestro trabajo:
  • Solo hay dos colores por "celda" de 8x8 en pantalla. Es decir, cuando "pisamos" una celda, solo podemos gestionar dos colores a la vez, el de fondo (PAPER) y el del carácter o gráfico (INK). 
  • La ordenación de las direcciones en memoria, no son "lineales". Es decir, hay que calcular posiciones en memoria sabiendo que hay "saltos raros" de memorias en función de la posición en pantalla. 
  • No hay una manera directa de pasar de decimal a binario. Hay que hacer una subrutina que nos lo haga, dado que necesitamos saber el "dibujo" en pantalla por donde pasamos. PEEK nos devuelve un decimal y para dibujar en pantalla, necesitamos saber que es lo que estamos "pisando". 
  • Los efectos FLASH y BRIGHT no afectan a los colores y se pueden usar libremente. Es decir, no nos complican la vida.


Acordémonos que lo que hay en cada celda, es un dibujo de 8x8 bits.

En el siguiente vídeo, vemos ejemplificado el paso de un misil, de izquierda a derecha, que además pasa por encima de letras, y cuando sale, las deja como estaban. 





Se observa el efecto de los colores, cuando pasamos por una letra. Dado que el misil es negro, cuando escribimos un punto con PLOT toda la celda se pasa a color negro. 

Para hacerlo funcionar correctamente tanto en las celdas "vacias" (que solo tienen un espacio en blanco y color de fondo) y recomponer las letras y los colores cuando el misil pasa por encima de ella usamos dos instrucciones importantes, OVER e INVERSE.

Cuando el misil pasa, imprimimos un punto usando OVER 1. Cuando vamos "borrando" el misil y recuperando lo que había, usamos INVERSE en función de lo que había en esa posición, un 0 o un 1. 

También tenemos en cuenta si es el último pixel, para devolverle el color a la celda. 

IF r$(x)="0" AND X<8 THEN PLOT OVER 1;(px-8)+(x-1),py+4: GO TO 390
IF r$(x)="1" AND X<8 THEN PLOT INVERSE 0; FLASH f; BRIGHT bold; FLASH f; INK co;(px-8)+(x-1),py+4: GO TO 390
IF R$(X)="0" AND X=8 THEN PLOT INVERSE 1; INK co; BRIGHT bold; FLASH f;(px-8)+(x-1),py+4: GO TO 390
IF R$(X)="1" AND X=8 THEN PLOT INVERSE 0; INK co; BRIGHT bold; FLASH f;(px-8)+(x-1),py+4

También usamos ATTR y un mecanismo para calcular la posición en memoria y el contenido.

Es todo un poco lioso, pero viendo el ejemplo en código se entiende mucho mejor. 

Como siempre el código BASIC es bastante lento, pero una vez que lo pasamos a código máquina usando el compilador HiSoft (y habiendo optimizado el código BASIC) el rendimiento es aceptable.

He dejado el código BASIC del ejemplo un este enlace

En este otro enlace que dejado el mismo ejemplo pasado a código máquina. 



miércoles, 13 de enero de 2016

Algoritmo de búsqueda de camino en ZX BASIC (Path Finding)

Una de las cosas más comunes en los juegos es implementar un algoritmo que lleve a un personaje del punto A al punto B. En el camino, siempre, obstáculos por donde no se puede pasar.

Estos algoritmos, denominados "Path Finding" o "Búsqueda de Camino", están muy bien documentados y hay muchísimos ejemplos en Internet para entornos de programación actuales.

Bueno, ¿ Que pasa si queremos implementar esta funcionalidad en Sinclair BASIC ?. 

Nos encontramos con varios problemas:
  • El uso de recursos de lenguaje de programación como colas, objectos , etc.
  • Las limitaciones propias del Sinclair BASIC. Tenemos muy poca memoria donde debe residir todo el programa o juego, no solo el algoritmo y un numero muy limitado de espacio para variables.
  • La velocidad de proceso en Sinclair BASIC, sobre todo dentro de bucles. 


Todo esto comienza como parte del desarrollo del juego Retro8ogue. Estoy en la fase de implementar el movimiento de los monstruos en pantalla, pero con sentido (o lo que llaman también I.A., Inteligencia Artificial). 

Una vez dentro del rango de visión / oído del monstruo, éste debe encontrar el camino hacia el personaje, independientemente del mapa y los obstáculos que se pueda encontrar. Como es un juego rogue - like, el mapa es generado aleatoriamente y nunca es igual. 

Dadas las limitaciones, partimos de las siguientes premisas:


Es decir, simplificar al máximo las posibilidades de búsqueda para minimizar el código y las necesidades de proceso. Añadir un poquito de aleatoriedad en la búsqueda del mejor camino. 

Veamos el ejemplo en pantalla de nuestro intento: 























Hay una escalera que el personaje tiene que encontrar. 

El mapa es aleatorio, pero no muy poblado de bloques para facilitar la labor. 

Lo primero que definimos es que hay 8 direcciones posibles para el personaje. 

4 5 6
3@7
2 1 8

La idea es que, de las 8 posibles direcciones, ¿cual es la que está mas cerca del objetivo?. 

Todo lo hacemos con coordenadas X e Y, para lo que la pantalla del Spectrum es perfecta. 

Esto lo calculamos de la siguiente manera: 

d = ABS(destino_x - origen_x)+ABS(destino_y-origen_y) 

Si calculamos esto sobre las 8 posibles direcciones que podemos seguir, sabemos cual es la que mas cerca nos deja del objetivo. Si esa dirección está ocupada por algo que no nos deja pasar, nos movemos de forma aleatoria 1 vez. 

Hay muchas maneras más efectiva de hacer esto, pero la utilizada nos garantiza que: 
  • Eventualmente, lleguemos al objetivo. 
  • El monstruo se comporta de vez en cuando de forma aleatoria en su movimiento, lo que añade jugabilidad al juego. 
  • Es muy fácil de implementar en BASIC y de optimizar para usar el mínimo de código, variables y proceso. 


El resultado en BASIC es aceptable en términos de rendimiento, y una vez pasado a código máquina, funciona de forma excelente. 

Si al ejemplo le quitamos el uso de detección de UDG's , irá aún más rápido. He utilizado los UDG's para hacer el ejemplo más completo y aplicable al desarrollo de Retro8ogue

Hay otras optimizaciones hechas en el movimiento aleatorio para que no se pierda tiempo, pero esas las puedes ver en el propio código (como no intentar otra vez un lado que está obstruido en el mismo ciclo).

Puedes descargar una cinta en formato TAP con el código Sincalir Basic aquí.

Puedes descargar una cinta en formato TAP con el programa en código máquina aquí

Puedes ver el vídeo del ejemplo funcionando: 








lunes, 14 de diciembre de 2015

BASIC, no puedo localizar UDG's con SCREEN$ !!. He aqui una opción buena bonita y barata.

Una de las funciones más útiles que tiene el BASIC de nuestro ZX Spectrum es SCREEN$. 

Gracias a esta función podemos saber que carácter se encuentra presente en la pantalla, dadas las coordenadas X e Y.

En el modo de "texto" tenemos 22 líneas (Y) y 32 columnas (X). Es muy cómodo, utilizar SCREEN$ como mecanismo de detección de colisiones en un juego en el que hallamos usado caracteres (de 8x8) como gráficos. 

Como se puede ver en !Como me gustan los UDG! es muy sencillo usar los 20 caracteres de gráficos de usuario que nos facilita el Spectrum, para hacer nuestros propios sprites de 8x8. 

Los podemos usar en pantalla, y todo bien, hasta que se nos ocurre la brillante idea de usar SCREEN$ para obtenerlos de pantalla. SCREEN$ nos devolverá un vacío, dado que no es capaz de gestionar los UDG. Nuestro gozo en un pozo. 

He aquí unas cuantas líneas de código para tener nuestro propio "SCREEN$" y poder identificar los UDG en pantalla. 

La idea es la misma, le damos unas coordenadas de linea/columna, y el código nos devuelve el código UDG al cual pertenece. Si la variable de control (res) es un cero (0) es que el carácter que se encuentra en las coordenadas proporcionadas, no es un UDG. Si la variable de control res tiene un valor igual o superior a 144, nos está devolviendo el código del UDG que ha encontrado en esas coordenadas. 

  10 REM Localizacion de  UDG por coordenadas x,y
  20 REM igual que el SCREEN$(y,x) pero funciona con UDG
  30 REM vamos a cargar el UDG 149 (f)
  40 FOR i=0 TO 7
  50 POKE USR "f"+i,255
  60 NEXT i
  70 PRINT AT 7,13;CHR$ 149
  80 REM le damos las coordenadas y res nos devuelve el UDG
  90 LET x=13: LET y=7
 100 GO SUB 260
 110 PRINT AT 0,0;"El UDG es el ";res
 120 GO TO 310
 130 LET res=udg
 140 LET vY=y*8
 150 LET vX=x
 160 FOR S=0 TO 7
 170 LET iY=vY+S
 180 LET BLOCK=INT (iY/64)
 190 LET CROW=INT (iY/8)
 200 LET YR=iY-(CROW*8)
 210 LET CROW=CROW-(BLOCK*8)
 220 LET ADD=16384+BLOCK*2048+CROW*32+YR*256
 230 IF (PEEK ((USR (CHR$ udg))+s))<>(PEEK (add+vX)) THEN LET res=0: RETURN
 240 NEXT S
 250 RETURN
 260 FOR i=144 TO 164
 270 LET udg=i
 280 GO SUB 130
 290 IF res>=144 THEN RETURN
 300 NEXT i
 310 STOP
Hay varias cosas que tener en cuenta:


  • Las coornedas linea/columa las fijamos igual que en el SCREEN$. Ya se encarga el cógido de las "visicitudes" y "saltos" de las direcciones de memoria en pantalla. 
  • Está optimizado para UDG's de 8x8. Con un poco de paciencia y coco, se puede adaptar a 16x16, o 32x32 de forma sencilla. 
  • Puedes leer  "Jugando a los Sprites, episodio I" para ampliar la funcionalidad.
  • Si res vale cero(0), no es un UDG.
  • Si res es 144 o más , es un UDG y res es el código del mismo. 
  • Se compara byte a byte el caracter en pantalla, con la(s) definición(es) de(los) UDG.
  • Está optimizado para el mejor rendimiento (en caso de diferencia, aborta y al siguiente). 


Ahora ya no tienes excusa para usar los UDG's en esos juegos que puedes hacer y basar tu detección de colisiones de forma muy sencilla desde BASIC.