Cómo modelar múltiples "usos" (ej. Arma) para inventario utilizable / objeto / ítems (ej. Katana) dentro de una base de datos relacional

10

Así que estoy trabajando para expandir los usos de los elementos en www.ninjawars.net , y no estoy exactamente seguro de cómo representarlos de manera flexible en la base de datos relacional que usamos.

Puede que esté ladrando al árbol equivocado, así que siéntase libre de hacer sugerencias en otras direcciones, pero actualmente estoy pensando que cada elemento debería tener "etiquetas" relacionales.

Por ejemplo, una Katana es actualmente una fila en la base de datos de "elementos". Para convertirlo en un arma, y ​​una cosa holgable, estaba pensando que tendría una base de datos de "rasgos", y una tabla item_traits que simplemente se vinculaba entre ellos.

// Objects and their basic data

item_id | item
1 | Naginata


// Things that objects can do

trait_id | trait
1 | weapon
2 | holdable

// How those objects do those things, e.g. powerfully, weakly, while on fire

_item_id | _trait_id | item_trait_data
1 | 1 | damage: 5, damage_type: sharp, whatever, etc

No estoy realmente seguro de cómo modelar los datos adicionales que resultan (por ejemplo, el daño que causará una espada, el tipo de daño, etc.).

Tampoco estoy especialmente contento de que todo un artículo se almacene en más de un lugar, por ejemplo, para crear una copia de un artículo con un nombre diferente, como una "espada corta", tendría que copiar de múltiples tablas para crear el elemento duplicado.

¿Hay una mejor manera de exponer estas cosas que me estoy perdiendo?

Editar: solo debo tener en cuenta que ya tengo una base de datos postgresql en uso en el sitio, por lo que quiero usarla para mi almacenamiento de datos.

Editar: he agregado una respuesta para la implementación que estoy viendo actualmente.

Kzqai
fuente

Respuestas:

10

Voy a estar ligeramente en desacuerdo con todos y decir que el enfoque relacional es razonable aquí. Lo interesante aquí es que los artículos pueden tener múltiples roles. El problema principal será que el mapeo entre este diseño relacional y un diseño OO en el código no se sentirá "natural", pero creo que en el lado de la base de datos, múltiples roles pueden expresarse limpiamente (sin codificaciones extrañas o redundancia, solo se une) .

Lo primero que debe decidir es qué cantidad de datos es específica del elemento y cuánto es compartida por todos los elementos de un tipo determinado.

Esto es lo que haría si todos los datos son específicos del artículo:

// ITEMS table: attributes common to all items
item_id | name        | owner         | location             | sprite_id | ...
1       | Light Saber | 14 (Tchalvek) | 381 (Tchalvek house) | 5663      | ...

// WEAPONS table: attributes for items that are weapons
item_id | damage | damage_type | durability | ...
1       | 5      | sharp       | 13         | ...

// LIGHTING table: attributes for items that serve as lights
item_id | radius   | brightness | duration | ...
1       | 3 meters | 50         | 8 hours  | ...

En este diseño, cada elemento está en la tabla Elementos, junto con los atributos que tienen todos (o la mayoría) de los elementos. Cada rol adicional que un elemento puede jugar es una mesa separada.

Si quieres usarlo como arma, lo buscarías en la tabla de Armas. Si está allí, entonces se puede usar como arma. Si no está allí, no se puede usar como arma. La existencia del registro te dice si es un arma. Y si está allí, todos sus atributos específicos de armas se almacenan allí. Dado que esos atributos se almacenan directamente en lugar de forma codificada, podrá realizar consultas / filtros con ellos. (Por ejemplo, para la página de métricas de su juego, es posible que desee agregar jugadores por tipo de daño de arma, y ​​podría hacerlo con algunas combinaciones y un tipo de daño de grupo).

Un elemento puede tener múltiples roles y existir en más de una tabla específica de roles (en este ejemplo, tanto arma como iluminación).

Si es solo un booleano como "es así de retenible", lo pondría en la tabla Elementos. Puede valer la pena almacenar en caché "es un arma", etc., para que no tenga que realizar una búsqueda en las Armas y otras tablas de roles. Sin embargo, agrega redundancia, por lo que debe tener cuidado de mantenerlo sincronizado.

La recomendación de Ari de tener una tabla adicional por tipo también se puede utilizar con este enfoque si algunos datos no varían según el elemento. Por ejemplo, si el daño del arma no varía por elemento, pero los roles aún varían por elemento, puede factorizar los atributos de armas compartidas en una tabla:

// WEAPONS table: attributes for items that are weapons
item_id | durability | weapon_type
1       | 13         | light_saber

// WEAPONTYPES table: attributes for classes of weapons
weapon_type_id | damage | damage_type
light_saber    | 5      | energy

Otro enfoque sería si los roles desempeñados por los elementos no varían por elemento, sino solo por tipo de elemento. En ese caso, pondrías el item_type en la tabla Items, y puedes almacenar las propiedades como "¿es un arma" y "es holdable" y "es una luz" en una tabla ItemTypes. En este ejemplo, también hago que los nombres de los artículos no varíen por artículo:

// ITEMS table: attributes per item
item_id | item_type    | owner         | location
1       | light_saber  | 14 (Tchalvek) | 381 (Tchalvek house)

// ITEMTYPES table: attributes shared by all items of a type
item_type   | name        | sprite_id | is_holdable | is_weapon | is_light
light_saber | Light Saber | 5663      | true        | true      | true

// WEAPONTYPES table: attributes for item types that are also weapons
item_type   | damage | damage_type
light_saber | 5      | energy

Es probable que los tipos de elementos y los tipos de armas no cambien durante el juego, por lo que puede cargar esas tablas en la memoria una vez y buscar esos atributos en una tabla hash en lugar de con una unión a la base de datos.

amitp
fuente
+1. Esta es la única respuesta que utiliza una base de datos relacional como una base de datos relacional. Cualquier otra cosa que no sea este u otro extremo de la sugerencia de blobs de Nevermind y solo usar el DB como almacén de datos, son planes horribles.
+1 Pero estos enfoques parecen amables inflexibles. Pido disculpas si sueno inconsistente, pero quiero poder crear las estructuras de la base de datos para los elementos esencialmente una vez ... ... y luego codificar más funciones para los elementos, hacer más inserciones en la base de datos para los elementos, pero no tener que cambiar la base de datos nuevamente en el futuro (con excepciones infrecuentes, por supuesto).
Kzqai
Si desea que la base de datos relacional conozca sus campos y optimice su representación, debe agregarlos en alguna parte. Es como los tipos estáticos. Si desea que su compilador conozca sus campos y optimice su representación, debe declarar los tipos. La alternativa es un enfoque de tipo dinámico, utilizado por algunas de las bases de datos de documentos, o al codificar sus datos en la base de datos relacional de alguna manera. La base de datos no sabrá cuáles son los campos ni podrá optimizar su almacenamiento, pero obtendrá cierta flexibilidad.
amitp
4

Desafortunadamente, situaciones como esta son donde las bases de datos relacionales (como SQL) se quedan cortas, y las bases de datos no relacionales (como MongoDB ) se destacan. Dicho esto, no es imposible modelar los datos en una base de datos relacional, y dado que parece que su base de código ya depende de SQL, aquí está el modelo con el que iría:

En lugar de crear un elemento y usar una tabla, cree una tabla y una tabla de referencia "[item] _type" para cada tipo de elemento.

Aquí están algunos ejemplos:

weapon_type_id | weapon_type
1 | sharp

weapon_id | weapon| weapon_type_id | damage
1 | Short Sword | 1 | 5

potion_type_id | potion_type
1 | HP
2 | Mana

potion_id | potion| potion_type_id | modifier
1 | Healing Elixer | 1| 5
2 | Mana Drainer | 2| -5

Esta solución le brinda mucha flexibilidad y escalabilidad a largo plazo, reduce (si no elimina) el espacio desperdiciado y es autodocumentado (a diferencia de "item_use_data"). Implica un poco más de configuración por parte del desarrollador, pero, en mi opinión, es la solución más elegante disponible para situaciones en las que necesita usar una base de datos relacional para almacenar sus datos. En términos generales, las bases de datos no relacionales son mucho mejores para el desarrollo del juego, ya que son más flexibles en la forma en que modelan los datos, a la vez que son más efectivas que las bases de datos basadas en SQL, lo que las convierte en una opción de "ganar-ganar".

Edición 1: se corrigió un error con el campo potion_type_id

Edición 2: se agregaron más detalles en bases de datos no relacionales versus relacionales para proporcionar una perspectiva adicional

Ari Patrick
fuente
Realmente no veo mucha flexibilidad saliendo de ese sistema. Por ejemplo, si quisiera un frasco de vidrio, que pudiera usarse como arma, y ​​ser 'afilado' ... ... no tendría suerte. Además, para cada nuevo tipo de elemento, tendría que crear una estructura de base de datos para él. Incluso si solo hubiera uno o dos del tipo.
Kzqai
Los puntos que menciona son muy válidos y destacan ejemplos adicionales de por qué las bases de datos relacionales no son adecuadas para esta situación específica. El modelo que presenté simplemente muestra la mejor manera de diseñar los datos de tal manera que se conserve la memoria y se conserve la funcionalidad de SQL. Para almacenar elementos que son de varios tipos, la mejor manera de representar esos datos en este modelo sería crear una tabla de pociones_armas con las propiedades apropiadas de pociones y armas. Nuevamente, esta solución NO es ideal, pero es la solución más elegante cuando se utiliza una base de datos relacional.
Ari Patrick el
@Ari: No se pretende faltar al respeto, pero su problema es precisamente por qué el enfoque relacional es más válido, no menos. Es bien sabido que las bases de datos NoSQL (o NoRel) son excelentes para lecturas de gran volumen, datos distribuidos / altamente disponibles o esquemas mal definidos. Este caso es simplemente una asociación clásica de muchos a muchos, y dbs como Mongo no lo hacen tan bien como un RDBMS. Ver stackoverflow.com/questions/3332093/…
alphadogg el
@alphadogg ¿Cómo es esta una relación de muchos a muchos? En la base de datos, las armas conocen los tipos de armas, pero los tipos de armas no tienen necesidad de conocer las armas con las que están asociadas. Si por alguna razón desea conocer todas las armas que son de un tipo de arma específico, simplemente debe escribir una consulta que repita la colección de documentos del arma.
Ari Patrick el
@Ari: En la pregunta del OP, un arma puede tener muchos tipos, y un tipo puede estar vinculado a muchas armas. Por ejemplo, una poción y una espada son sostenibles. Una espada es a la vez holgable y arma.
alphadogg el
3

En primer lugar, volcar el enfoque de herencia orientado a objetos e ir con un sistema basado en componentes .

Una vez que haya hecho eso, el diseño SQL de repente se vuelve mucho más fácil. Tiene una tabla para cada tipo de componente con un número de ID compartido. Si desea el ítem # 17, busque "ID de ítem 17" en cada tabla. Cualquier tabla que tenga una clave obtiene su componente agregado.

Su tabla de artículos contiene todos los datos requeridos para los artículos en general (nombre, precio de venta, peso, tamaño, cualquier otra cosa que se comparta entre todos los artículos). Su tabla de armas contiene todos los datos apropiados para las armas, su tabla de pociones contiene todos los datos apropiados. para pociones, tu tabla de armadura contiene todos los datos apropiados para la armadura. Quiere una coraza, esa es una entrada en Objeto y una entrada en Armadura. Si quieres un yelmo de espada que puedas beber, solo pones una entrada en cada mesa, y bam, ya terminaste.

Tenga en cuenta que este mismo patrón no es particularmente específico de un elemento: puede usarlo para criaturas, zonas, cualquier otra cosa que desee. Es sorprendentemente versátil.

ZorbaTHut
fuente
2

Usar SQL fue tu grave error. Absolutamente NO es adecuado para almacenar datos estáticos de diseño de juegos.

Si no puede alejarse de SQL, consideraría almacenar elementos en forma serializada. Es decir

item_id (int) | item (BLOB)
1             | <binary data>

Por supuesto, eso es feo y arroja todas las "sutilezas" de SQL por la ventana, pero ¿realmente las necesita ? Lo más probable es que leas todos los datos de tus artículos al comienzo del juego de todos modos, y nunca SELECTpor otra cosa que no sea item_id.

No importa
fuente
3
Dado que (según tengo entendido) el juego es multijugador y admite PvP, existe la posibilidad de que la mayoría de la información del juego no se almacene en el cliente. Si eso es correcto, cada vez que se lean datos de la tabla, tendrá que ser deserializado antes de ser remotamente útil. Como resultado, este formato hace que la consulta de artículos por sus propiedades sea MUY costosa; por ejemplo: si desea recuperar todos los elementos que son del tipo "arma", tendría que recuperar cada elemento, deserializar cada uno y luego filtrar manualmente el tipo de "arma", en lugar de ejecutar una consulta simple .
Ari Patrick el
1
Desde mi experiencia, todos los elementos se leen en la memoria caché en memoria al inicio del juego de todos modos. Por lo tanto, solo los deserializa una vez al inicio y no utiliza la base de datos en absoluto después. Si esto no es así en este caso, entonces estoy de acuerdo, almacenar objetos serializados es una mala idea.
importa
Hmmm, lo único es que esto me deja con una interfaz requerida para editar los datos, lo que sería inconveniente.
Kzqai
2

Dependiendo de cuántos rasgos es probable que necesite, podría usar una máscara de bits simple para objetos con los diferentes bits correspondientes a diferentes rasgos:

000001 = holdable
000010 = weapon
000100 = breakable
001000 = throwable
010000 = solids container
100000 = liquids container

Luego puede hacer pruebas de bits simples para ver si un objeto se puede usar para una determinada función.

Por lo tanto, la "botella de vidrio" puede tener un valor de 101111, lo que significa que se puede guardar, se puede usar como arma, se rompe fácilmente, se puede tirar y puede contener líquidos.

Cualquier editor que cree para elementos puede tener un conjunto simple de casillas de verificación para habilitar / deshabilitar rasgos en un objeto.

Muttley
fuente
1
Me gusta la idea, porque permite que un elemento mantenga sus propios rasgos, y no sé si voy a necesitar buscar los rasgos en la base de datos, pero no estoy realmente seguro de cómo hacer que eso funcione en un contexto relacional Supongo que cada bit se correlacionaría con los identificadores incrementales de los rasgos en la base de datos ...
Kzqai
Sin embargo, supongo que el enmascaramiento de bits es más útil para un número fijo de rasgos.
Kzqai
Mucho. ¿Qué pasa si has agotado tu último "bit"? ¿Qué tan fácil es aumentar el tipo de datos para ganar bits y filtrarlos a través de la capa de su aplicación, o agregar una columna y hacer lo mismo? Además, ¿está bien ajustado su motor de base de datos particular para indexar operaciones de bits? Es mejor en una base de datos relacional mantenerse atómico con columnas de datos. No agrupe múltiples dominios en una columna. No puede aprovechar los beneficios de la plataforma.
alphadogg
1
Si quisiera una tabla de búsqueda en la base de datos para los rasgos (probablemente una buena idea ya que luego puede agregarlos dinámicamente a cualquier editor), entonces tendría algo como esto: id, máscara de bits, descripción. 1,1, "Holdable"; 2,2, "Arma"; 3,4, "rompible", etc. Con respecto al número fijo, sí, esto es cierto. Pero si usa un int 64bit, debería tener un amplio alcance.
Muttley
1

En nuestro proyecto tenemos atributos_elemento para los diferentes "datos adicionales" que puede tener un elemento. Se presenta algo como esto:

item_id | attribute_id    | attribute_value | order 
1        25 (attackspeed)       50             3    

Luego tenemos una tabla de atributos que se ve así:

id |   name       | description
1    attackspeed    AttackSpeed:

Sin embargo, Ari Patrick tiene razón, en última instancia, los db relacionales no están hechos para esto. La desventaja es que tenemos algunos procedimientos bastante complejos para generar nuevos elementos (hecho a través de una herramienta externa, que recomiendo encarecidamente, no intentes agregarlos manualmente, solo te confundirás)

La otra opción que tiene es usar un lenguaje de secuencias de comandos para crear las plantillas de elementos, luego puede analizarlas fácilmente y usarlas para crear nuevos elementos. Por supuesto, todavía tiene que guardar los datos del elemento en la base de datos, pero en ese momento no tiene que preocuparse por los detalles de la creación de nuevos elementos, puede simplemente copiar un archivo de script antiguo, cambiar algo de información y estará bien. ir.

Honestamente, si tuviéramos que rehacer cómo creamos nuevos elementos estáticos, probablemente optaríamos por un enfoque mucho más simple utilizando plantillas de elementos de secuencias de comandos.

Kyle C
fuente
1

Esta es su típica relación de muchos a muchos, nada demasiado esotérico para cualquier base de datos relacional capaz. Tiene muchos rasgos para cualquier objeto, y uno o más tipos de objeto diferentes pueden usar cualquier rasgo. Modele tres relaciones (tablas), siendo una relación de asociación, y ya está. La indexación adecuada le asegurará lecturas rápidas de datos.

Coloque un ORM en su código y debería tener muy pocos problemas entre DB y middleware. Muchos ORM también pueden generar automáticamente las clases y, por lo tanto, se vuelven aún más "invisibles".

En cuanto a las bases de datos NoSQL, no hay razón para que no pueda hacerlo con ellas. Actualmente está de moda animar esa tecnología en trapos comerciales y blogs, pero vienen con una serie de sus propios problemas: plataformas relativamente inmaduras, ideales para lecturas simples que cambian con poca frecuencia (como un par de uno a muchos en un perfil) pero pobre para lecturas o actualizaciones dinámicas complejas, cadena de herramientas de soporte deficiente, datos redundantes y los problemas de integridad que lo acompañan, etc. Sin embargo, ofrecen el atractivo de una mejor escalabilidad porque evitan capacidades transaccionales de diversos grados y su sobrecarga, y modelos más fáciles de distribución / replicación .

El duende habitual de las bases de datos relacionales frente a las bases de datos NoSQL es el rendimiento. Diferentes RDMBS tienen diferentes grados de sobrecarga involucrados que los hacen menos preferidos para escalar a los niveles de Facebook o Twitter. Sin embargo, es muy poco probable que enfrentes esos problemas. Incluso entonces, los sistemas de servidor simples basados ​​en SSD pueden hacer que el debate sobre el rendimiento sea inútil.

Seamos claros: la mayoría de las bases de datos NoSQL son tablas hash fundamentalmente distribuidas, y lo limitarán de la misma manera que lo haría una tabla hash en su código, es decir. No todos los datos encajan bien en ese modelo. El modelo relacional es mucho más poderoso para modelar relaciones entre datos. (El factor de confusión es que la mayoría de los RDBMS son sistemas heredados que están mal ajustados para las demandas del popular 0.0001% de la web, es decir, Facebook et al.)

alphadogg
fuente
Si va a referirse a ellos colectivamente, ni se moleste en hablar de bases de datos. NoSQL no es una entidad que pueda discutirse significativamente, es una referencia masiva inútil que los fanbois de SQL usan para aplicar la lógica defectuosa de que si una base de datos de este grupo definido carece de una característica, todos ellos carecen de esa característica.
aaaaaaaaaaaa
NoSQL es el término que los impulsores de NoSQL eligieron adaptar. No sé por qué, no es muy descriptivo. Pero no es una especie de peyorativo. Después de haber trabajado con varios, creo que la descripción de alphadogg de ellos es justa.
Tal vez mi comentario fue un poco duro, aún así, odio ese nombre y la agrupación, es imposible tener un debate calificado sobre los detalles más finos de los diferentes sistemas de bases de datos cuando las personas mantienen esta visión simplificada del mundo.
aaaaaaaaaaaa
@eBusiness: Encuentro divertido su comentario, dado que es el campo anti-RDBMS, si lo hay, que ungieron a su grupo de tecnologías como "NoSQL". Es cierto que a muchos de ellos les gustaría ver ese nombre en desuso, en retrospectiva. Especialmente porque muchos de ellos finalmente están superponiendo SQL sobre sus implementaciones físicas. En cuanto a hablar de ellos, no tomé tu comentario como duro, o tengo la piel gruesa, ¡tú eliges! :) Quiero decir, uno puede hablar sobre Tea Party, ¿por qué no el grupo de bases de datos NoSQL?
alphadogg
Eso es justo, reconozco que la relación es totalmente de muchos a muchos. Lo que me impide crearlo en un estilo verdaderamente normalizado es la idea de que las "cosas" que compondrían un elemento se distribuirán en al menos dos tablas. Creo que no me gusta la idea de no poder crear insertos atómicos.
Kzqai
0

Aquí es donde encuentro que los mapeadores OR más modernos son realmente útiles, como Entity Framework 4 y su función de código primero (CTP) . Simplemente escriba las clases y sus relaciones en código regular y sin siquiera tener que decorarlas o ejecutar manualmente ninguna herramienta, EF generará el almacén de respaldo en formato SQL para usted, con las tablas de enlaces requeridas y todo ... realmente desata la creatividad imo ^^

Oskar Duveborn
fuente
Bueno, primero crearé el código y las clases y todo eso en el código ... ... luego tengo que traducir eso a la estructura de la base de datos existente.
Kzqai
0

Esto es lo que estoy considerando ahora:

Dado que cada "rasgo" esencialmente requiere cambios en el código de todos modos, así que decidí mantener los rasgos (y cualquier información predeterminada que requieran) en el código mismo (al menos por ahora).

P.ej $traits = array('holdable'=>1, 'weapon'=>1, 'sword'=>array('min_dam'=>1, 'max_dam'=>500));

Luego, los elementos obtienen un campo "trait_data" en la base de datos que utilizará la json_encode()función para almacenarse en el formato JSON.

item_id | item_name | item_identity | traits
1 | Katana | katana | "{"holdable":1,"weapon":1,"sword":{"min_dam":1,"max_dam":1234,"0":{"damage_type":"fire","min_dam":5,"max_dam":50,"type":"thrown","bob":{},"ids":[1,2,3,4,5,6,7]}}}"

El plan es que los elementos heredarán todas las estadísticas predeterminadas de sus rasgos principales y solo tendrán rasgos de anulación que especifiquen diferencias con respecto a lo que los rasgos establecen como predeterminados.

La desventaja del campo de rasgos es que si bien podría editar la parte json a mano ... ... no será realmente fácil o seguro hacerlo con los datos que están en la base de datos.

Kzqai
fuente
1
¿Qué sucede cuando introduces un nuevo rasgo? ¿Con qué frecuencia puede suceder esto? ¿Cuáles serían los efectos posteriores en la carga de trabajo de mantenimiento de su código? Contraste con almacenarlo como un muchos a muchos mediante el cual sus instancias de objetos se construyen dinámicamente (a través de su elección de ORM) para almacenar esos datos.
alphadogg
Gracias, ese es un buen punto, agregar nuevos rasgos no será trivial. Has añadido a mi parálisis de análisis. : p
Kzqai
Lo siento, pero esto es bastante crítico. Solo necesitas pensarlo bien. Modela el ejercicio en tu mente de lo que tendrías que hacer cuando algunas armas necesitan ser actualizadas para agregar un nuevo rasgo para que la mejora de tu juego pueda funcionar.
alphadogg
-1

Esto es un poco hacky, y no garantizo que sea un buen diseño de base de datos; mis clases de DB fueron hace un tiempo, y no me vienen a la mente rápidamente. Pero si se garantiza que los elementos tienen, digamos, menos de 10 elementos, puede asignar a cada elemento un campo de atributo1 y un campo de atributo2, y así sucesivamente hasta el atributo10. Eliminaría su necesidad de la tabla de atributos de elementos de muchos a muchos, a costa de limitar el número de atributos.

En retrospectiva, estoy bastante seguro de que es un diseño terrible, pero significa que puede quedarse con una base de datos relacional cómoda y no tener que entrar en un territorio desconocido. Depende de usted decidir si la compensación vale la pena.

Gregory Avery-Weir
fuente
Este es un diseño terrible, nunca lo hagas. Sería mejor no usar una base de datos.
ZorbaTHut
-1

Nevermind dio una buena respuesta, pero me pregunto, ¿necesitas una base de datos para esto? Almacenar todos los datos en un archivo plano y cargarlos en el programa en el lanzamiento parece ser la forma más inteligente de hacerlo.

Editar:
Correcto, en un entorno impulsado por solicitudes sin estado, es mejor que guarde los datos en una base de datos. Escriba sus datos en un archivo y escriba un fragmento de código para convertirlo en una base de datos del tipo que Nevermind sugirió.

Alternativamente, si el número de objetos no es demasiado grande, declarar el lote literalmente en un archivo de código puede ser el método más rápido.

aaaaaaaaaaaa
fuente
Err, ya tengo una base de datos en uso e integrada con el resto del sitio.
Kzqai