Crea tus Juegos con 8BPCursos

Programación avanzada y lógicas masivas con 8BP

5
(5)

En el articulo de esta semana hablaremos sobre la programación avanzada en 8 Bits de Poder, para la que vamos a usar las lógicas masivas. Agarrarse los machos que viene articulo serio y con gran cantidad de información.

El interprete BASIC es muy pesado en ejecución debido a que no solo ejecuta cada comando, sino que analiza el número de línea, realiza un análisis sintáctico del comando introducido, valida su existencia, el número y tipo de parámetros, que sus valores que se encuentren en rangos validos (por ejemplo, PEN 40 es ilegal) y muchas más cosas.

Comenzando con la programación avanzada

Es el análisis sintáctico y semántica de cada comando lo que realmente pesa y no tanto su ejecución. El caso de los comandos RSX no es una excepción. El intérprete BASIC comprueba su sintaxis y eso pesa mucho, a pesar de que sean rutinas escritas en ASM, pues antes de invocarlas, el intérprete BASIC ya ha hecho muchas cosas.

¿Quieres saber por que deberías de querer programar una maquina de 1984? Te lo explicamos en este articulo.

Por consiguiente, hay que ahorrar ejecuciones de comandos, programando con astucia para que la lógica del programa pase por el menor número de instrucciones posibles, aunque ello a veces implique escribir más. Una práctica indispensable es usar instrucciones que muevan o afecten a un grupo de sprites, tales como COLSPALL, AUTOALL o MOVERALL, evitando el uso de bucles con instrucciones que afectan a un solo sprite.

Un factor decisivo a la hora de invocar un comando es el paso de parámetros. Cuantos más parámetros tiene, más costoso es su interpretación por parte del BASIC, incluso aunque sea una rutina ASM que se invoque por CALL, pues el comando CALL sigue siendo BASIC y antes de acceder a la rutina en ASM, se analiza el número y tipo de parámetros irremediablemente.

Para evaluar el coste de ejecución de un comando puedes usar el siguiente programa. También te servirá para evaluar el rendimiento de nuevas funciones en ensamblador que incorpores a la librería 8BP si deseas hacerlo.

1 call &6b78
10 MEMORY 23999
11 DEFINT a-z
12 c%=0: a=2
30 FOR i=0 TO 31:|SETUPSP,i,0,0:NEXT:'reset
31 iteraciones=1000
40 a!= TIME
50 FOR i=1 TO iteraciones
60 <aqui pones un commando, por ejemplo, PRINT “A”>
70 NEXT
80 b!=TIME
90 PRINT (b!-a!): rem lo que tarda en unidades de tiempo cpc. (1/300 segundos)
100 c!=((b!-a!)*1/300)/iteraciones: rem c! = lo que tarda cada iteracion en segundos
120 d!=(1/50)/c!
130 PRINT "puedes ejecutar ",d!, "comandos por barrido (1/50 seg)"
140 PRINT "el comando tarda ";(c!*1000 -0.47);"milisegundos"  

Nota: para los expertos en lenguaje ensamblador, debéis tener en cuenta que si pretendéis medir el tiempo de ejecución de una rutina que internamente desactiva las interrupciones (usa las instrucciones DI, EI) el tiempo que transcurre durante la desactivación no es medible con este programa BASIC. Los comandos de 8BP no desactivan las interrupciones y son todos medibles.

Vamos a ver a continuación el resultado del rendimiento de algunos comandos (medidos con el programa anterior). Hay que decir que es más rápido ejecutar una llamada directa a la dirección de memoria (un CALL &XXXX) que invocar el comando RSX correspondiente. En la siguiente tabla obviamente cuanto menor sea el resultado (expresado en milisegundos), mas rápido es el comando. La tabla que aquí se presenta debes tenerla en todo momento presente y tomar tus decisiones de programación en base a ella.

A continuación veremos una tabla donde encontraremos las medidas en milisegundos y los tiempos de ejecución de las distintas instrucciones Basic.

Tabla con medidas de comandos BASIC y comandos 8BP

ComandomsComentario
PRINT “A”3.63Lentísimo. Ni se te ocurra usarlo, salvo puntualmente para cambiar el número de vidas, pero no imprimas puntuación en un juego por cada enemigo que mates
LOCATE 1,1 PRINT puntos24.87Colocar el cursor de texto con LOCATE e imprimir el valor de luna variable “puntos” es costosísimo. Si actualizas puntos hazlo sólo de vez en cuando y no en cada ciclo de juego
C$=str$(puntos) |PRINTAT,0, y, x, @c$10Imprimir los puntos usando PRINTAT es mucho mas eficiente que usar PRINT, pero aun asi es costoso. Usa PRINTAT con moderación.
REM hola0.20Los comentarios consumen
‘ hola0.25Ahorras 2 bytes de memoria, pero es más lento!!
GOTO 600.19Muy rápido!!! Más rápido incluso que REM. Usa este comando sin piedad, úsalo!!!
A = 3   A = B   A = miarray(x)   A= miarray(x,y)0.55   0.72   1.33   1.84Una simple asignación cuesta. Todo cuesta, cada instrucción debe ser pensada. Asignar el valor de una variable a otra es mas costoso que asignar un valor. Y asignar el valor de un array es aun mas costoso, porque acceder al array cuesta. Y si el array es bidimensional aun cuesta mas.
|LOCATESP,i,10,20   |LOCATESP,i,y,x   CALL &XXXX,i,x,y2.8   3.22   1.81Si no usas coordenadas negativas es mejor usar el comando BASIC POKE para establecer coordenadas. Si las coordenadas son variables entonces tarda mas.   El equivalente CALL es mucho mas rápido.
|MOVER,31,1,1   CALL &XXXX,31,1,1  3.23   1.77Es algo lento y por ello debes usarlo con moderación   El equivalente call es mucho mas rápido
POKE &XXXX, valor0.71Muy rápido! Úsalo para actualizar las coordenadas de los sprites (si son positivas) POKE no acepta números negativos, pero puedes usar la formula 255+x+1 si quieres meter un numero negativo. Por ejemplo, para meter un -4 debes meter 255-4+1=252 Otra forma sencilla de meter positivos y negativos es usar POKE dirección, x and 255
POKE dir,dato0.85Muy rápido teniendo en cuenta que además debe traducir la variable “dir”
|POKE,&xxxx,valor2.5Permite números negativos y si sólo actualizas una coordenada (X o Y) es mejor que LOCATESP
X=PEEK(&xxxx)0.93Muy rápido! Según el tipo de videojuego puede ser una alternativa a COLSP, mirando el color de una dirección de memoria de pantalla. En el apéndice sobre la memoria de video te explico como hacerlo.
X=INKEY(27)1.12Muy rápido. Apto para videojuegos, aunque debes usarlo inteligentemente como se recomienda en este libro.
IF x>50 THEN x=01.42Cada IF pesa, hay que tratar de ahorrarlos porque una lógica de juego va a tener muchos
IF A=valor THEN GOTO 100   Vs   IF A=valor THEN 1001.24   Vs   1.18Ambas sentencias son equivalentes pero la segunda tarda menos
IF inkey(27)=0 then x=51.75Aceptable. Es más rápido que hacer b=INKEY(27) y después el IF…THEN
10 If inkey(27) then  30 20 x=5 30 <instrucciones>1.0Una forma mucho mas eficiente de hacer lo mismo
IF x>0 then   Vs   IF x then  1.3   Vs   0.8En BASIC es posible ahorrar 0.5ms teniendo en cuenta que cualquier valor distinto de cero significa TRUE. Si queremos controlar un valor concreto haremos: 10 IF x-20 THEN 30 20 <cosas a hacer si x=20> 30 … El uso de esta técnica es muy recomendable en la lectura de teclado
A=A+1: IF A>4 then A=0    Vs   A=A MOD 3 +1  2.6     Vs   1.7Este es un ejemplo clarisimo de como debemos programar. Es mucho mejor usar la segunda opción. Por otro lado, el uso de MOD hay que hacerlo con cautela. Si hacemos: A=(A+1) MOD 3 Nos cuesta 2 ms ya que los paréntesis son muy costosos y sin embargo conseguimos lo mismo. Hay una forma mejor de hacerlo, con el operador binario AND
A=1+A AND 7     A=20 +A MOD 7     A=21 + (A and 7)  1.6     1.88     1.95Esto te permite hacer variar una variable cíclicamente entre N valores de modo que te sirve para elegir un sprite ID para tu nuevo disparo o para un enemigo que entra en pantalla   Es mejor usar AND que MOD, ya que AND es una operación binaria rápida y MOD implica una división, muy costosa para nuestro querido microprocesador Z80. Sin embargo, si necesitamos usar sprites ID que no comiencen en 1, entonces necesitaremos paréntesis y la ventaja de velocidad del AND se pierde
:  0.05No ahorra mucho, pero es mas rápido usar “:” en lugar de un nuevo número de línea, y si aplicas esto muchas veces acabas ahorrando de forma significativa.  Dos instrucciones en dos lineas gastan 0.03ms mas que si ambas estan en la misma línea separadas por “:”
|PRINTSP,0,10,105.1Un solo sprite de 14 x 24  (7 bytes x 24 lineas) Ojo, si vas a imprimir varios compensa mucho mas imprimir todos los sprites de golpe con PRINTSPALL
CALL &xxxx,0,10,103.5Equivalente a PRINTSP, así es mas rápido, aunque menos legible
|PRINTSPALL   (32 sprites 8×16 de mode 0, es decir 4 bytes x 16 lineas)57Esto son unos 17 fps a plena carga de sprites. Lo que tarda es   T = 3.25 + N x 1.7 Es decir, 1.7 ms por sprite y un coste fijo de 3 ms. Este coste fijo es el coste del análisis sintáctico de BASIC sumando al de recorrer la tabla de sprites buscando cuales hay que imprimir. Si se omiten los parámetros (es posible y se tomarian los valores de la ultima invocación), se ahorran 0.6ms en la parte fija, es decir: T = 2.6 + N x 1.1   Si la impresión es con sobreescritura y/o flipeada, es mas costosa. A continuación, se muestran los costes relativos de cada tipo de impresión:   Impresión normal: 100% Impresión con sobreescritura: 164% Impresión flipeada: 179% Impresión flipeada con sobreescritura: 220%
|PRINTSPALL,N,0,0 (ningún sprite activo)   N=0 N=10 N=31        2.6 4.3 5.9Coste de ordenar los sprites: Cuando N=0, no habiendo ningún sprite que imprimir, la función debe recorrer la tabla de sprites de forma secuencial. Pero recorrerla de forma ordenada es más costoso, tal como evidencia el tiempo consumido al aumentar N. La diferencia de tiempos (5.9 -2.6 =2.5ms) es lo que cuesta ordenar todos los sprites
|COLAY,@x%,0       |COLAY3.0   Vs   2.4Usar solo con el personaje, no con los enemigos o el juego ira lento. Si el personaje mide múltiplos de 8 es más rápido. En este ejemplo era de 14×24 y lógicamente 14 no es múltiplo de 8. cuanto mayor es el sprite más tarda. ¡Si invocas el comando sin parámetros es mucho mas rápido! ( ahorras 0.6 ms)
|COLAY vs CALL &XXXX2.4 vs 2.0Usar CALL como siempre es más rápido, pero menos legible.
GOSUB / RETURN0.56Aceptablemente rápido.  La medida la he hecho con una rutina que solo hace return.
|SETUPSP, id, param, valor2.7Aceptable, aunque POKE es mucho mejor para ciertos parámetros. Hay parámetros que se pueden establecer con POKE como el estado, pero otros no (como una ruta). Consulta la guía de referencia
FOR / NEXT0.6Lo puedes usar para recorrer varios enemigos y que cada uno se mueva de acuerdo a una misma regla. Debes valorar si puedes usar AUTOALL o MOVEALL para tus propósitos ya que en un solo comando moverías a todos los que quieras, lo cual es mucho mejor que un bucle.
|COLSP,0, @c%5.5Tarda lo mismo con independencia del número de sprites activos. Esta rutina la tendrás que invocar en cada ciclo de la lógica de tu juego, de modo que son casi 5ms que obligatoriamente tienes que destinar a esto. Si tienes una nave o personaje y varios disparos es mucho mas eficiente que invoques a COLSPALL en lugar de invocar varias veces a COLSP
|ANIMALL3.5Es costoso pero hay una forma de invocarlo conjuntamente al invocar |PRINTSPALL , mediante un parámetro que hace que se invoque a esta función antes de imprimir los sprites. Ello permite ahorrar la capa del BASIC, es decir lo que consume enviar el comando, que es >1ms. Por ello podemos decir que este comando consumirá normalmente algo menos de 2ms
|AUTOALL2.76No es costosa y puede mover a la vez los 32 sprites
|MOVERALL,1,13.4No es muy costosa y puede mover a la vez los 32 sprites
SOUND10El comando sound es “bloqueante” en cuanto se llena el buffer de 5 notas. Esto significa que tu lógica de BASIC no debe encadenar más de 5 comandos SOUND o se parará hasta que alguna nota termine..Si decides usarlo debe ser con sumo cuidado ya que consume mucho tiempo su ejecución (10 ms es muchísimo)
IF a>1 AND a>2 THEN a=2   Versus   IF a>1 THEN IF a>2 THEN a=22.52   Vs   2.39Una sencilla forma de ahorrar 0.13 ms   En cada cosa que programes ten en cuenta estos detalles, cada ahorro es importante
A=RND*104.2La función RND de BASIC es muy costosa. Puedes usarla, pero no en cada ciclo de juego sino solo eventualmente, por ejemplo, cuando aparezca un nuevo enemigo o cosas así. Otra solución sencilla es almacenar 10 números aleatorios en un array y utilizarlos en lugar de invocar a RND
Border <x>0.75Bastante rápida. Útil para usarla en combinación con algun tipo de colisión de sprites, reforzando el efecto explosivo
IF a AND 7 then 30   IF A MOD 8 then 30  1.19   1.29He puesto el tiempo de ejecución cuando se cumple la condición. Ambos casos son bastante rapidos.
Relación de tiempos de ejecución de algunas instrucciones

Recomendaciones importantes para el uso de funciones Basic

  • Usar DEFINT A-Z al principio del programa. El rendimiento mejorará muchísimo. Esto es casi obligatorio. Este comando borra las variables que existiesen antes y obliga a que todas las nuevas variables sean enteros a menos que se indique lo contrario con modificadores como “$” o “!” (Consulta la guía de referencia de programador BASIC de Amstrad). Ojo, en cuanto uses DEFINT, si quieres asociar un número mayor que 32768 tendrás que hacerlo en hexadecimal.
  • Si puedes evitar pasar por un IF insertando un GOTO, siempre será preferible
  • Cuando te falte velocidad y necesites un poquito más de rapidez utiliza CALL <dirección> y en lugar de RSX. En caso de hacer esto, has de pasar los parámetros que contengan números negativos en hexadecimal.
  • No sincronices el comando PRINTSPALL con el barrido de pantalla a menos que tu juego funcione muy rápido. Sincronizar puede reducir tus FPS. En general con que consigas 12 FPS tu juego será “jugable”.
  • Eliminar espacios en blanco. Cada espacio en blanco en tu listado BASIC consume 0.01ms en ejecución.
  • Una vez que hayas invocado con parámetros al comando STARS o al comando PRINTSPALL, o a COLAY o a otros comandos de 8BP, las siguientes veces no lo invoques con parámetros. La librería 8BP tiene “memoria” y usará los últimos parámetros que usaste. Esto ahorra milisegundos al atravesar la capa de análisis sintáctico del intérprete BASIC.
  • Ten siempre en cuenta que una expresión diferente de cero es TRUE. Esto te permitirá ahorrar 0.5ms en cada IF y lo puedes usar en la lectura de teclado y en el control de variables.
Mala opciónBuena opción (ahorras 0.5ms)
IF x<>0 THEN <instrucciones>IF x THEN <instrucciones>
IF x=20 THEN…10 If x-20 THEN 30 20 <instrucciones> 30
IF INKEY(34)=0 THEN <instrucciones>10 IF INKEY(34) THEN 30 20 <instrucciones> 30
  • En juegos de naves donde no uses sobreescritura, procura que tu nave sea el sprite 31, de este modo pasará por “encima” de los sprites que simulan ser el fondo, pues tu nave se imprimirá después.
  • Prueba versiones alternativas de una misma operación:

A=A+1:IF A>4 then A=0 : REM esto consume 2.6ms

A=A MOD 3 +1 : REM esto consume 1.84 ms

A=1 + A AND 3 : REM esto consume 1.6 ms

  • Evita el uso de coordenadas negativas. Ello te permitirá usar POKE para actualizar la posición de tu personaje. El comando POKE (el de BASIC) es muy veloz pero solo soporta números positivos, al igual que PEEK. En caso de usar coordenada nagtivas, usa |POKE y |PEEK (comandos de 8BP). Reserva el uso de |LOCATESP para cuando vayas a modificar ambas coordenadas a la vez y puedan ser positivas y/o negativas. Recuerda también que un POKE de un valor x negativo se puede hacer usando POKE dirección, 255+x+1. En caso de que quieras usar coordenadas negativas para que se vea como poco a poco los enemigos entran a la pantalla por el lateral izquierdo (que se perciba el clipping), puedes evitar las coordenadas negativas usando un SETLIMITS y de esa manera producir el mismo efecto con coordenadas que comienzan en cero y una pantalla de juego ligeramente mas pequeña
  • Si necesitas comprobar algo, no lo hagas en todos los ciclos de juego. A lo mejor basta que compruebes ese «algo» cada 2 o 3 ciclos, sin ser necesario que lo compruebes en cada ciclo. Para poder elegir cuando ejecutar algo, haz uso de la «aritmética modular». En BASIC dispones de la instrucción MOD que es una excelente herramienta. Por ejemplo para ejecutar una de cada 5 veces puedes hacer : IF ciclo MOD 5 = 0 THEN … aunque es mejor que uses operaciones AND que operaciones MOD
  • Haz uso de las «secuencias de muerte». Ello te permitirá ahorrar instrucciones para comprobar si un sprite que está explotando ha llegado a su último fotograma de animación para desactivarlo.
  • La sobreescritura es costosa: si puedes hacer tu juego sin sobreescritura ahorrarás milisegundos y ganaras colorido. Úsala cuando la necesites, pero no sin motivo
  • Las macrosecuencias de animación te ahorran líneas de BASIC ya que no necesitas chequear la dirección de movimiento del sprite. Úsalas siempre que puedas.
massive-logics-programacion-avanzada

Pero… si tenemos varias pantallas, ¿no seria mas eficiente contar con una lógica para controlarlas a todas? esto lo veremos en el siguiente articulo de 8 Bits de Poder. !Hasta la semana que viene!

¿De cuánta utilidad te ha parecido este contenido?

¡Haz clic en una estrella para puntuar!

Mostrar más

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Publicaciones relacionadas

Botón volver arriba