inmensia |
MMORPG
Juan Mellado, 27 Diciembre, 2008 - 11:34
Coincidiendo con el fin de año llega también el fin de esta serie de posts. Del modelo de datos de PlaneShift quedan todavía algunas tablas por examinar, pero las principales creo que se han examinado todas, o al menos las suficientes como para entender como está estructurado en términos bastantes generales el juego en lo que refiere a esta parte concreta. Queda un primer grupo de tablas por examinar relacionadas con el concepto de "superclient", un término que utilizan en este juego para referirse a la gestión que realizan de los NPCs, mediante procesos ejecutándose en máquinas conectadas al servidor como si fueran jugadores normales. Otro segundo grupo de tablas que contiene información variada sobre el juego, como recursos naturales, zonas de caza, y cosas por el estílo. Y un tercer grupo que almacena opciones de configuración del servidor, comandos de gm (game master), mensajes de ayuda, etc... Llegado este punto, si a alguien le interesa profundizar más en el funcionamiento del juego, le recomiendo que continue examinando el detalle concreto de cada tabla, estudiando a la par el modelo y el código fuente. Pero esa no es una opción que me atraiga mucho. Lo tomé sólo como modelo de un ejemplo real totalmente operativo, pero no quiere decir que sea el camino a seguir en todos los casos. Lo importante ha sido comprobar que realmente no hay mucha diferencia entre una aplicación de gestión y un juego de estas características, al menos en lo que al diseño de la base de datos se refiere. Es lo que tiene basarse en un gestor relacional, al final tiene que acabarse haciendo un modelo relacional, dando igual que sea para una aplicación de contabilidad que para un MMORPG. Echando la vista atrás, y releyendo un poco por encima toda esta serie, he recordado las dudas que tenía al principio sobre la viabilidad del uso de una base de datos convencional para este tipo de juegos. Sobre todo después de leer que algunas compañías habían optado por desarrollar su propio gestor de transacciones. O que otras, a pesar de utilizar un gestor comercial, habían decidido saltarse cualquier tipo de normalización. Supongo que cuando se espera tener millones de cuentas y los objetivos son muy elevados, casi extremos, muchas de esas soluciones cobrarán sentido. Pero creo que sería algo para analizar en detalle para cada caso concreto. Yo personalmente optaría primero por tener un modelo totalmente normalizado, y luego ir desnormalizando a medida que haga falta. Aunque de todas formas hoy en día las mejoras de rendimiento muchas veces se prefieren obtener sustituyendo el hardware, algo generalmente más barato y menos complejo que realizar grandes cambios en el software. Las limitaciones que tradicionalmente se le han achacado a las base de datos relaciones, en cuanto a escalabilidad se refiere, se están dejando atrás por el aumento de prestaciones del hardware y la disminución de su coste, además del uso de otras técnicas como el particionado de tablas por ejemplo. El siguiente paso lógico, después de la elaboración del modelo, sería contruir la capa de abstracción para el acceso a la base de datos. Normalmente un conjunto de clases a modo de wrapper sobre el API nativo proporcionado por el gestor utilizado. En PlaneShift estas clases están implementadas en los ficheros dal.cpp y dal.h (DAL = Data Access Layer), suministrando los habituales métodos de conexión, desconexión y ejecución de sentencias SQL. Si la elaboración del modelo es ya de por si un mundo, la construcción de una capa de abstracción no se queda atrás. Para sacar el máximo partido posible de las capacidades de una base de datos se debe optar generalmente por estudiar a fondo las posibilidades que ofrece esta. La ejecución de una simple sentencia puede ser algo rápido y casi inmediato, pero la ejecución de miles ya no tanto. La utilización de sentencias precompiladas, el uso de caches intermedias, la programación en algún tipo de PL/SQL soportado por el gestor, o las facilidades para las inserciones masivas (bulk arrays) son sólo algunas de las estrategías básicas que se deberían tener en cuenta. Aunque naturalmente, en vez de construir una capa de persistencia propia, se puede optar por la utilización de algún tipo de framework ya construido a tal efecto. Es dificil a priori decir que enfoque será el más eficiente de cara a la implementación de esta parte. Lo mejor como siempre será probar varias técnicas y evaluarlas de forma individual mediante las oportunas pruebas de volumen y rendimiento. Si se espera que una tabla tenga miles de registros es mejor probarla con un millón, y si se espera que tenga un millón probarla con varios de ellos. Conocer en este punto las características particulares de almacenamiento proporcionado por el gestor puede ayudar a decidir como guardar los datos, optimizando la creación de los índices, particiones y las formas de acceso. En este punto también es bueno construir pequeñas herramientas auxiliares para la generación automática de registros. Las base de datos suele proporcionar meta-información acerca de la misma, de forma que a través de una consulta SQL normal se puede recuperar el nombre de las tablas, de las columnas, y sus tipos. Con esa información es fácil hacer un programa que inserte de forma automática registros con información aleatoria con la que rellenar facilmente una tabla. Incluso existe software comercial especializado en ese tipo de tareas; muy útil para realizar "demos" con nombres, direcciones y teléfonos de clientes ficticios, por ejemplo. La actualización diferida de la base de datos es otro punto que quizás debería tenerse en cuenta desde el principio. Es lógico pensar que toda sentencia a ejecutar acabe teniendo una prioridad. No deberá ser lo mismo actualizar el modelo para reflejar un daño inmediato que está sufriendo un personaje, que actualizar la dirección en la que mira. En algunos casos puede que realmente ambas tengan la misma prioridad, en otros no. Además, si los cambios afectan a columnas de una misma tabla, de un mismo registro, entonces realizar las dos actualizaciones al unísono puede ser incluso mejor. Y todo ello sin perder de vista que lo importante es conseguir que el estado del mundo virtual quede siempre coherentemente reflejado en la base de datos. Y en fin, esta es sólo la punta del iceberg. Más allá de la base de datos queda una lista enorme de cosas por hacer. Posiblemente lo siguiente sería estudiar la conectividad entre los clientes y el servidor, o servidores, o entre los propios clientes entre sí (para un chat de voz por ejemplo). Decidir que protocolo de red usar (la eterna discución TCP vs UDP). E incluso definir un protocolo propio para el intercambio de mensajes tratando de minimizar el volumen de información intercambiada (usando coordenadas relativas por ejemplo). Y así un largo, largo, largo, etcétera.
Juan Mellado, 20 Diciembre, 2008 - 09:55
En los juegos de ambientación medieval, como es el caso de PlaneShift, es bastante normal que se permita a los jugadores desarrollar algún tipo de profesión mediante la cual puedan crear objetos propios empleando elementos clásicos de esta época, como yunques o forjas por ejemplo. Estas profesiones, así como los objetos que pueden crearse, están también almacenados en base de datos. Por un lado hay un primer grupo de tablas donde están las habilidades que pueden desarrollarse, junto con los instructores que las enseñan, y por otro lado hay un grupo de tablas con los procesos y patrones (recetas, fórmulas, ...) disponibles. Me ha llamado más la atención este segundo grupo, así que este artículo sólo toca esa parte en concreto. Algunas de las tablas de base de datos referidas a este tema son de las pocas que están comentadas, aunque sólo dentro de los propios scripts de creación, los comentarios no forman parte de la definición de las mismas. ![]() Una "transformación" (transformation) define como un jugador puede crear un nuevo objeto a partir de otro. Cada transformación se compone de un "patrón" (pattern) y de un "proceso" (process). Por ejemplo, una transformación podría consistir en la creación de una espada mediante la fundición y posterior tratamiento de varias menas de hierro. La tabla de transformaciones en base de datos tiene dos primeras referencias, una a la tabla de patrones y otra a la tabla de procesos. A continuación sigue un referencia a la tabla de objetos, para indicar que tipo de objeto se crea, y el número de ellos que se genera cada vez. Después sigue una referencia al tipo de objeto a transformar, junto con el número requerido de ellos. Luego está la cantidad de tiempo necesaria para completar la transformación (aunque la columna se llama "points"), un porcentaje a aplicar sobre la calidad del objeto creado, y cierra la típica columna de descripción. La tabla de patrones por su parte tiene varias columnas de significado claro, como el ID para la clave primaria, el nombre corto de la receta, la descripción larga, una referencia a la tabla de objetos para indicar cual de ellos permite crear, y un ID de grupo para los patrones de un mismo tipo. Sin embargo, tiene otra columna llamada "k_factor" cuyo significado no queda claro atendiendo sólo a su declaración. Puede estar relacionada con las "Knowledge Areas", o "áreas de conocimiento" que tiene el juego y mediante las cuales se controlan las distintas habilidades necesarias para el desarrollo de profesiones. Otra columna de significado un tanto confuso es la del tipo de objeto generado, ya que es opcional, por lo que no se ha interpretar al pie de la letra. Es decir, un mismo patrón se puede utilizar para crear más de un tipo de objeto. No todo patrón corresponde con una única receta, diseño o fórmula. El ejemplo claro de esto es el patrón llamado "Baking" (hornear) que está asociado a un montón de transformaciones culinarias distintas. La tabla de procesos tiene un número de "subproceso", para la elaboración de objetos en varias fases. Un par de columnas con los nombres de la animación y el efecto gráfico a mostrar mientras se elabora el objeto. Una referencia al objeto necesario para llevar a cabo el proceso, como un yunque por ejemplo. Y otra referencia más a la tabla de objetos para indicar esta vez el tipo de objeto que debe llevar equipado el personaje, como un martillo de herrero por ejemplo. A continuación sigue una referencia a una tabla de "restricciones" (constraints), que son condiciones especiales que deben darse para la ejecución del proceso. Una nueva referencia a la tabla de objetos señala a un tipo de objeto "basura" (residuo) que se genera si la transformación no se produce correctamente, junto con el número que de estos objetos pueden llegar a generarse. Después hay dos grupos de cinco columnas cada uno, las cinco primeras relacionadas con la habilidad primaria que debe poseerse para realizar la transformación, y las cinco segundas, de idéntico significado, para una posible habilidad secundaria. Estas columnas incluyen una referencia a la tabla de habilidades, un valor mínimo y máximo de habilidad requerida, un valor de puntos de aprendizaje ganados por el personaje al realizar la transformación, y un porcentaje que afecta a la calidad del objeto de forma directamente proporcional a la habilidad del personaje que lo crea. Cierra de nuevo la tabla la típica columna de descripción. La tabla de restricciones almacena las condiciones que deben darse para que los jugadores puedan ejecutar los procesos. Es una tabla muy sencilla, y en los registros de ejemplo se observa que son restricciones de tipo temporal ("You can not complete the work at this time of the day!"), de ubicación ("You can not finish this work here!"), y así sucesivamente. En el campo de descripción de esta tabla se indica que se pueden indicar parámetros concretos, como una hora del día o lugar concreto en el que tiene que estar el personaje. Una "combinación" (combination) define como un jugador puede crear un nuevo objeto mediante la agrupación de una cantidad determinada de otros. Es similar a una transformación, con la diferencia que no aplica un proceso, sólo un patrón. Las columnas de la tabla de combinaciones son muy similares a las de la tabla de transformaciones. Patrón, objeto resultante, cantidad resultante, objeto de partida, mínima y máxima cantidad de objetos de partida, y una descripción. La última tabla del modelo de esta parte almacena lo que se denominan "autocontenedores" (autocontainers). Un nombre un tanto extraño quizás. Mirando los registros de ejemplo se observa que contiene los objetos utilizados para crear otros, como por ejemplo los yunques y las forjas. La columna más importante a mi juicio es la que hace referencia a la tabla de objetos instanciados en el mundo virtual, lo que define su ubicación concreta dentro del mundo, junto con el resto de atributos que tienen los objetos en general. Es decir, que esta tabla se utiliza para modelar físicamente la herencia, como una clase especializada de objetos.
Juan Mellado, 13 Diciembre, 2008 - 11:01
Los personajes de PlaneShift pueden lanzar distintos hechizos, conjuros, efectos, y toda esa clase de magias que uno espera encontrar en este tipo de juegos. El modelo de datos es bastante sencillo, y a grandes rasgos, consta de una tabla de definición de los hechizos disponibles, asociada a sus tipos y demás, junto con una tabla que los relaciona con los personajes. La verdadera dificultad de esta parte siempre he pensado que deberá estar en el proceso de cálculo en tiempo real del resultado de todos esos efectos lanzados por varios personajes y NPCs a un mismo tiempo. ![]() La tabla maestra de hechizos almacena el detalle de cada uno de ellos. Como de costumbre, el nombre de las columnas permite identificar el uso que se le da a cada una de ellas, aunque para alguna se requiere tener un mayor conocimiento del funcionamiento del juego. Además del habitual ID para la clave primaria, el nombre del hechizo ("Summon Missile", "Defensive Wind", ...), y su descripción ("A wooden arrow is summoned and thrown at the target dealing 6*P damages.", ...) destaca una relación a la tabla de escuelas de magia (ways). Para entender la intención de esta columna hay que saber que en PlaneShift existen varios tipos de estas escuelas, llamadas "Crystal", "Azure", "Red", "Dark", "Brown" y "Blue". Otras columnas de esta tabla principal de hechizos indican los efectos visuales que tienen que mostrarse al lanzarse ("caster_effect") o al alcanzarse un objetivo ("target_effect"). A partir de aquí siguen toda una serie de valores que controlan el detalle del efecto resultante. En este punto llama la atención de que no se utilicen columnas de texto con scripts al igual que para el resto de entidades del juego. Hay una columna para indicar si el hechizo es de caracter ofensivo o no, los eventos que tienen que generarse durante el lanzamiento, la potencia del hechizo, el tipo de objetivo sobre el que puede aplicarse, y alguna otra cosa más de este estilo. El detalle preciso del significado de cada uno puede irse deduciendo, pero a mi particularmente me atraería más analizar el uso específico que se hace de estos valores dentro del algoritmo de cálculo del impacto de los efectos sobre el mundo y los personajes. Actualización: Después de estar revisando el modelo mientras escribía el siguiente artículo de esta seríe, he encontrado que los scripts para el cálculo de los efectos están en la tabla "math_scripts" (no se muestra en la imagen). La estructura de la tabla es muy sencilla, apenas un nombre y el cuerpo del script en sí mismo. Por ejemplo, el script "Calculate Fall Damage" tiene el siguiente cuerpo: exit = (rnd(1) > 0.5); Asociada a la tabla de hechizos existe una tabla de relación con los "glyphs" (¡menuda palabreja!), que son objetos de caracter mágico que se deben combinar para poder lanzar los hechizos. En la práctica simplemente relaciona la tabla de hechizos con la tabla de objetos, incluyendo una columna para indicar la posición concreta dentro del inventario correspondiente donde se encuentra cada objeto. La última tabla de este sencillo modelo es prácticamente igual a la anterior, sólo que esta vez sirve para relacionar la tabla de hechizos con la tabla de personajes, incluyendo igualmente una columna para indicar la posición que ocupa dentro del inventario correspondiente.
Juan Mellado, 6 Diciembre, 2008 - 09:51
Después de matar una criatura en un mundo virtual, lo normal es despojarla (loot) de todo lo que lleva. La recompensa que se obtiene en cada caso suele ser distinta en función de la criatura en cuestión. En el modelo de datos de PlaneShift hay una serie de tablas que definen las reglas mediante las cuales se calcula el dinero y los objetos que puede llegar a obtener el personaje. Y escribo "puede" porque las recompensas no suelen ser fijas, normalmente están sujetas a unos porcentajes de probabilidad. ![]() La tabla principal con las reglas apenas tiene un ID y un nombre. Este último supongo que a modo de referencia para no perderse entre tanta regla. Incluso tiene definido un índice único sobre él. Lo importante del asunto es que es a esta tabla a la que apunta la tabla de creación de NPCs, de forma que cada uno de ellos tiene asociado una regla de loot. A su vez cada regla de loot puede tener varias reglas de detalle, de forma que un mismo NPC puede soltar (drop) varios objetos distintos. Toda la chicha del asunto está en la tabla de detalle, en ella se encuentra una referencia a un objeto, un porcentaje de probabilidad de aparición de dicho objeto, una cantidad mínima y máxima de dinero, y una última columna que parece indicar la aplicación o no de un factor de aleatoriedad. El tipo de la columna de la probabilidad es un FLOAT con un entero y cuatro decimales, lo que parece indicar que se almacena como un tanto por uno con un número elevado de decimales con el fin de ajustar al máximo la probabilidad de los objetos de mayor rareza. El hecho de que aparezcan columnas con referencia a objetos y dinero a la vez en esta misma tabla es un poco curioso. Supongo que estará implementado así para evitar tener la información separada en tablas distintas, una para los objetos y otra para el dinero. El problema es que a veces sólo tendrá sentido la columna que hace referencia al objeto, otras veces las que hacen referencia al dinero, y otras tendrán sentido ambas. Las referencias nulas tendrán que obviarse, y las cantidades de dinero resultante de cada registro de detalle sumarse. La tercera tabla en juego es bastante peculiar, he de confensar que no me la esperaba, tiene un enfoque que no había considerado nunca. La idea es que almacena "modificadores" a aplicar sobre los objetos devueltos como recompensa. Estos modificadores actúan sobre los atributos básicos de los objetos, mejorándolos o empeorándolos según sea el caso de forma aleatoria. De esta forma no hace falta tener creado un objeto de cada tipo en la base de datos, sino que se tiene un único objeto base, y lo que se instancia para su entrega al personaje es un objeto modificado según los criterios que dictamina esta tabla. Mejor verlo con ejemplos. En la columna "name" se almacenan los nombres de los modificadores, tales como "Platinum", "Steel", "of Strength" u "of Purity". En la columna "modifier_type" se almacena el tipo de modificador, como "prefix" o "suffix", para indicar que la modificación quedará reflejada al principio o final del nombre del objeto respectivamente. Y de esta forma una espada básica (Sword) puede transformarse en una espada de acero (Steel Sword) utilizando un prefijo, o en una espada de pureza (Sword of Purity) utilizando un sufijo. En la columna "effect" se almacenan los efectos resultantes del modificador, como por ejemplo un incremento del daño causado del 30% (<ModiferEffect operation="mul" name="item.damage" value="1.30" />). Al estar escritos en formato de texto legible es bastante fácil de entender como funciona. El resto de columnas de la tabla incluyen un factor de probabilidad (en tanto por cien al parecer esta vez), los valores de atributos requeridos para utilizar el objeto, un coste del modificador (que no sé a que se refiere), y cuatro columnas más con una referencia a la malla gráfica a mostrar, incompatibilidades con otros objetos, y los eventos que deben desencadenarse cuando el personaje se equipe o quite el objeto.
Juan Mellado, 29 Noviembre, 2008 - 09:00
NPC es el acrónimo de "Non-Player Character", un término que se utiliza para denotar a los personajes que no manejan directamente los jugadores, sino el juego. Todas las criaturas que pueden encontrarse en los mundos virtuales, ya sean monstruos hostiles, codiciosos vendedores, o experimentados instructores, entran dentro de esta categoría. Muchos jugadores se refieren a ellos también con el término "mobs", una abreviatura de "mobile objects", sobre todo cuando son criaturas contra las que se puede pelear. En el modelo de base de datos de PlaneShift los NPCs reciben, a grandes rasgos, el mismo tratamiento que los personajes manejados por los jugadores. No obstante, existen tablas específicas donde se almacenan las características particulares de este tipo de entidades, como las reglas que definen el proceso de regeneración automática de los mismos (spawn), y las recompensas que pueden obtenerse al despojar sus cuerpos tras un combate (loot). ![]() La tabla de reglas de spawn almacena, entre otros, el detalle del rango de tiempo que debe transcurrir entre la muerte y resurrección de un NPC, la posición en la que debe regenerarse, y una referencia a las reglas de loot que deben aplicársele. Respecto a la posición de aparición, se distinguen dos casos. Por un lado los NPCs que tienen un lugar concreto fijo de aparición, y por otro lado los que tienen un rango de posiciones más amplio donde aparecer. En el primer caso se suministran las coordenadas espaciales concretas en la propia tabla, junto con una referencia a un sector del mundo, y la dirección inicial en la que debe mirar la criatura. En el segundo caso existe una tabla auxiliar con los posibles rangos de aparición que almacena pares de coordenadas que definen regiones. Para entender como se interpretan esos pares de coordenadas hay que mirar el valor de la columna "range_type_code", que en los registros de ejemplo toman los valores "A" o "L", lo que parece indicar que las dos coordenadas delimitan una "Área" rectángular, o un segmento sobre una "Línea" recta. Interesante solución. Otras columnas de estas tablas tienen un significado más complicado de entender atendiendo sólo a la definición del modelo. Como ya he comentado alguna que otra vez, PlaneShift es un juego en el que sus desarrolladores tratan de dar prioridad al "role playing". No es un juego para ir corriendo de un lado a otro. La interacción con los NPCs es a través de diálogos escritos, y ello implica que el sistema ha de ser capaz de reconocer los textos que introducen los jugadores. Para ello existe una serie de tablas entre las que se incluye un diccionario de sinónimos. Gracias a ella, se puede abordar a un NPC con "hi", "hello", "bonjour" o "buenos días" de forma indistinta, por poner sólo algunos ejemplos. Otras tablas incluyen una lista "negra" de palabras no permitidas, y un registro de cuando algún jugador las pronunció. Todo el proceso de interacción de los personajes con el mundo en este tipo de juegos se basa en el concepto de "trigger", cuya traducción literal sería "disparador". Cuando un jugador aborda a un NPC, o interacciona de alguna forma con un objeto, se dispara un evento como respuesta. En el caso de los NPCs, sus respuestas a los comentarios o preguntas que les hacen los jugadores están controladas por los registros almacenados en las correspondientes tablas de triggers. Hay una primera tabla de grupos que aglutina tipos de acciones equivalentes, una segunda con las frases concretas con los que los jugadores pueden provocar dichas reacciones, como "greetings" o "give me fruit", y una tercera que almacena las respuestas que provocan en los NPCs, como "Hello friend" o "I have the following fruits: pear, plum". En esta última tabla de respuestas se puede ver que existen cinco columnas para otras tantas posibles respuestas que se deben escogerse aleatoriamente. De igual forma, se aprecian dos columnas de tipo BLOB, una para establecer precondiciones, como la de pertenecer a una facción concreta por ejemplo, y otra para almacenar scripts que desencaden efectos o acciones concretas, como el proceso de asignación de una quest por ejemplo. Alguna que otra tabla suelta que he puesto en la imagen incluye información relacionada con los NPCs, como las "KAs" (Knowledge Areas), o áreas de conocimiento, que son las distintas habilidades o profesiones que pueden desarrollar los personajes en el juego. |