¿Cuál es una buena forma de almacenar datos de mapa de mosaico?


12

Estoy desarrollando un juego de plataformas en 2D con algunos amigos. Lo hemos basado en el XNA Platformer Starter Kit que utiliza archivos .txt para almacenar el mapa de mosaico. Si bien esto es simple, no nos da suficiente control y flexibilidad con el diseño de nivel. Algunos ejemplos: para múltiples capas de contenido se requieren múltiples archivos, cada objeto se fija en la cuadrícula, no permite la rotación de objetos, un número limitado de caracteres, etc. Por lo tanto, estoy investigando cómo almacenar los datos de nivel y archivo de mapa.

Esto se refiere solo al almacenamiento del sistema de archivos de los mapas en mosaico, no a la estructura de datos que utilizará el juego mientras se está ejecutando. El mapa de mosaico se carga en una matriz 2D, por lo que esta pregunta es acerca de qué fuente llenar la matriz.

Razonamiento para DB: Desde mi perspectiva, veo menos redundancia de datos usando una base de datos para almacenar los datos del mosaico. Las fichas en la misma posición x, y con las mismas características se pueden reutilizar de un nivel a otro. Parece que sería bastante simple escribir un método para recuperar todos los mosaicos que se utilizan en un nivel particular de la base de datos.

Razonamiento para JSON / XML: archivos editables visualmente, los cambios pueden rastrearse a través de SVN mucho más fácilmente. Pero hay contenido repetido.

¿Alguno tiene inconvenientes (tiempos de carga, tiempos de acceso, memoria, etc.) en comparación con el otro? ¿Y qué se usa comúnmente en la industria?

Actualmente el archivo se ve así:

....................
....................
....................
....................
....................
....................
....................
.........GGG........
.........###........
....................
....GGG.......GGG...
....###.......###...
....................
.1................X.
####################

1 - Punto de inicio del jugador, X - Salida de nivel,. - Espacio vacío, # - Plataforma, G - Gema


2
¿Qué "formato" existente estás usando? Solo decir "archivos de texto" solo significa que no está guardando datos binarios. Cuando dice "no hay suficiente control y flexibilidad", ¿cuáles son los problemas específicos con los que se encuentra? ¿Por qué la separación entre XML y SQLite? Eso determinará la respuesta. blog.stackoverflow.com/2011/08/gorilla-vs-shark
Tetrad

Optaría por JSON ya que es más legible.
Den

3
¿Por qué la gente piensa en usar SQLite para estas cosas? Es una base de datos relacional ; ¿Por qué la gente piensa que una base de datos relacional tiene un buen formato de archivo?
Nicol Bolas

1
@StephenTierney: ¿Por qué esta pregunta pasó repentinamente de ser XML vs. SQLite a JSON vs. cualquier tipo de base de datos? Mi pregunta es específicamente por qué no es solo: "¿Cuál es una buena manera de almacenar datos de mapa de mosaico? Esta pregunta X vs. Y es arbitraria y sin sentido.
Nicol Bolas

1
@Kylotan: Pero no funciona muy bien. Es increíblemente difícil de editar, porque solo puedes editar la base de datos a través de comandos SQL. Es difícil de leer, ya que las tablas no son realmente una forma efectiva de mirar un mapa de mosaico y comprender lo que está sucediendo. Y aunque puede hacer búsquedas, el procedimiento de búsqueda es increíblemente complicado. Hacer que algo funcione es importante, pero si va a hacer que el proceso de desarrollar el juego sea mucho más difícil, entonces no vale la pena tomar el camino corto.
Nicol Bolas

Respuestas:


13

Entonces, al leer su pregunta actualizada, parece que está más preocupado por los "datos redundantes" en el disco, con algunas preocupaciones secundarias sobre los tiempos de carga y lo que utiliza la industria.

En primer lugar, ¿por qué le preocupan los datos redundantes? Incluso para un formato hinchado como XML, sería bastante trivial implementar la compresión y reducir el tamaño de sus niveles. Es más probable que ocupes espacio con texturas o sonidos que tus datos de nivel.

En segundo lugar, es más probable que un formato binario se cargue más rápido que uno basado en texto que debe analizar. Doblemente, si puedes dejarlo en la memoria y tener tus estructuras de datos justo allí. Sin embargo, hay algunas desventajas en eso. Por un lado, los archivos binarios son casi imposibles de depurar (especialmente para las personas de creación de contenido). No puedes diferenciarlos o versionarlos. Pero serán más pequeños y se cargarán más rápido.

Lo que hacen algunos motores (y lo que considero una situación ideal) es implementar dos rutas de carga. Para el desarrollo, utiliza un formato basado en texto de algún tipo. Realmente no importa qué formato específico use, siempre que use una biblioteca que sea sólida. Para el lanzamiento, cambia a cargar una versión binaria (una carga más pequeña, más rápida, posiblemente despojada de entidades de depuración). Sus herramientas para editar los niveles escupen ambos. Es mucho más trabajo que un solo formato de archivo, pero puedes obtener lo mejor de ambos mundos.

Dicho todo esto, creo que estás saltando un poco el arma.

El primer paso para estos problemas es siempre describir el problema con el que se está ejecutando a fondo. Si sabe qué datos necesita, entonces sabe qué datos necesita almacenar. A partir de ahí es una pregunta de optimización comprobable.

Si tiene un caso de uso para probar, puede probar algunos métodos diferentes de almacenamiento / carga de datos para medir lo que considere importante (tiempo de carga, uso de memoria, tamaño de disco, etc.). Sin saber nada de eso, es difícil ser más específico.


8
  • XML: fácil de editar a mano, pero una vez que comienza a cargar muchos datos, se ralentiza.
  • SQLite: Esto puede ser mejor si desea recuperar muchos datos a la vez o incluso solo pequeños fragmentos. Sin embargo, a menos que esté usando esto en otra parte de su juego, creo que es excesivo para sus mapas (y probablemente sea demasiado complicado).

Mi recomendación es usar un formato de archivo binario personalizado. Con mi juego, solo tengo un SaveMapmétodo que pasa y guarda cada campo usando a BinaryWriter. Esto también le permite elegir comprimirlo si lo desea y le da más control sobre el tamaño del archivo. es decir, guarde un en shortlugar de un intsi sabe que no será mayor que 32767. En un bucle grande, guardar algo como un en shortlugar de un intpuede significar un tamaño de archivo mucho más pequeño.

Además, si sigue esta ruta, le recomiendo que su primera variable en el archivo sea un número de versión.

Considere, por ejemplo, una clase de mapa (muy simplificada):

class Map {
    private const short MapVersion = 1;
    public string Name { get; set; }

    public void SaveMap(string filename) {
        //set up binary writer
        bw.Write(MapVersion);
        bw.Write(Name);
        //close/dispose binary writer
    }
    public void LoadMap(string filename) {
        //set up binary reader
        short mapVersion = br.ReadInt16();
        Name = br.ReadString();
        //close/dispose binary reader
    }
}

Ahora, supongamos que desea agregar una nueva propiedad a su mapa, digamos a Listde Platformobjetos. Elegí esto porque es un poco más complicado.

En primer lugar, incrementa MapVersiony agrega List:

private const short MapVersion = 2;
public string Name { get; set; }
public List<Platform> Platforms { get; set; }

Luego, actualice el método de guardar:

public void SaveMap(string filename) {
    //set up binary writer
    bw.Write(MapVersion);
    bw.Write(Name);
    //Save the count for loading later
    bw.Write(Platforms.Count);
    foreach(Platform plat in Platforms) {
        //For simplicities sake, I assume Platform has it's own
        // method to write itself to a file.
        plat.Write(bw);
    }
    //close/dispose binary writer
}

Luego, y aquí es donde realmente ve el beneficio, actualice el método de carga:

public void LoadMap(string filename) {
    //set up binary reader
    short mapVersion = br.ReadInt16();
    Name = br.ReadString();
    //Create our platforms list
    Platforms = new List<Platform>();
    if (mapVersion >= 2) {
        //Version is OK, let's load the Platforms
        int mapCount = br.ReadInt32();
        for (int i = 0; i < mapCount; i++) {
            //Again, I'm going to assume platform has a static Read
            //  method that returns a Platform object
            Platforms.Add(Platform.Read(br));
        }
    } else {
        //If it's an older version, give it a default value
        Platforms.Add(new Platform());
    }
    //close/dispose binary reader
}

Como puede ver, el LoadMapsmétodo no solo puede cargar la última versión del mapa, ¡sino también versiones anteriores! Cuando carga los mapas más antiguos, tiene control sobre los valores predeterminados que usa.


8

Cuento

Una buena alternativa a este problema es almacenar sus niveles en un mapa de bits, un píxel por mosaico. El uso de RGBA permite fácilmente almacenar cuatro dimensiones diferentes (capas, identificadores, rotación, tinte, etc.) en una sola imagen.

Larga historia

Esta pregunta me recordó cuando Notch transmitió en vivo su entrada de Ludum Dare hace unos meses, y me gustaría compartir en caso de que no sepa lo que hizo. Pensé que era realmente interesante.

Básicamente, utilizó mapas de bits para almacenar sus niveles. Cada píxel en el mapa de bits correspondía a un "mosaico" en el mundo (no es realmente un mosaico en su caso, ya que era un juego emitido por rayos pero lo suficientemente cerca). Un ejemplo de uno de sus niveles:

ingrese la descripción de la imagen aquí

Entonces, si usa RGBA (8 bits por canal), puede usar cada canal como una capa diferente y hasta 256 tipos de mosaicos para cada uno de ellos. ¿Sería eso suficiente? O, por ejemplo, uno de los canales podría mantener la rotación del mosaico como usted mencionó. Además, crear un editor de niveles para trabajar con este formato también debería ser bastante trivial.

Y lo mejor de todo, ni siquiera necesita ningún procesador de contenido personalizado, ya que puede cargarlo en XNA como cualquier otro Texture2D y leer los píxeles de nuevo en un bucle.

IIRC usó un punto rojo puro en el mapa para señalar a un enemigo, y dependiendo del valor del canal alfa para ese píxel determinaría el tipo de enemigo (por ejemplo, un valor alfa de 255 podría ser un murciélago mientras que 254 sería un zombie u otra cosa).

Otra idea sería subdividir los objetos del juego en aquellos que están fijos en una cuadrícula y aquellos que pueden moverse "finamente" entre las fichas. Mantenga los mosaicos fijos en el mapa de bits y los objetos dinámicos en una lista.

Incluso podría haber una forma de multiplexar aún más información en esos 4 canales. Si alguien tiene alguna idea sobre esto, avíseme. :)


3

Probablemente podría salirse con la suya con casi cualquier cosa. Desafortunadamente, las computadoras modernas son tan rápidas que, presumiblemente, esta cantidad relativamente pequeña de datos realmente no haría una diferencia de tiempo notable, incluso si se almacena en un formato de datos hinchado y mal construido que anhela una gran cantidad de procesamiento para ser leído.

Si quieres hacer lo "correcto", haces un formato binario como el que sugirió Drackir, pero si haces otra elección por alguna razón tonta, probablemente no volverá y te morderá (al menos no en cuanto al rendimiento).


1
Sí, definitivamente depende del tamaño. Tengo un mapa que tiene alrededor de 161 000 fichas. Cada uno de los cuales tiene 6 capas. Originalmente lo tenía en XML, pero tenía 82 MB y tardó una eternidad en cargarse. Ahora lo guardo en formato binario y son unos 5 mb antes de la compresión y se cargan en unos 200 ms. :)
Richard Marskell - Drackir

2

Vea esta pregunta: ¿ 'XML binario' para los datos del juego? También optaría por JSON o YAML o incluso XML, pero eso tiene bastante sobrecarga. En cuanto a SQLite, nunca lo usaría para almacenar niveles. Una base de datos relacional es útil si espera almacenar una gran cantidad de datos relacionados (por ejemplo, tiene enlaces relacionales / claves externas) y si espera hacer una gran cantidad de consultas diferentes, el almacenamiento de mapas de mosaico no parece hacer este tipo de cosas y agregaría una complejidad innecesaria.


2

El problema fundamental con esta pregunta es que combina dos conceptos que no tienen nada que ver entre sí:

  1. Paradigma de almacenamiento de archivos
  2. Representación en memoria de los datos.

Puede almacenar sus archivos como texto sin formato en algún formato arbitrario, JSON, script Lua, XML, un formato binario arbitrario, etc. Y nada de eso requerirá que su representación en memoria de esos datos tome una forma particular.

El trabajo de su código de carga de nivel es convertir el paradigma de almacenamiento de archivos en su representación en memoria. Por ejemplo, usted dice: "Veo menos redundancia de datos usando una base de datos para almacenar los datos del mosaico". Si desea menos redundancia, eso es algo que debería tener en cuenta su código de carga de nivel. Eso es algo que su representación en memoria debería poder manejar.

Es no algo que sus necesidades de formato de archivo que se ocupa. Y he aquí por qué: esos archivos tienen que venir de alguna parte.

O va a escribir esto a mano, o va a usar algún tipo de herramienta que cree y edite datos de nivel. Si los escribe a mano, lo más importante que necesita es un formato que sea fácil de leer y modificar. La redundancia de datos no es algo que su formato incluso deba considerar, porque pasará la mayor parte de su tiempo editando los archivos. ¿Desea tener que usar manualmente cualquier mecanismo que se le ocurra para proporcionar redundancia de datos? ¿No sería un mejor uso de tu tiempo que tu cargador de nivel lo maneje?

Si tiene una herramienta que los crea, entonces el formato real solo importa (en el mejor de los casos) por razones de legibilidad. Puedes poner cualquier cosa en estos archivos. Si desea omitir datos redundantes en el archivo, simplemente diseñe un formato que pueda hacerlo y haga que su herramienta use esa capacidad correctamente. Su formato de mapa de mosaico puede incluir compresión RLE (codificación de longitud de ejecución), compresión de duplicación, cualquier técnica de compresión que desee, porque es su formato de mapa de mosaico.

Problema resuelto.

Las bases de datos relacionales (RDB) existen para resolver el problema de realizar búsquedas complejas en grandes conjuntos de datos que contienen muchos campos de datos. El único tipo de búsqueda que realiza en un mapa de mosaicos es "obtener mosaico en la posición X, Y". Cualquier matriz puede manejar eso. El uso de una base de datos relacional para almacenar y mantener datos de mosaico sería una exageración extrema , y realmente no vale nada.

Incluso si construye algo de compresión en su representación en memoria, aún podrá superar los RDB fácilmente en términos de rendimiento y huella de memoria. Sí, tendrá que implementar esa compresión. Pero si el tamaño de los datos en memoria es tan preocupante que consideraría un RDB, entonces probablemente quiera implementar tipos específicos de compresión. Será más rápido que un RDB y menor en memoria al mismo tiempo.


1
  • XML: realmente simple de usar, pero la sobrecarga se vuelve molesta cuando la estructura se vuelve profunda.
  • SQLite: más conveniente para estructuras más grandes.

Para datos realmente simples, probablemente seguiría la ruta XML, si ya está familiarizado con la carga y el análisis XML.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.