Isshokuta; 20160531

Le estoy pegando mucha pensada al tema de cómo lanzar (= crear, no os flipéis) los enemigos, porque hay muchas opciones y combinaciones de estas y no soy capaz de decidirme por una. Sé que el principal problema de no tener las ideas claras es porque hace mucho tiempo que no juego a mierda de esta. Creo que lo suyo es que me descargue una colección de juegos de estos y vea como se comportan. Por ejemplo, podría hacer una primera batería de reconocimiento con:

  • Hokuto no ken (NES)
  • Hokuto no ken (MD)
  • Spartan X (NES)
  • Dragon Ninja (NES)

Voy a copiar lo que he puesto en el foro de mojonia sobre el tema, que es básicamente una descripción de tres posibles soluciones:

  • La opción principal era tener la “tira de enemigos”. Lo llamo así porque es una “tira” asociada al mapa, en la que los enemigos se ordenan en orden ascendente por su coordenada X de aparición. Cuando el mapa scrollée y dicha coordenada entre en juego, el enemigo se crea. Esto es muy sencillo de hacer, pero tiene el inconveniente de que siempre podemos ir avanzando pixel a pixel tiqui tiqui tiqui y parar en cuanto veamos que sale un enemigo, esperarlo, hacer por matarlo sin avanzar, y a por el siguiente. Eso se carga absolutamente todo el gameplay, y es algo que quiero evitar.
  • Otra opción es la opción kung fu master de enemigos ladilla. Mientras haya slots libres para enemigos activos, se van tirando enemigos. Se puede dividir el mapa en zonas para saber qué tipos de enemigos tirar, con cierta distribución pseudo-aleatoria. Se puede refinar para que los enemigos salgan en tandas y hasta que no te cargues una tanda no salga la otra, o espaciar entre tanda y tanda, y cosas por el estilo.
  • La tercera opción es ir parando. El motor te deja avanzar hasta X, fija el scroll, y te saca una serie de enemigos. Cuando te los cargas, se libera el scroll y puedes avanzar otro rato. Las zonas se podrían hacer más o menos largas para meter partes en las que haya que avanzar un rato saltando agujeros y cosas así, y luego parar y tirar enemigos. Así funciona Golden Axe, por poner un ejemplo.

Creo que la elección tipo “kung fu master de enemigos ladilla” por zonas podría molar. Sería más o menos una solución híbrida:

  • Se divide el mapa por zonas. Hay zonas activas y zonas de descanso.
  • En una zona activa, se lanza una horda (hasta tres enemigos, o cuatro, si el motor los aguanta, que lo quiero probar).
  • Hay un flag: si está activo, los enemigos se renuevan tan pronto son eliminados con uno del mismo tipo. Si no, los enemigos se vuelven a lanzar de nuevo cuando todos hayan muerto. En ambos casos, hay un delay (configurable y levemente variable) antes de que esto pase.
  • Esto se acaba cuando el jugador avanza lo suficiente como para salir de una zona.
  • Si se entra en otra zona activa con enemigos activos, se espera a que se mueran todos.

Voy a consultarlo con la almohada, y con los chicos.

En el caso de hacerlo, necesito:

  • Un array de offsets con los inicios de cada zona.
  • Configuración de la zona, el tiempo que se tarda en lanzar nuevos enemigos / hordas, si se renuevan los enemigos inmediatamente o sólo la horda completa, si la horda se crea del tirón o se retrasa la creación de cada enemigo…
  • Una definición de lo que ocurre en cada zona. Esto lo tengo que diseñar. Permitir cierto número de hordas diferentes, y una descripción de como es cada horda. Tengo que trabajar en esto.
  • Control sobre la horda activa. Cuántos personajes quedan.

Eso de arriba hay que, como siempre, codificarlo de la forma más compacta y eficiente posible, así que lo suyo es encontrar el modelo de datos ideal.

enemy_spawner (zona) {
	hay que crear un enemigo? 
		- Crear un enemigo.
}

Qué fácil parece ¿verdad?

¿Cuándo hay que crear un enemigo?

  • Cuando haya pasado X tiempo desde que murió el que quiero sustituir, o cuando todos estén muertos y haya pasado X tiempo desde que murió el último, según flag.

Debo extender eso a la situación inicial, que sería algo así como:

  • Cuando estemos en la situación inicial, creamos un nuevo enemigo del tipo correspondiente cuando haya pasado N segundos desde que se creó el anterior, hasta haberlos creado todos. N puede valer 0, claro.

Creo que lo suyo es que las hordas se compongan de un grupo preestablecido de enemigos 100% determinista, o sea, que sean los que yo programe. A lo mejor se podría ir alternando entre diferentes hordas, aunque a lo mejor eso es liar un poco la marrana.

Voy a ver qué pasa si meto cuatro enemigos !

~~~~

Va bien, pero se me salta frame si trato de crear más de uno en el mismo cuadro.

OBVIAMENTE no voy a crear los enemigos en los cuadros en los que haya scroll… De hecho, esta es una de las cosas que podría no, debería meter en los frames en los que no se escrollea, antes del split.

Debo crear un enemigo cada frame cuando ese frame no haya scrolleado. Espero que esto no sea muy obvio.

[[[ También debo asegurar que no se llama al render justo después de crear a los enemigos. Debe haberse procesado al menos un cuadro de los mismos en enems_do ]]] – Esto es relativamente fácil si los creo antes del split, por el orden en el que se ejecutan las diferentes tareas.

Voy a intentar optimizar de todos modos la creación de los nuevos enemigos.

Debería poder eliminar ciertas cosas de enems_create_new para hacerlo más liviano:

  • enframebase.
  • enstate -> se pone a idle al eliminarlos, y se puede inicializar a idle también.

Y creo que ya. JODO. Pero algo es algo.

Luego tengo dos desplazamientos de 5 espacios, y eso puede ser lento. ¿Y si precalculo los offsets de las partes de arriba y abajo? Del tirón. A meterlo en precalcs…

Coño, pues sí que ha mejorado. Ahora no se salta el frame en el cuadro en el que se crea dos enemigos. Se ve que los shifts son algo a evitar cuando las posibilidades son pocas, como es el caso: se usaban sobre los identificadores de torso y piernas para multiplicarlos por 32 y seleccionar el cell base correcto. Los he sustituido por una lectura a un array precalculado con los valores 0, 32, y 64. Si necesito más, amplío el array.

Hablando de optimizaciones, tengo otros dos << 5 en las funciones p_closer_than y p_further_than que, salvo fallos, sólo deberían presentar valores de 0 a 8. Me voy a hacer un array general para este tipo de shifts…

~~~~

Voy a hacer un cálculo de estrés, a ver el número máximo de sprites que puedo tener, y si es necesario modificar neslib para detectar y limitar a 64 en oam_meta_spr

  • Frame de prota con más sprites: 11
  • Piernas con más sprites: 6
  • Torso con más sprites: 5
  • por tanto, enemigo con más sprites: 11

Tendríamos 5 * 11 = 55 sprites, con el sprite 0 = 56, lo que deja 8 proyectiles.

¡Phew, creo que así no me voy a pasar nunca!

Aunque podría hacer un limitador de sprites en oam_meta_spr y meter más malos, pero ¿es necesario?

~~~~

He pensado que las hordas pueden ser una lista de definiciones de X enemigos, y un puntero que se va incrementando cada vez que hace falta crear un nuevo enemigo. Si el puntero lee un “0”, vuelve al principio de la lista de definiciones.

Esto me vale para los dos supuestos: el de crear de uno en uno, o el de crear hordas, y me sirve para ir alternando y rotando de formas chulas.

La lista de zonas debería contener la “X” donde empieza (expresada en columnas, es tontería hilar más fino, y me vale usar un entero), un puntero a su horda (o 0 si no salen enemigos), y un byte de flags con cosas.

En una lista de enemigos, los enemigos deben almacenar (repito y refino):

  • ID de piernas
  • ID de torso
  • Sale por la izquierda o por la derecha
  • Sale en el nivel superior o en el nivel inferior
  • Qué IA lleva
SNPPPTTT xxIIIIII
  • PPP es ID de piernas (0 – 7)
  • TTT es ID de torso (0 – 7)
  • S == 0, izquierda, == 1, derecha
  • N == 0, arriba, == 1, abajo
  • xx, reservado por el momento.
  • IIIIII es el ID del comportamiento (0 – 63).

Si encuentro algo que meter en “xx” y si necesito más bits, afeitaré un poco el ID de comportamiento. No creo que necesite ¡¡64!! comportamientos diferentes, ¿no?

	// Algo así
	rda = *gp_gen ++;
	rdb = rda & 0x07;
	rdc = (rda >> 3) & 0x07;
	rdxx = (rda & 0x80) ? (cam_pos + 256+12) : (cam_pos - 12);
	rdy = (rda & 0x40) ? 207 : 127;
	rdt = *gp_gen ++;
	enems_create_new ();
	if (! (*gp_gen)) // reset pointer

Sería eso cada vez que hubiese que crear un nuevo enemigo de la tira.
Necesito una leche de variables nuevas con persistencia, así que debería pegar un par de laneos a ver qué variables puedo mover a bss, que tengo casi todo en zp.

Cada vez que entre un nuevo tile en el mapa debo comprobar si debo entrar en una zona nueva. Necesito un puntero a la zona actual. Si la zona actual tiene una X mayor o igual que el número de la columna que acaba de entrar, cambio los valores de creación y reinicio algunas variables.

En cada cuadro, si no hubo scroll, debo ver si tengo que crear algún enemigo, crearlo, y volver. Sólo uno por frame. Tengo que crear un nuevo enemigo si:

  • El contador del tiempo de espera vale 0
  • Si el flag de “spawn_enable” está a 1
  • Si hay menos de “spawn_max” enemigos

Considerando que:

  • El contador de tiempo de espera se resetea al morir un enemigo si el contador es igual a 0.
  • El contador de tiempo decrece en cada frame si es diferente de 0.
  • Si estamos en modo individual, spawn_enable vale 1 siempre.
  • Si estamos en modo grupo, spawn_enable se pone a 0 al crear un nuevo enemigo, y sólo se pone a 1 si el número de enemigos activos es 0.

Ahorrando, y refinando, creamos un enemigo nuevo si:

spawn_timer == 0 && (spawn_type == ST_INDIVIDUAL || enems_active == 0)

Anda, mira qué bonico.

Voy a descansar y reposar un ratow.

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