Blog

Juan Mellado, 9 Agosto, 2008 - 09:06

La gestión de objetos es un tema que ya ha aparecido anteriormente, ya sea de una forma auxiliar, como objetivos o recompensas de misiones por ejemplo, o de una forma totalmente destacada, como cuando se trató el tema de los contenedores (bolsas). Por si solos constituyen una entidad bastante importante dentro de lo que sería un diseño de juego clásico, aunque en la práctica resultan sencillos de modelar con una base de datos relacional.

La idea fundamental es que debe existir una tabla con todas las clases de items posibles, y otra tabla con las instancias concretas que existen en el mundo del juego. Para los que tengan nociones de programación orientada a objetos esto debe de ser fácil de entender. El item es el "concepto" abstracto (como automóvil), y los objetos son las "instancias" concretas de ese concepto (como cada uno de los coches del mundo mundial).

La cantidad, tipo y naturaleza de cada atributo concreto que pueda llegar a tener la tabla de items dependerá enteramente del juego concreto que se esté diseñando. Lo más sencillo sería tratar de identificar el mayor número posible de ellos en las fases iniciales de diseño, e ir añadiendo columnas a la tabla para aquellos que tengan una naturaleza estática, y para los que no se prevean grandes cambios a priori. Para el resto de atributos que no se identifiquen claramente, o cuyos valores apliquen muy rara vez, o que sólo tengan sentido con un tipo muy concreto de objetos, tal vez la mejor solución sería la de crear tablas auxiliares, u optar por una estructura genérica de pares (tipo, valor) como ya se ha discutido muchas veces en esta serie.

Tal vez una buena forma de ver todo esto sea con un pequeño análisis de algunos atributos básicos que pueden distinguirse en los objetos de un juego real como el popular "World of Warcraft" (WoW).

Lo más evidente es el nombre de cada objeto, que en un principio iría en la tabla de items a través de una foreign key a la tabla de textos para el soporte multidioma. Y otro tanto ocurría con las descripciones, o cualquier otro tipo de texto asociado de forma única a cada item.

Otros atributos básicos que se añadirían como columnas de la tabla serían los "tipos", "categorías", "familias" o "grupos" a los que pertenece cada item. A grandes rasgos, en WoW los objetos están clasificados en función de aspectos tales como su utilidad (armadura, arma, munición, comida, ...), material (tela, cuero, malla, placa, ...), manejo (mano izquierda, mano derecha, ambas manos, ...), y así sucesivamente, aunque no todas esas clasificaciones tienen sentido para todas las clases de items existentes. Por ejemplo, un elixir no se maneja ni puede equiparse, solo consumirse, lo que varía de un elixir a otro es el efecto que provoca en quien lo consume o el tiempo que dura dicho efecto, aunque esto no resta el hecho de que también existan categorías propias dentro de la familia de elixires (batalla, defensa, ...). La forma más lógica de modelar toda esta vasta variedad de posibilidades sería crear tablas específicas para cada tipo, almacenando en ellas sus atributos concretos, con la idea de dejar el modelo lo más normalizado posible. La penitencia de esta solución posiblemente sea la de tener que realizar más accesos, al encontrarse la información distribuida por más tablas. Lo importante es recordar que esta información es de carácter estático, introducida durante el desarrollo del juego, que no cambiará durante la ejecución del mismo, y que por lo tanto, podría sacarse partido de estructuras básicas como las caches de objetos más frecuentemente utilizados.

El precio de un objeto también sería un atributo básico a guardar en una columna de la tabla de items. Aunque en WoW no se puede comerciar con todos los objetos, algunos no tienen un precio predeterminado, como por ejemplo los objetos únicos utilizados para la realización de algunas misiones. Algo que en la práctica podría simplemente indicarse guardando un valor de cero para ellos en la columna de precio. No obstante, en WoW también existen otras restricciones a la hora de comerciar con los objetos. Por ejemplo, existe el concepto de "ligamiento", de forma que los objetos ligados al espíritu de un personaje no pueden ser utilizados por otros personajes, lo que los hace inútiles a efectos de trueques o subastas por ejemplo, pero que no impide que puedan sean vendidos por dinero a los NPCs comerciantes del mundo virtual. Es decir, que sería necesaria una columna para indicar por cada item si puede ligarse y la forma en que se liga (no se liga, se liga al recogerlo, se liga al equiparlo, ...). Y de igual forma, sería necesaria una columna en la tabla de objetos para indicar para cada instancia concreta si se encuentra ligada o no.

Por su parte, la rareza de un objeto, un concepto que en WoW se utiliza para distinguir objetos difíciles de encontrar o que aportan algún tipo de ventaja significativa, sería igualmente una gran candidata a almacenarse en la propia tabla de items. Las ventajas que aporta cada objeto, en cambio, habría que mirarlas una a una. Si la ventaja que reporta es un incremento de estadísticas en uno o varios atributos concretos del personaje, entonces estos valores deberían almacenarse de igual forma que se hace para cada personaje. Esto es, si en la tabla de personajes hay una columna para cada atributo (fuerza, intelecto, poder, agilidad, espíritu, ..) entonces podrían añadirse esas mismas columnas en la tabla de items para facilitar su gestión. El riesgo que se correría con esta solución es que dichas columnas no tuvieran sentido para la gran mayoría de items, que para un porcentaje enorme de ellos siempre se almacenase un valor de cero, con el consiguiente desperdicio de almacenamiento. Si el tamaño es un problema, entonces se podría optar por crear una tabla auxiliar que sólo contuviera esas columnas, y que sólo contuviera registros para los items que aporten alguna ventaja de ese tipo. Un matiz importante al respecto que hay que tener en cuenta dentro del diseño de WoW, es que algunos objetos (instancias, no items) pueden ser mejorados por los propios personajes aplicándoles refuerzos o encantamientos. Debe ser claro que esas mejoras deben estar almacenadas en columnas de nuevas tablas relacionadas con las instancias en vez de con los items. Es decir, al crear una instancia se crearían los registros copiando los atributos del item tomado como patrón, y a partir de ahí ya se podrían cambiar los valores para cada objeto en particular.

Otra peculiaridad propia de WoW es que permite que los personajes creen objetos mediante el aprendizajes de profesiones y habilidades (sastrería, herrería, cocina, primeros auxilios, ...), y la combinación o refinamiento de la materia prima apropiada. Varios objetos se destruyen, borrando físicamente los registros asociados en la base de datos, para crear unos nuevos, insertando físicamente los registros correspondientes. Las recetas, diseños o patrones que indican el tipo y número de objetos necesarios podrían modelarse simplemente creando una tabla con una columna que hiciera referencia a un item y otra al número concreto de ellos que hace falta.

Un análisis más detallado de los objetos de WoW incluirían otros atributos básicos como el nivel mínimo que debe tener un personaje para utilizar o equipar un objeto, la durabilidad de las armas o armaduras, y así sucesivamente. Pero este análisis no resultaría de ninguna utilidad a menos que se quiera hacer un clon de WoW. Lo importante, a mi juicio, es entender que el diseño de la base de datos necesario para soportar items y objetos es algo que depende enteramente del juego que se quiera desarrollar.

Temas: Stratos
Juan Mellado, 2 Agosto, 2008 - 08:45

La gestión del seguimiento de misiones debería diseñarse siguiendo el mismo estilo que se haya utilizado para la gestión de objetivos de las mismas. Es decir, si se ha diseñado un modelo con tablas que permiten definir misiones típicas como "matar N monstruos de tipo X", entonces parece lógico que se deban añadir más tablas que almacenen el número concreto de monstruos de este tipo que se han matado desde que se aceptó la misión. No obstante, para otro tipo de misiones típicas como "recolectar N objetos de tipo X" puede que no se quiera hacer eso, e ir directamente al inventario a contar cada vez el número de objetos de ese tipo que se tiene en cada momento. La principal diferencia entre ese primer y segundo tipo de misiones, es que las primeras exigen gestionar un contador, y las segundas realizar un recuento. El número de objetos de un determinado tipo que se llevan consigo puede obtenerse directamente del inventario, por lo que almacenarlo en otra tabla sería guardar información redundante, ya que es una cantidad que puede calcularse a partir de otra información almacenada en la propia base de datos, y es algo que sólo debería hacerse cuando el coste de dicho cálculo sea muy elevado.

Otro aspecto interesante del seguimiento es el estado de las misiones con respecto al personaje. Una misión empieza cuando un personaje la acepta, así que hará falta una tabla que relacione a los personajes con las misiones que han aceptado y el estado en que se encuentran. A priori, una misión puede estar en curso o completada, aunque esto es algo que dependerá enteramente del juego concreto que se esté desarrollando. Las misiones de escolta por ejemplo pueden marcarse como fracasadas, que vendría a ser una especie de estado intermedio entre los dos anteriores. Esto puede realizarse con un modelo físico compuesto por una tabla con al menos tres columnas, una con el ID del personaje, otra con el ID de la misión, y otra con el estado de la misión respecto al personaje. Aunque también es posible realizar el modelo con varias tablas, guardando en una de ellas las misiones en curso, y en otras las misiones completadas o fracasadas. Una tabla para cada estado distinto. Esta segunda solución tiene la ventaja de reducir el número de registros en una tabla de trabajo que a priori tendrá un gran número de ellos, y evitar tener que realizar búsquedas o agrupaciones por la columna de estado. Lo curioso es que, de hacerse así, habría que borrar registros de una tabla e insertarlos en otra para cambiarlas de estado, y sería la primera vez que se borra algo, ya que hasta ahora todo han sido inserciones y actualizaciones.

Si una misma misión puede repetirse varias veces por un mismo personaje, entonces hay que plantearse como se quiere hacer la gestión de ese caso particular. Una primera solución, utilizando una columna de estado, podría consistir simplemente en cambiar el estado de la misión con respecto al personaje, y ponerla de nueva en curso cada vez que se repita. El inconveniente de este método es que se perdería el histórico, ya que además del cambio de estado, habría que actualizar las tablas auxiliares de seguimiento para reiniciar los contadores asociados a la misma. Otra solución, si no se quiere perder el histórico, consistiría en permitir guardar en una misma tabla más de una vez la misma pareja (personaje, misión), descartando la idea intuitiva de utilizar esas dos columnas como clave primaria compuesta de la tabla y crear un nuevo ID exclusivo para dicha relación.

En la imagen pueden verse las dos nuevas tablas que se han añadido para el seguimiento. Por una parte la que relaciona a los personajes con las misiones, que incluye una columna con el estado de la misión con respecto al personaje, y por otra parte la que relaciona a esta primera tabla con la de los objetivos concretos de cada misión, que incluye una columna para almacenar el grado de avance de cada uno de ellos. Esta última tabla puede verse como un almacén temporal, ya que una vez completada una misión los registros correspondientes podrían borrarse, a menos que se quiera conservar el histórico o almacenar en ella información complementaria con fines estadísticos. Otra solución sería borrarla cada cierto tiempo mediante algún proceso automatizado (batch), eliminando los registros correspondientes a misiones completadas.

Desde un punto de vista técnico, habrá que tener en cuenta además que posiblemente sea necesario definir un índice compuesto, no único además, sobre las columnas de personaje y misión de la tabla de seguimiento principal, ya que será por esas dos por donde normalmente se acceda.

Temas: Stratos
Juan Mellado, 2 Agosto, 2008 - 05:40

Esta semana hemos incorporado a Planet Stratos el blog de Worvast. Para filtrar las entradas de tipo personal, y que sólo aparezcan las relacionadas con el mundillo del desarrollo de videojuegos, se han suscrito sólo aquellos posts etiquetados dentro de la categoría (tag) "Planet Stratos". El blog aún no tiene mucho contenido, pero promete reunir información interesante, como una serie dedicada a la programación de videojuegos para Nintendo DS.

¡Bienvenido!

Juan Mellado, 26 Julio, 2008 - 11:54

En este post continúo la serie la serie dedicada a las misiones, que no hace más que ampliarse, centrándome en los objetivos. Es decir, en lo que hay que hacer para completar una misión. En la práctica esto es bastante parecido a la vida real, como por ejemplo cuando a un empleado a primeros de año se les establecen una serie de hitos que tiene que tratar de cumplir de cara a su subida salarial. Los más viciados lo tienen claro: "WoW no es un juego, es un trabajo".

Para la gestión de los objetivos, mi idea original consistía simplemente en añadir columnas a la tabla de misiones (target_1, target_2, ...), de forma que cada una de ellas pudiera interpretarse libremente en función del tipo concreto de misión que se tratase. RobiHm me dejó un comentario al respecto acusándome, con toda la razón del mundo, de pasar de puntillas por el tema sin presentar una solución normalizada como he venido haciendo hasta ahora con el resto de temas. Por su parte, cuando estuve hablando de la gestión de personajes, yEns me dejó otro comentario al respecto diciendo que la solución general que presentaba con tablas de registros (tipo, valor) podría dar mucho juego, al conseguirse modelos más verticales (más registros) que horizontales (más columnas), pero que debían preverse los posibles problemas de rendimiento asociados a tal tipo de diseño.

¿Por qué pensé en saltarme el proceso de normalización en este punto en concreto? La respuesta está en que hasta ahora todo el modelo lo he ido construyendo basándome en mi experiencia en el diseño de aplicaciones de gestión, haciendo hincapié en distinguir claramente las entidades y sus relaciones, y encapsulando cada una de ellas en su propia tabla. De hecho, ninguna de las tablas que he ido presentando hasta ahora tiene una columna que pueda contener un valor nulo. Sin embargo, para el diseño de esta parte de las misiones, el modelo lo enfoqué pensando en la eficiencia, en reducir el número de accesos a base de datos necesarios para obtener los objetivos y realizar su posterior seguimiento. Y deber ser claro que si toda esta información está toda en las columnas de una única tabla, entonces bastará con hacer un único acceso, de ahí la solución que planteaba.

En la práctica, saltarse la normalización al diseñar la base de datos que dará soporte a un MMORPG es algo que parece bastante habitual. Sólo hay que echar un vistazo a los pocos modelos que pueden encontrarse por Internet, o leer los comentarios que algunos desarrolladores dejan en sus blogs o foros. La justificación de esto es que un MMORPG no es un producto al uso, no hay que verlo como una aplicación de gestión ordinaria, y que el uso de una base de datos relacional es una solución de carácter práctico. La alternativa, desarrollar un gestor propio de base de datos, resulta prohibitiva en la mayoría de los casos. ¿Pero entonces es correcto saltarse la normalización? No creo que haya una única respuesta a esta pregunta. Y mucho menos en una fase temprana del desarrollo. Lo lógico sería empezar realizando un diseño lo más normalizado posible al principio, y a medida que se vaya avanzando y probando, detectar que partes no funcionan y sustituirlas sugiriendo otros enfoques, incluyendo el abandono de la normalización si es preciso. En base a esto, voy a echar para atrás mi ideal original, y volver a presentar todo lo más normalizado posible.

En la imagen puede verse el modelo considerado para almacenar los objetivos de cada misión. La idea es que existe una tabla principal en la que se almacenan objetivos tales como "recolectar n items" o "matar n NPCs", y luego tablas auxiliares en las que se indican los items o NPC concretos a los que hacen referencia dichos objetivos. De esta forma, si una misión consiste en "matar 6 Guardianes Dorados de la Frontera Sur, a 1 Capitán de la Guardia, y ya de paso, recuperar 1 Estandarte robado a la Compañía Imperial", entonces se almacenarían tres registros en la tabla de objetivos, dos en la de relación con los NPCs, y uno en la de relación con los items, siendo la columna de tipo en la tabla de objetivos la que indique a que tabla de relación hay que ir en cada caso concreto. O sea, para los objetivos de tipo "matanza" se iría a la de relación objetivo-NPC, para los objetivos de tipo "recolección" a la de objetivo-item, y así sucesivamente.

Por último, desde un punto de vista técnico, señalar que será necesario crear un índice por el ID de misión en la tabla de objetivos, ya que es por esa columna por donde primero se intentará acceder normalmente.

Juan Mellado, 19 Julio, 2008 - 11:50

En el post anterior se identificaron los componentes básicos de una misión en su acepción más clásica: iniciador, requisito, recompensa, objetivo y seguimiento. En este post ha llegado el momento de desarrollar algunas soluciones para la construcción del modelo físico de base de datos que soporte los tres primeros componentes de dicha estructura.

Para la gestión de los iniciadores ya apunté que una posible solución era crear tablas de relación entre las entidades que inician las misiones, como personajes y objetos por ejemplo, con las propias misiones en si mismas. Adicionalmente, en función del diseño del juego concreto que se esté desarrollando, se podría añadir una o más columnas en dichas tablas que indiquen la acción que se tiene realizar para lanzar la misión, sobre el personaje (que suele ser hablar normalmente), o sobre el objeto (examinar normalmente).

En la primera imagen de este post puede verse el sencillo esquema, donde aparece por primera vez la tabla de personajes no jugadores (NPC = Non-Player Character). Lo más destacable desde un punto de vista técnico es que las claves primarias de las nuevas tablas son compuestas, lo cual no siempre es una buena idea, sobre todo de cara a futuras ampliaciones del modelo.

Para la gestión de los requisitos planteaba simplemente incorporar nuevas columnas a la tabla de misiones, con los valores de los atributos mínimos que debe tener el personaje para que se le pueda ofrecer la misión. Naturalmente esto dependerá nuevamente, como casi todo, de las características concretas del juego que estemos desarrollando. Por ejemplo, imaginemos que los personajes tienen facción, raza, clase, o algún tipo de nivel o karma. Si los valores de esos atributos se encuentran almacenados en columnas de la tabla de personajes, entonces lo más natural es añadir también las columnas correspondientes en la tabla de misiones. De igual forma, si esos valores se encuentran en una tabla de pares (tipo, valor), entonces los más normal sería crear otra tabla con la misma estructura para las misiones. Y si se quiere que una misma misión la puedan realizar personajes de características distintas, entonces habrá que añadir nuevas tablas para guardar todas las posibles combinaciones.

En la segunda imagen de este post puede verse un desarrollo del modelo que contempla un atributo fijo en la tabla de personajes, la columna de karma, que ha sido llevado a la tabla de misiones, la columna con el mínimo de karma necesario. De igual forma puede verse como se ha creado una tabla para valores de atributos variables para las misiones a semejanza de la existente para los personajes, aunque algo modificada a como la desarrollé la primera vez en el post dedicado a la personalización de avatares.

En medio de este segundo esquema, puede verse además la tabla de misiones previas que deben haberse realizado anteriormente como requisito para poder optar a realizar una misión determinada. En un principio iba a ser una columna de la propia de tabla de misiones que apuntara a si misma, pero al final he decidirlo crear una nueva tabla para permitir que por ejemplo dos tramas del guión del juego se junten en una sola. A partir de aquí, debe quedar claro que si se quisieran poner requisitos relacionados con otras entidades del juego, como por ejemplo que el personaje haya visitado un área concreta del mundo, entonces habría que ir añadiendo las tablas de relación correspondientes con la de misiones. Y lógicamente, habría que ir almacenando para cada personaje sus relaciones con esas entidades, como las misiones completadas o las áreas del mundo visitadas.

Para la gestión de recompensas hay que relacionar las misiones con el dinero y los objetos que reportan por su finalización, y naturalmente con cualquier otro tipo de beneficio que ofrezcan en función del juego concreto que se esté desarrollando. El dinero, a menos que se quieran tener varios tipos de divisas distintos, puede ir perfectamente almacenado como una columna más en la tabla de personajes, y por consiguiente en la de misiones. Se podría tratar como un tipo de objeto más, e insertarlo en la tabla de items, pero a mi particularmente esa idea no me acaba de convencer, lo considero más como el valor de un atributo del personaje (riqueza). Los objetos por su parte deben ir en tablas aparte que almacenen sus relaciones con las misiones, junto con una columna que indique la cantidad concreta que se ganará de cada una de ellos. Si además se quiere que la recompensa pueda tener una parte fija y otra variable, entonces habrá que añadir alguna columna más a dichas tablas que indique el tipo concreto de recompensa de cada lote particular de objetos.

En la tercera imagen de este post puede verse como se ha ampliado el esquema con una nueva tabla intermedia de objetos ofrecidos como recompensa por cada misión, con la cantidad dada de cada uno de ellos, y un indicador si se pertenecen a la parte fija o variable. En la tabla de misiones he puesto a modo de ejemplo un par de columnas con recompensas de tipo fijo, como el karma o el dinero. Evidentemente el esquema podría ampliarse con más tablas de recompensa, ya sea para relacionarlas con otras entidades, o con más pares (tipo, valor) como se hizo anteriormente.

La gestión de objetivos y seguimiento de misiones la desarrollaré en detalle en el siguiente post, haciéndome eco de algunos comentarios que me han hecho.