PongPong; 20160203

He perdido algo de tiempo (más del que pensaba, la verdad) en hacer un split del scroll. También he medido cuánta frame me queda y me queda un pasote, así que por ahora va guay todo.

Tengo que pensar en los enemigos. Varias cosas así, al batiburral:

  • Of course que los lineales van dentro.
  • Dejamos espacio para las plataformas móviles pero por ahora no.
  • Sea lo que sea, no deben poder salir de la “pantalla” en la que están creados, o me jode el invento. Limitar por LSB a 0..240
  • Implementar motor de disparos sencillo.
  • Punto fijo para las cosas que quiera hacer, supongo.
  • El enemigo de trayectorias random durante X frames es una mierda pero puede funcionar.
  • Por ahora mejor paso de programar cosas que precisen colisión con el escenario.
  • Sigue buscando tu regalo.
  • Tortilla.
  • Mierda, los necesito con colisión. Quiero hacer que la tía del culo pegue saltos.

Bueno, antes de seguir voy a ver cómo se manejaba Sir Ababol con los enemigos y a escribir aquí todos los datos que encuentre, porque seguro que encuentro una forma mejor de almacenar los enemigos en ROM y copiarlos a RAM para las pantallas actuales.

El juego original copia todos los enemigos de la tira actual. Estamos hablando de 48 enemigos, que con nada que necesiten ya hacen que la memoria necesaria escale. Por suerte no tengo más buffers.

Imaginando que necesito, así, a voleo, x, y, mx, my, x1, y1, x2, y2 – esto ya son 8 bytes, o 384 para toda la tira (nivel). Lo bueno es que con esos 8 bytes ya tengo para todo.

Creo que el juego original usaba valores de 16 bits para las X de los enenmigos, pero teniendo en cuenta que los enemigos no pueden cambiar de pantalla lo veo un gasto inútil. Por supuesto, era más fácil comparar X con CamX para saber si el enemigo estaba en la pantalla, pero no pasa nada por complicar esto un poco más. Veamos:

Tenía que ser CamX <= X < CamX + 256

Teniendo en cuenta que “X” era en realidad (n_pant << 8) + x … tengo que coger papel y lápiz, porque la solución trivial (enX = (n_pant << 8) + x) no me gusta mucho. Pero antes voy a ver cómo se hacía todo antes y lo voy documentando aquí.

  • Tengo una array sprite_assign de 8 espacios que inicializo a 0xff
  • La función activate_enemy activa el enemigo número t. Lo que hace es buscar un espacio libre (0xff) en sprite_assign y asigna t a ese espacio. Luego asigna el índice de ese espacio al array enem_spr, sólo si el sprite no estaba ya activo (enem_spr [t] != 0xff). La búsqueda de huecos libres en sprite_assign se hace sólo sobre los seis primeros espacios.
  • La función deactivate_enemy hace exactamente lo contrario: pone un 0xff en enem_spr [t] y un 0xff en el hueco que ocupaba el sprite en sprite_assign (el valor original en denem_spr [t]).
  • La función assign_enemies es la que maneja todo el cotarro. Se encarga de definir qué enemigos deberían verse en la pantalla y cuáles no. Para ello crea o elimina enemigos al vuelo.

La función assign_enemies

Tiene varias partes:

  • La primera parte recorre los 6 enemigos referenciados en las primeras seis posiciones de sprite_assign y comprueba que no se hayan salido de la pantalla mirando cam_pos y su coordenada X. En efecto, las coordenadas X son enteros absolutos. En el caso de salirse de la pantalla se los carga con deactivate_enemy.
  • La segunda parte es la que crea sprites nuevos, de ser necesario. Para ello primero hace una primera criba muy inteligente de los 48 posibles candidatos: sólo podrá haber seis visibles a lo más: los que estén en las “pantallas” MSB (cam_pos) y siguiente. Como los enemigos están ordenados por pantallas, sólo hay que multiplicar cam_pos * 3 y procesar los seis enemigos a partir de ahí en la tabla de enemigos. Para cada uno de ellos, se compara X con cam_pos y se llama a activate_enemy si quedan dentro del área visible.
  • La tercera parte crea los sprites en la OAM para los seis enemigos potencialmente visibles. Como ahora mis metasprites tienen tamaños variables, tendré que modificar levemente esta parte.

Me gustaría ahorrarme todas las matemáticas de 16 bits implicando la posición X de los enemigos en el motor para ganar ciclos. Lo que pasa es que esto complica la comparación tan sencilla que se hacía originalmente con cam_pos.

Estoy seguro de que el cansancio me está impidiendo ver la solución probablemente trivial al problema. La requete-lame es coger y precalcular el valor de 16 bits o coordenada X absoluta a partir de la relativa…

Si “t” es el índice del enemigo, t / 3 es el número de pantalla que ocupa. Por tanto, su coordenada X absoluta es ((t / 3) << 8) + Xr, siendo Xr la coordenada X realtiva dentro de la pantalla.

De ahí arriba lo que no mola es dividir entre tres. Cosas de tener 3 enemigos por pantalla. Dejar espacio para 4 puede ser overkill con tan poca RAM aunque simplicaría los calculos.

No sé qué hacer. Voy a desconectar un rato, que siempre vienen las soluciones del tirona (¿un lookup table de 48 posiciones? 0, 0, 0, 1, 1, 1, 2, 2, 2 …)

~~

Leyendo por ahí y pensando un poco me doy cuenta de que lo mejor es dejar la coordenada X “absoluta” de 16 bits. Lo que sí puedo hacer, en la rutina que mueve a los enemigos, es quedarme con el LSB y utilizar sólo eso para todos los cálculos, para luego volver a colocarle el MSB. Clever. Y si uso *(&(x[t]) + 1) lo puedo manipular directamente, aunque eso es ya un poco heavy peavy y no ahorraría gran cosa en ciclos y tal. Tan fácil como sacar un xr = LSB (x[t]) al principio de cada tile y luego hacer un *(x + 1 + t + t) = xr al final del bucle (actualiza el LSB directamente en la zona de RAM que ocupa el array).

Creo que esta será la dirección que tome. Ahora voy a analizar el formato de los datos en ROM a ver si puedo ahorrar algo. En MK1 NES, última versión estoy almacenando en ROM los valores T XY1 XY2 y P, con T codificando tipo/comportamiento, XY1 y XY2 coordenadas a nivel de metatile, y P las propiedades (por ejemplo, la velocidad).

Por cierto, teniendo 12 gráficos de enemigos diferentes sólo necesito hacer esto:

T = CBBBAAAA

Donde AAAA es el # de gráfico, BBB describe el tipo de movimiento (quitando los lineares me quedan 6 más, el 0 no cuenta) y C vale 1 si el enemigo dispara proyectiles (lo dejo por ahora es estado “reservado y ya lo pondré si cabe”). De hacerlo, el sistema de proyectiles debe ser global y utilizable también por el jugador.

~~

Sigo apuntando cosas que luego me darán que pensar: como el escenario que se ve es mayor que “el que existe”, voy a considerar que las pantallas son en realidad de 15 tiles de alto, solo que empiezan en el tile Y=2. Todo esto quedará reflejado en las funciones que cree para la colisión, que tendrán en cuenta el offset al leer del mapa y rellenarán con el valor de mapY=0 en las posiciones que se salgan.

Tendré que tener todo esto en cuenta para dejarlo ya pre-ajustado en el conversor de enemigos, porque cuantos más cálculos me ahorre en la ROM, mejor.

~~

Voy a colocar unos enemigos de prueba (lineales) y a escribir el conversor. Voy a hacer una pequeña modificación a col_incog.exe para que permita meter números en HEX, que vendrá bien, pero por ahora no será necesario, ya que los enemigos de prueba tienen CBBB = 1 (no disparan y llevan el tipo de movimiento 1), así que serán 16 + ID del gráfico, fácil de calcular al vuelow.

Hablando de ahorros: 3 tiras de 16 pantallas (48 en total) en el original ocupaban 1296 bytes. Ahora, si los cálculos no me fallan, ocuparán 4*16*3*4 = 768 bytes, lo que está bastante mejor – siempre que el código de “expandir” esos datos mientras se copian a RAM no ocupe demasiado, claro.

~~

Algunos enemigos añadidos y conversor modificado con offsets y para no generar hotspots. Voy a programar el siguiente hito:

Hito #3: Enemigos

  • Programar al rutina de copia/expansión de ROM a RAM para el nivel actual.
  • Programar la rutina de movimiento preparada para varios tipos, pero implementando solo el tipo 1 (lineal).
  • Integrar con el sistema de sprites.

~~

Mientras estoy en ello, tengo que pensar algo de una vez para las velocidades de los enemigos que no sea un pifostio y me permita tener actualizaciones cada 2 o cada 4 frames y cosas así…

Veamos. Establezcamos estas velocidades:

1/8 1/4 1/2 0 1 2 4.

Tengo que codificar las 1/X de alguna manera que me permita manejar todo esto de una forma buena. Por un lado, 1/X hace que se actualice cuando (frame_counter & (X-1)) == 0

Puedo codificarlos como 0x80 + N en el propio colocador. Voy a poner uno y así pruebo.

Las velocidades son, pues:

8    8 pixels por frame
4    4 pixels por frame
2    2 pixels por frame
1    1 pixel por frame
129  1 pixel cada 2 frames (129 - 128 = 1)
131  1 pixel cada 4 frames (131 - 128 = 3)
135  1 pixel cada 8 frames (135 - 128 = 7)

~~

Ahí estamos – hay cosas raras, pero ahí estamos :-P. Los incrementos fraccionales funcionan, pero creo que me estoy haciendo la picha un lío con el tema de la coordenada relativa temporal del enemigo para los cálculos en el eje X y su actualización. Pero ahí estamos, casi casi. Pero tengo menos de 15 minutos para irme, así que…

–> lo tengo aislado. Esto es lo que lo está peyendo todo

*(en_x + gpit + gpit + 1) = en_rx;

Me estoy cargando el array. Algo falla. Bocadillo de caballa. Pero ¿qué? No lo sé. Ya averiguaré que falla (¿Pensaba que los números en este CPU eran MSB LSB?). Por ahora hago esto, y va:

en_x [gpit] = en_x [gpit] & 0xff00 | en_rx;

Pasemos a otro tema. Voy a ver cómo vamos de tiempo de frame y meto la rutina básica de plataformeo para el personaje principal.

~~

Hito #4: Personaje principal

La rutina que empleaba en Sir Ababol era la de “la vieja escuela”. Paso, prefiero la colision liviana por puntos, que además voy a optimizar haciendo una rutina de ATTR que pille los dos puntos indicados de forma específica y así ahorrarme todo el paso de parámetros (y muchos ciclos).

Lo haré además de forma que los distintos ejes se puedan cambiar al vuelo para poder hacer el truco y generar, con la misma ROM, varios posibles juegos diferentes.

Lo primero que tengo que hacer es sacar la fórmula para el attr, dados x e y en coordenadas de tile. Teniendo en cuenta que el mapa se divide en “chunks”, tenemos que:

01 0A
02 0B
03 0C
04 0D
05 0E
06 0F
07 10
08 11
09 12

Sería (x >> 1) * 25 + (x & 1) * 10 + y. Urm. Muchas multipicaciones feas…

rda = (x >> 1);
gp_gen = map_ptr + 
	(rda << 4) + (rda << 3) + rda + 
	((x & 1) ? 10 : 0) + 
	y;

Yuh, overkill, pero asín es esto. Lo que gano en el scroller lo pierdo aquí. Qué se le va a hacer. Para otro juego lo hago de otra forma y no intercalo tiles y atributos, y me busco números más amigos de las potencias de dos. Por ahora, vamos a tirar con esto.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google photo

Estás comentando usando tu cuenta de Google. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s