Diseñando un motor flexible basado en azulejos

8

Estoy tratando de crear un motor de juego flexible basado en fichas para hacer todo tipo de juegos de rompecabezas que no sean en tiempo real, como Bejeweled, Civilization, Sokoban, etc.

El primer enfoque que tuve fue tener una matriz 2D de objetos Tile, y luego tener clases heredadas de Tile que representaran los objetos del juego. Desafortunadamente de esa manera no pude apilar más elementos del juego en el mismo mosaico sin tener una matriz 3D.

Luego hice algo diferente: todavía tenía la matriz 2D de objetos Tile, pero cada objeto Tile contenía una Lista donde puse y diferentes entidades. Esto funcionó bien hasta hace 20 minutos, cuando me di cuenta de que es demasiado costoso hacer muchas cosas, mira este ejemplo:

Tengo una entidad de pared. Cada actualización tengo que verificar los 8 mosaicos adyacentes, luego verificar todas las entidades en la Lista de mosaicos, verificar si alguna de esas entidades es un Muro, y finalmente dibujar el sprite correcto. (Esto se hace para dibujar muros que están uno al lado del otro sin problemas)

La única solución que veo ahora es tener una matriz 3D, con muchas capas, que pueda adaptarse a cada situación. Pero de esa manera no puedo apilar dos entidades que comparten la misma capa en el mismo mosaico. Cada vez que quiero hacer eso tengo que crear una nueva capa.

¿Hay una mejor solución? ¿Qué harías?

Vee
fuente
Cómo usar un arreglo 3D solucionaría su problema con esa situación de verificación de pared. ¿No sería igual?
Michael Coleman
Sabría que los Muros solo permanecen en la capa número 1. Así que puedo hacer: Mosaicos [Wall.X - 1, Wall.Y, 1] es Wall?
Vee
Entonces, ¿no puede simplemente verificar el primer elemento de la lista? No veo cómo una lista plantea un problema.
Michael Coleman
El muro puede estar en cualquier lugar de la lista con el segundo enfoque. Necesito recorrer cada Entidad y verificar si es un muro.
Vee
44
¿Realmente tuvo un problema de rendimiento o simplemente le preocupa que lo haga? ¿Lo has perfilado?
munificent

Respuestas:

2

¿Realmente ve este problema, o ha pensado simplemente para arriba? Porque no veo cómo iterar sobre la lista de objetos tiene un efecto notable en el rendimiento. Tendría que tener cientos de objetos por ficha para que esto sea importante. La mayoría de los juegos que se me ocurren tienen 10, como máximo.

Si realmente tiene un problema, la mejor idea es almacenar en caché los datos, no cambiar la representación. En su ejemplo, la configuración de la pared probablemente no cambia cada cuadro. Simplemente guarde en caché el tipo de sprite correcto, no lo recalcule constantemente.

Si quieres crear un juego loco que tenga muchos objetos y cambien todo el tiempo, puedes crear diferentes capas con diferentes semánticas. Por ejemplo, la capa de pared contiene solo paredes, no más de 1 por mosaico y, por ejemplo, la capa de decoración contiene listas de objetos que nunca cambian.

Finalmente, si quieres tener una arquitectura ultra flexible que sea ideal para todos los juegos posibles, mala suerte, no existe tal cosa.

No importa
fuente
2

Dos sugerencias Primero, debes resolver el sprite con el que se dibujará cada ficha durante la carga de tu mapa / nivel de ficha. Debería ser bastante fácil atravesar la matriz 2D en la que está cargado su nivel y decidir que un muro certian debe tener forma de L y otro debe ser un | forma.

En segundo lugar, almacenaría datos estáticos relacionados con un mosaico en un lugar diferente al de los datos activos. Por lo tanto, un objeto de mosaico puede tener una lista de objetos que van y vienen del mosaico, pero no es necesario recorrerlo para ver si, por ejemplo, solo queremos saber si el mosaico es transitable o no.

Este psudocódigo es más o menos cómo me he acercado a un sistema basado en mosaicos.

Tile {
  Texture tex;
  //...other static data...
  List currentObjects;
}

Tile t = Tiles[x][y];

Esto supone que hay fichas estáticas en su juego, pero esa es una suposición bastante buena en muchos juegos basados ​​en fichas en las que se trata de paredes y similares.

Nick Van Brunt
fuente
1

Tu dijiste:

Tenía que tener una matriz 2D de objetos Tile, y luego tener clases heredadas de Tile que representaran los objetos del juego. Desafortunadamente de esa manera no pude apilar más elementos del juego en el mismo mosaico sin tener una matriz 3D.

Luego hice algo diferente: todavía tenía la matriz 2D de objetos Tile, pero cada objeto Tile contenía una Lista donde puse y diferentes entidades. Esto funcionó bien hasta hace 20 minutos, cuando me di cuenta de que es demasiado caro hacer muchas cosas.

Esta es exactamente la situación que desea resolver utilizando el patrón MVC (controlador de vista de modelo) para separar su modelo de su vista. MVC hace que este problema tenga dos partes:

  • Cómo modelar una pila de fichas
  • ¿Cómo mostrar una pila de mosaicos?

En lugar de tener una matriz 2D de mosaicos, o una matriz 2D de una lista de mosaicos, su modelo almacenaría una matriz 2D de objetos de juego. Luego, tu matriz 2D de clases de mosaico mirará los objetos de juego correspondientes y decidirá cómo representarlos.

En lugar de tener una sola instancia de Tile que represente un solo objeto de juego, puede hacer que una única instancia de Tile represente algo que se parece a una pila de objetos. Esto sería más eficiente y le daría una separación clara de su lógica de código central y cómo se ve.

Al igual que las paredes. La lógica es muy trivial, pero la representación es más compleja.

cenizas999
fuente
3
No veo cómo MVC (que no es común en los juegos desde mi experiencia) ayudaría aquí. En todo caso, lo empeoraría: terminaría con cierta redundancia entre el modelo y la vista que debe mantenerse sincronizada.
munificent
1
Sí, cuando tienes un problema, solo usa <buzzword>. Ahora tienes dos problemas.
importa
1
np, enjuagué más detalles.
cenizas999
OK, retomé mi voto negativo. Aún así, no creo que MVC sea útil aquí. La mayoría de las veces, los objetos difieren por lógica, no (solo) por representación gráfica.
importa
No puedo complacer a todos, supongo. Para mí, MVC saltó inmediatamente como parte de la solución a parte del problema. Pero estoy de acuerdo en que no estoy proporcionando una solución completa a todos los problemas, ¡eso requeriría un ensayo!
cenizas999
0

Estoy de acuerdo con @Omnion. Use el enfoque de lista, pero manténgalo ordenado si el rendimiento es un problema. Es decir, usar un enfoque híbrido. Use la lista como su tercera dimensión, pero solo identifique los primeros 5 elementos como un tipo específico, y cualquier cosa después de eso es un tipo propio de tinta, o un tipo que no requerirá verificar varias veces por cuadro.

Nate
fuente
0

"Nunca" debe hacer clases separadas para cosas como fichas. Debe hacer una estructura a partir de bytes, cortos e ints que se refieren al tipo de mosaico y objetos en él. Los ints de rendimiento inteligente son los mejores para usar o incluso largos en sistemas de 64 bits. Pero si alguna vez desea guardar su mapa, debe ir con bytes o cortos siempre que pueda, especialmente para mapas enormes.

En mis juegos tengo una clase de mapa que solo tiene campos como: byte tileType; 256 tipos de terreno diferentes son suficientes para este juego. Toma 1 byte. ushort tileObject; 65,536 objetos diferentes es suficiente para ir. Toma 2 bytes, etc.

Si tomo el ejemplo anterior, cada mosaico ocupa solo 3 bytes en la memoria. Por lo tanto, un 10000x10000 tendría 300 MB de memoria y no debería ser un problema. Cada vez que necesite buscar algo, determinará en qué parte del mapa desea buscar y lo que está buscando e iterará las matrices en consecuencia.

Si tienes cosas dinámicas en todo el mapa, pregúntate si el jugador realmente nota esas cosas lejos de su ventana gráfica.

Madmenyo
fuente