help me to make 8BP better

If you like 8BP, your help is welcome. Help me supporting this initiative (from 1€, whatever you want). Donations will be used for materials and 8BP disemination:
Mostrando entradas con la etiqueta consejos. Mostrar todas las entradas
Mostrando entradas con la etiqueta consejos. Mostrar todas las entradas

lunes, 7 de marzo de 2022

Cambios de rutas desde BASIC

 Hola amigos de 8 bits de poder

En esta ocasión os traigo un pequeño truco motivado por un desarrollador que quería cambiar la dirección de salto de un personaje en mitad de una ruta

Supongamos que quieres que tu personaje salte hacia la derecha y en mitad del sato quieres que el personaje pueda cambiar hacia la izquierda, continuando el salto. Lo que estamos planteando se puede representar con este dibujo:



En este ejemplo nuestro personaje salta y en mitad de la ruta de salto hacia la derecha, cambia de dirección en el punto 2, continuando con la ruta de salto a la izquierda, pero sin comenzar un nuevo salto, simplemente continuando el salto y alcanzando la misma altura (punto 3) que alcanzaría con el salto hacia la derecha para finalmente terminar el salto a la izquierda en el punto 4.

Para poder hacer esto necesitamos cambiar la ruta a nuestro personaje desde basic en el punto 2, pero no podemos usar el comando SETUPSP porque ello iniciaría la ruta, obteniendo un salto mucho mayor, que comenzaría en el punto 2. Lo que podemos hacer en ese caso es simplemente un POKE a la dirección de memoria que almacena la ruta del Sprite, que es la dirección de su estado +15, tal como muestra la tabla de atributos de sprites.

 

Suponiendo que tenemos dos rutas de salto (ruta 0 salto a derecha y ruta 1 salto a izquierda), el siguiente ejemplo ilustra el concepto (puedes cargar las demos de 8BP y modificar estas líneas en la demo 2 para ver el efecto

130 'ciclo de juego ------

150 |AUTOALL,1:|PRINTSPALL,0,1,0

170 ' rutina movimiento personaje -----

173 IF PEEK(27000)<128 THEN 178

174 'estamos en mitad de un salto

175 IF INKEY(27)=0 THEN POKE 27015,0:GOTO 180:'cambio ruta a derecha

176 IF INKEY(34)=0 THEN POKE 27015,1:GOTO 180:'cambio ruta a izq

177 GOTO 193

178 IF INKEY(67)=0 THEN |SETUPSP,0,0,137:|SETUPSP,0,15,dir:'saltar

180 IF INKEY(27)=0 THEN dir=0:|SETUPSP,0,6,1:'ir derecha

190 IF INKEY(34)=0 THEN dir=1:|SETUPSP,0,6,-1:'ir izquierda

193 ciclo=ciclo+1

310 GOTO 150


La única limitación del uso de POKE para este propósito es que la imagen del personaje no cambia hasta que no encuentra un código de cambio de imagen en mitad de la ruta. Cambiar la imagen usando SETUPSP es posible pero peligroso, ya que no sabes si el personaje esta subiendo (borrado con líneas inferiores) o bajando (borrado con líneas superiores). Por ello es mejor simplemente asignar la ruta y que la propia ruta cambie la imagen lo antes posible. Puedes incluso poner en mitad de la ruta cambios de imagen aimque no sean necesarios por si ocurre esta circunstancia. Lo que si tienes que hacer es que la imagen de salto tenga bytes de borrado en ambos lados pues si no lo tiene y cambia de dirección, dejaría un rastro.

He añadido este briconsejo en el manual de 8BP (apartado 12.2.5 del manual) , y en mi próximo post además os hablaré de un fabuloso juego que acaba de salir para 8BP y que podréis encontrar en el respositorio github de 8BP

hasta la vista!

lunes, 19 de febrero de 2018

Usa más colores en 8BP con sobreescritura

Hola amigos de 8 Bits de poder

hoy os traigo un consejo de programación, una técnica para usar mas de 9 colores en 8BP con sobreesritura.

Como sabéis, A diferencia de muchos videojuegos que para la sobreescritura usan máscaras y doble buffer (gastando mucha memoria), en 8BP he optado por una solución que no gasta memoria y permite la sobreescritura y restablecer el fondo como por arte de magia, sin necesidad de guardarlo. Y sin necesidad de mascaras.

Básicamente 8BP "esconde" los colores del fondo dentro de los colores del personaje. En el fondo lo que ocurre es que los bits que se usan para el color de fondo no interfieren con los bits que se usan para el color del personaje y por lo tanto el fondo nunca se destruye aunque lo parezca. En el capitulo "sprites con sobreescritura" del manual describo con detalle esta técnica. La ventaja: no requiere doble buffer y es tan rápida como usar mascaras. El inconveniente: solo puedes usar 7 de los 16 colores para tus sprites y para los colores de fondo puedes usar 2 colores. Para los objetos de fondo que no requieran sobreescritura (una nube,un tejado) se pueden usar 9 colores. La limitación compensa la gran ventaja de no usar doble buffer, pues asi hay mucha mas memoria para tu programa. Por ejemplo el juego "Nibiru" usa sobreescritura ( mira la fase 2) usando esta técnica. Y también he subido algunas demos que la usan


Los resultados los teneis en las imagenes que siguen.

 
 

¿Y si quieres aun mas color?
Si has hecho tus primeras pruebas con sobreescritura y necesitas mas colores en tu videojuego, hay una forma de lograrlo, pero necesitas entender bien el método de 8BP.

Suponte que solo uno de tus sprites requiere sobreescritura y consume 3 colores. Eso significa que debes destinar 6 tintas a este Sprite. Sin embargo, si el resto de sprites no requieren sobreescritura, puedes imprimirlos sin sobreescritura y usar mas colores en ellos, es decir:
2 tintas para el fondo ( 2 colores)
6 tintas para el Sprite con sobreescrituras ( 3 colores)
8 tintas para los demás sprites ( 8 colores)

En este caso, en total podrás usar 13 colores!!!! Simplemente tienes que activar el flag de sobreescritura en el Sprite que lo necesita y dejarlo inactivo en los otros sprites. En los sprites que no usen sobreescritura podrás usar los 13 colores, en el Sprite con sobreescritura usarías 3 y en el fondo 2.


Otros ejemplos son posibles. Por ejemplo, si los sprites con sobreescritura necesitan 4 colores, entonces gastarán 8 tintas. Aparte tendremos 2 tintas para el fondo y las 6 tintas restantes pueden identificar 6 colores diferentes, es decir que podremos usar un total de 12 colores.

Espero que este consejo de programación os sea útil. Pronto os  traeré la 8BP v32 , está terminada, simplemente quiero entregaros también un juego de coches que estoy acabando. Un poco de paciencia y os prometo que os gustará

un abrazo y hasta pronto!


viernes, 18 de marzo de 2016

Lógicas masivas: controla el tiempo, no el espacio

hola amigos de 8 bits de poder

Hoy voy a tratar de explicar en profundidad como funciona la técnica de lógicas masivas en el caso mas complejo: hileras de naves con trayectorias. Espero que esta detallada explicación os ayude a construir vuestras propias lógicas masivas.

Comencemos imaginando una trayectoria para una hilera de 8 naves enemigas. las naves pasarán una a una por una serie de "nodos de control", que son lugares en el espacio donde deben cambiar su dirección, definida por sus velocidades en X, e Y, es decir (Vx,Vy)

Trayectoria definida con "nodos de control"
Una forma de controlar que las 8 naves cambien de dirección en dichos lugares sería comparar sus coordenadas X,Y con la de cada uno de los nodos de control y si coinciden con alguno de ellos, entonces aplicamos las velocidades nuevas asociadas al cambio en ese nodo. Puesto que hablamos de 2 coordenadas, 8 naves y 4 nodos, estamos ante:

2 x 8 x 4 = 64 comprobaciones en cada fotograma

Esto no es viable si queremos velocidad desde BASIC. Y es que no es una estrategia eficiente computacionalmente. Puesto que estamos ante un escenario "determinista", podríamos estar seguros en cada instante de tiempo donde van a encontrase cada una de las naves y por lo tanto en lugar de hacer comprobaciones en el espacio, podemos únicamente centrarnos en la coordenada temporal (que es el número de fotograma del juego o el también llamado número de "ciclo de juego")

Puesto que conocemos a la velocidad a la que se mueven las naves, podemos saber cuando la primera de ellas pasará por el primer nodo. A ese instante lo llamaremos t(1). También asumiremos que debido a la separación entre las naves, la segunda de las naves pasará por el nodo en el instante t(1)+10. la tercera en t(1)+20 y la octava en t(1)+70

Sabiendo esto podemos controlar el tiempo con dos variables: una contará las decenas (i) y otra las unidades(j). Para controlar el cambio de las 8 naves en el primer nodo podemos escribir:

j=j+1: IF j=10 THEN j=0: i=i+1
IF i>=t(1) AND i<t(1) +8 THEN [actualiza velocidad de nave  i-t(1) con los valores de velocidad del nodo 1]

Como vemos con una sola linea podemos ir cambiando las velocidades de cada nave a medida que van pasando cada una de ellas por el nodo 1. Durante los 8 instantes de tiempo posteriores a t(1) se van actualizando cada una de las naves, justo cuando pasan por el nodo de control

Ahora vamos a aplicar lo mismo a los 4 nodos. Podríamos ejecutar 4 comprobaciones en lugar de una, pero sería ineficiente. Además si tuviésemos muchos nodos esto supondría muchas comprobaciones. Podemos hacerlo solo con una, teniendo en cuenta que la primera nave pasa por un nodo en un instante t(n) y la ultima nave pasa por ese nodo en t(n)+7

Cuando la primera nave pasa por el primer nodo, tiene sentido pensar en empezar a comprobar el nodo 2, pero no el nodo 3 ni el 4. Ya tenemos el nodo mayor que vamos a controlar.
En cuanto al nodo menor, podemos asumir que aunque tengamos 20 nodos, estén lo suficientemente separados como para que no haya naves atravesando mas de 3 nodos a la vez (vamos a suponer eso y usaremos ese "3" como parámetro). Por lo tanto el nodo menor a comprobar es el mayor - 3. Al nodo menor lo vamos a llamar "nmin" y al mayor "nmax".  ( nmin = nmax-3 ). El caso que queramos tener plena libertad para poder definir cualquier trayectoria,  nmin debe ser nmax menos el número de naves de la hilera.



j=j+1: IF j=10 THEN j=0: i=i+1: n=nmax
IF n<nmin THEN 50:' no hay que actualizar mas naves
IF i>=t(n) AND i<t(n)+8 THEN [actualiza i-t(n)]:IF i-t(n)=0 THEN nmax=nmax+1: nmin=nmax-3
n=n-1

50 ' mas instrucciones del juego

Como veis cuando se incrementa en 1 la decena de tiempo, se comprueban los nodos desde "nmax" hasta "nmin" Y en cada nodo actualizamos la nave i-t(n). Pensad que si t(3) es, por ejemplo, 23, entonces cuando llegamos al nodo 3 en el instante i=23, actualizamos la nave i-23 =0, con los valores de velocidad del nodo 3.

En el siguiente ciclo de juego comprobamos si aun sigue "vigente" el intervalo de tiempo del nodo 2, y actualizamos la nave 1 con los valores de velocidad del nodo 2,

y en el siguiente ciclo comprobamos el nodo 1 y si sigue vigente actualizamos la nave 2, y asi sucesivamente, siempre teniendo en cuenta que la primera nave puede haber llegado a nmax, mientras que la siguiente nave puede estar en nmax-1 y la siguiente en nmax-2, etc.

En resumen, hemos transformado 64 comprobaciones en solo 1, usando "Lógicas masivas". Y si la trayectoria tuviese 40 en lugar de 4 nodos, habríamos transformado 640 operaciones en una sola!

Vale, es algo dificilillo de entender, pero si lo lees despacito otra vez, seguro que lo pillas....y si no, tu pregunta y trato de aclarar más. En la próxima versión de la librería incluiré una buena descripción en el manual sobre este asunto (quizás este mismo post) . Es algo fundamental.



jueves, 10 de marzo de 2016

Un ejemplo de la técnica de "lógicas masivas"

A modo de ejemplo de la técnica de programación de "lógicas masivas", vamos a ver como están hechas las hileras simétricas de 12 naves enemigas del videojuego "Anunnaki".
En este juego se ejecuta en cada ciclo de juego la lógica de chequeo de comprobación de un solo punto de control o "nodo" de la trayectoria, que es aquel lugar donde las naves pueden cambiar de dirección. Se van comprobando secuencialmente dichos nodos, pero solo uno en cada ciclo de juego, de modo que solo dos naves pueden cambiar de dirección simultáneamente en el mismo fotograma. Como podemos apreciar, no es una limitación importante y el resultado final es aceptable a pesar de que estamos ejecutando todo desde BASIC. 

Cada instrucción es muy costosa en BASIC, pero programando con esta técnica logramos controlar la lógica de 12 naves enemigas  + 3 disparos + nuestra nave, un total de 16 sprites con vida propia pero con una muy reducida lista de instrucciones en cada ciclo.


En azul he destacado el ciclo de juego (sin las inicializaciones) y en rojo la linea que gobierna el rumbo de todas las naves. Esto es "lógicas masivas". Intenta comprender la filosofía en este programa y no los detalles. Solo una línea que gobierna a todos. Solo una, y en cada ciclo de juego se centra en dos naves diferentes en función del instante de tiempo en que nos encontramos.

2999' fase 4 ----trayectorias en dos hileras simetricas configurables por k(i), kx(i),ky(i). se separan de 10 en 10 en x -------------
3000 totaln=12: blandos=31-totaln:duros=100: ini=31-totaln:|COLSP,32,ini,30 
3010 for i=ini to 24
3020 |SETUPSP,i,9,navemala1:|SETUPSP,i,7,0:|SETUPSP,i,5,ky(0):|SETUPSP,i,0,&x01011:|SETUPSP,i,6,kx(0)
3021 |SETUPSP,i+6,9,navemala1:|SETUPSP,i+6,7,0:|SETUPSP,i+6,5,ky(0):|SETUPSP,i+6,0,&x01011:|SETUPSP,i+6,6,-kx(0)
3030 |LOCATESP,i,-30-(i-ini)*20,80+(i-ini)*10:|LOCATESP,i+6,-30-(i-ini)*20,0-(i-ini)*10-6:'6 es el ancho en bytes de la navemala y 26 el alto
3040 |STARS:gosub 500:gosub 750:|AUTO,7:|AUTO,8:|AUTO,9:|PRINTSPALL,1,0
3050 next
3060 ciclo=0: t=0: col%=32:yd%=200
3100 gosub 500: gosub 750
3109 |AUTOALL:|PRINTSPALL:|STARS
3120 |COLSPALL: if sp<32 then if sp=31 then gosub 300:goto 3000 else gosub 770
3130 ciclo=ciclo+1: if ciclo =10 then ciclo=0:t=t+1:m=inim
3131 if m=finm+1 then 3100
3132 IF t>=k(m) AND t<k(m)+6 THEN i= t-k(m)+ini:|SETUPSP,i,5,ky(m):|SETUPSP,i,6,kx(m):|SETUPSP,i+6,5,ky(m):|SETUPSP,i+6,6,-kx(m):IF m=finm THEN finm=m+1:inim=finm-3  ELSE ELSE  IF t>=k(12) THEN RETURN
3133 m=m+1:goto 3100


sábado, 5 de marzo de 2016

Cómo programar un disparo múltiple

Hola amigos de 8 bits de poder

Hoy os voy a contar como programar un disparo múltiple de un modo eficiente.
Lo primero de todo, para evitar el paso de parámetros (es decir, col,<sprite>,@variable%) haremos

col%=0
|COLSP,33,@col%: ' el sprite 33 no existe, se usa para indicar la variable de trabajo

De este modo sucesivas invocaciones a COLSP,<sprite> dejarán el resultado (número de sprite que colisiona) en la variable col%

Primer caso : un personaje que no dispara
Cuando no queremos hacer que nuestro personaje dispare pero simplemente que le puedan matar los enemigos al colisionar, ejecutamos una instrucción como esta en nuestro ciclo de juego

|COLSP,31 : ' suponemos que el sprite 31 es nuestro personaje.

Segundo caso : un personaje que dispara un solo proyectil
En ocasiones queremos que nuestro personaje dispare un solo disparo. En ese caso incorporaremos
una invocación adicional a la colisión del disparo si ha sido lanzado, es decir

|COLSP,7 : ' suponemos que el sprite 7 es nuestro disparo. 

Tercer caso : un personaje que dispara hasta 3 proyectiles a la vez
pero y si queremos colisionar hasta 3 disparos?  vamos a suponer que tenemos hasta 3 disparos con los sprites 7,8 y 9

La solución mas efectiva se basa en uno de los casos más sencillos de aplicación de la técnica de "lógicas masivas". restringiendo a contemplar solo una de las siguientes cosas a la vez:

  • o colisiona uno de los 3 disparos (solo uno), y mata a un enemigo
  • o un disparo sale del área de pantalla (solo uno)


La rutina seria algo así (dentro de cada ciclo de juego haríamos un gosub 750)

744 ' RUTINA DE COLISION DE DISPAROS
745 ' -------------------------------------------------------  

746 ' CASO 1 comprobamos si hay alguna colision
747 '-------------------------------------------
750 if peek(27112)>0 then |colsp,7: if col%<32 then dir=27113:goto 820
760 if peek(27128)>0 then |colsp,8: if col%<32 then dir=27129:goto 820
770 if peek(27144)>0 then |colsp,9: if col<32 then dir=27145:goto 820

780 ' CASO 2 comprobamos si el disparo se ha salido de pantalla
790 ' ----------------------------------------------------------
800 dc=dc mod 3 +1:dir=ddisp(dc):|peek,dir,@yd%: if yd%<-10 then poke dir-1,0: |POKE,dir,200:nd=nd-1
810 return: 

820 ' continuacion del caso 1 en caso de colision
825 ' ------------------------------------------------------------
830 ' desactivo el disparo
840 poke dir-1,0: nd=nd-1:|PRINTSP,6,peek(dir),peek(dir+2): poke dir,255
850 ' ahora proceso al enemigo segun sea duro o blando
860 if col>=duros then return
870 ' alcance de enemigo tipo blando. lo matamos, asignandole una secuencia de muerte y poniendo su estado a no colisionar mas
880 if col>=blandos then |SETUPSP,col,7,4:|SETUPSP,col,0,&x101:return 
890 return


Y aquí tenéis el resultado. En una nueva versión de la librería podréis ajustar la sensibilidad del comando COLSP, decidiendo si el solape entre sprites debe ser de varios pixels o de uno solo, para considerar que ha habido colisión. Y también alguna cosa mas muy interesante relacionada con el comando COLSP. Y como siempre, serán mejoras retrocompatibles.

https://www.youtube.com/watch?v=QDUZmM6AEZ4




viernes, 26 de febrero de 2016

ANNUNAKI: nuestro pasado alien

Hola amigos de 8 bits de poder (8BP)

He avanzado un poco en el videojuego "Annunaki" (hecho en BASIC usando 8BP) y quiero compartir avances y un consejo de programación.

Os quiero contar la diferencia entre:

c = c + 1: IF c=4 THEN c=1

y

c = c mod 3 +1


A primera vista parecen lo mismo, pero si estamos programando en BASIC debemos ser muy cuidadosos. La primera opción consume 2.6 milisegundos  mientras que la segunda consume solo 1.84 ms
Un buen programador debe elegir la segunda opción. Pensad que vuestro videojuego esta lleno de instrucciones y si cada buena elección representa un ahorro de 1 ms o medio milisegundo , cuando se acumulan instrucciones puede haber una gran diferencia, sobre todo en juegos de arcade que requieren velocidad. Intentad siempre reducir complejidad computacional (número de instrucciones) y elegirlas bien (las que consuman menor tiempo). Programar es un arte.
Para medir el tiempo que consume un comando usad un programa como este:

10 MEMORY 26999
11 DEFINT a-z
12 c%=0: a=2
40 a!= TIME
50 FOR i=1 TO 1000
60 AQUI PONEIS LA INSTRUCCION QUE QUEREIS MEDIR
70 NEXT
80 b!=TIME
90 PRINT (b!-a!)
100 c!=1000/((b!-a!)*1/300)
110 PRINT c, "fps"
120 d!=c!/60
130 PRINT "puedes ejecutar ",d!, "comandos por barrido (1/50 seg)"
140 PRINT "el comando tarda ";((b!-a!)/300 -0.47);"milisegundos"

y ahora, un aperitivo, de lo que se puede hacer con 8BP y un poco de BASIC (imagen y video)