Aetheria Game Engine fue pensado inicialmente como sistema para crear juegos de rol de texto mono y multijugador, y por ello cuenta con un completo sistema para simular combates. Por supuesto, este sistema se puede obviar y no utilizar, de ahí que AGE también sirva perfectamente para crear juegos de aventura sin elementos de rol y que, de hecho, la mayoría de los juegos creados hasta hoy en AGE sean de este último tipo. Sin embargo, para quien esté interesado en hacer que su mundo permita emocionantes duelos a espada contra orcos, será interesante leer esta sección.
El sistema de combate de AGE está pensado fundamentalmente para combate estilo medieval y de fantasía, con armas cuerpo a cuerpo y magia. Sin embargo, al tratarse de un sistema muy genérico, también es posible adaptarlo a otras situaciones como pueden ser tiroteos con armas de fuego.
El combate en AGE está fuertemente basado en el sistema de temporización, así como en los estados de las criaturas. Si no recuerdas bien el contenido de esas dos secciones, es recomendable echarles un vistazo antes de seguir con ésta.
Como la inmensa mayoría de los juegos de rol por ordenador (CRPG's), el sistema de combate de AGE utiliza el modelo de recuento del daño por el cual una criatura cuenta con un determinado número de puntos de golpe o puntos de vida (abreviados como HP, del inglés “hit points”). Cuando la criatura recibe daño, por ejemplo por ser golpeada con un arma, pierde puntos de vida (y a los puntos de vida que se restan de su cifra total se les llama puntos de daño). Los puntos de vida, pues, miden el nivel de salud que le queda a una criatura, y si en algún momento llegan a cero o menos, la criatura muere.
Por ejemplo, supongamos que un jugador tiene diez puntos de vida. Si un orco le ataca con un hacha infligiéndole seis puntos de daño, estos puntos de daño se restan de sus diez puntos de vida, y por lo tanto el jugador se queda con sólo cuatro puntos de vida. Si a continuación el orco vuelve a atacarle y esta vez le inflige siete puntos de daño, el jugador se queda con 4-7=-3 puntos de vida. Como esta cantidad de puntos de vida es menor o igual (en este caso menor) que cero, el jugador muere.
En AGE, podemos definir cuántos puntos de vida tiene una criatura mediante el panel de criatura de PUCK:
Así pues, si ponemos la misma cifra en el campo “HP” que en el campo “HP máx”, esto significará que la criatura está inicialmente saludable. Si ponemos una cifra menor en “HP” que en “HP máx”, significará que la criatura ya comienza con algún daño.
Tanto “HP” como “HP máx” deben tener inicialmente un valor mayor que cero. No hay límite superior (al menos mientras no te pases de un par de miles de millones), así que puedes usar distintas escalas: puedes preferir un mundo donde las criaturas tengan pocos puntos de vida (que un hombre fuerte tenga diez, y un golpe impresionante haga cinco puntos de daño) o donde tengan muchos (que un hombre tenga mil puntos de vida, y un golpe pueda hacer fácilmente doscientos de daño). Lo importante para el programador será equilibrar su juego de forma que el combate resulte realista, es decir, que el daño que hacen las armas guarde una buena proporción con la vida que tienen las criaturas.
Los puntos de vida también se pueden obtener y modificar dinámicamente mediante código BeanShell:
/*clase Mobile*/ int getHP ( )
Devuelve la cantidad de puntos de vida que tiene actualmente la criatura sobre la que se invoca.
/*clase Mobile*/ void setHP ( int newHP )
Cambia la cantidad de puntos de vida que tiene la criatura sobre la que se invoca a la cantidad newHP
.
/*clase Mobile*/ int getMaxHP ( )
Devuelve la cantidad de puntos de vida máximos de la criatura sobre la que se invoca.
/*clase Mobile*/ void setMaxHP ( int newMaxHP )
Cambia la cantidad de puntos de vida máximos que tiene la criatura sobre la que se invoca a la cantidad newMaxHP
. Esto se puede utilizar, por ejemplo, para implementar subidas de nivel en juegos de rol basados en niveles. Este método estará disponible a partir de la versión 1.1.7 de AGE.
Otro elemento típico en muchos juegos de rol son los atributos y las habilidades.
Los atributos son valores numéricos que describen características físicas e intelectuales genéricas de un personaje. Por ejemplo, atributos que se suelen utilizar a menudo en juegos de rol son “fuerza”, “constitución”, “destreza”, “inteligencia” o “carisma”.
Las habilidades, por otra parte, son valores numéricos que describen la práctica o el talento que un personaje tiene para realizar alguna actividad específica. Ejemplos de posibles habilidades serían “espada”, “trepar”, “pescar”, “violín” o “diplomacia”.
AGE proporciona, por un lado, un sistema genérico para gestionar los valores de los atributos y habilidades, que es muy sencillo y prácticamente no hace nada más que almacenar los valores y permitirnos cambiarlos. Por otra parte, AGE también proporciona mecánicas específicas asociadas a habilidades de combate y magia.
Esto quiere decir que AGE nos proporciona toda la infraestructura necesaria para utilizar las habilidades y atributos en el combate: por ejemplo, el hecho de que la probabilidad de acertar en un ataque con un arma y el tiempo que nos consume el ataque dependan de nuestra habilidad con esa arma, o el hecho de que el utilizar repetidamente el arma haga que nuestra habilidad con ella aumente con el tiempo. Veremos más detalles sobre esto en las siguientes secciones.
Por otra parte, si queremos utilizar las habilidades y atributos para funcionalidad no relacionada con el combate (como podría ser usar una habilidad de “violín” para determinar si un personaje cautiva a su audiencia en un concierto o no); AGE sólo nos proporciona la infraestructura básica para almacenar y obtener los valores de esas habilidades y atributos, el resto (por ejemplo, el código para decidir si el personaje consigue tocar una pieza determinada o no según su habilidad con el violín) tendremos que ponerlo nosotros como programadores de aventuras.
La mencionada infraestructura básica para almacenar y obtener valores de habilidades y atributos consiste en un formulario de PUCK y una serie de métodos invocables desde código BeanShell.
El formulario es la ficha de “Características” del panel asociado a un personaje, y nos permite fijar los valores iniciales de habilidades y atributos para ese personaje. Para ello, escribimos el nombre de la habilidad o atributo (por ejemplo, “violín”) en el campo “Nombre” y su valor numérico (por ejemplo, 30) en el campo “Valor”, y pulsamos el botón “Añadir”. Si queremos rectificar el nombre y valor de un atributo, podemos hacerlo seleccionándolo en la lista, modificando los datos en los campos “Nombre” y “Valor”, y pulsando “Cambiar”.
Adicionalmente, AGE proporciona los siguientes métodos en la clase Mobile para manipular atributos y habilidades:
/*clase Mobile*/ int getStat ( String name )
Devuelve el valor numérico del atributo de nombre dado, por ejemplo mobile(“manolo”).getStat(“inteligencia”) devolverá la inteligencia de Manolo. Nótese que, si no hemos asignado ningún valor a un atributo, este método devolverá por defecto un valor de 12 (el tradicional valor por defecto de los atributos en juegos basados en Dungeons & Dragons).
/*clase Mobile*/ void setStat ( String name , int value )
Cambia el valor numérico del atributo de nombre name
por el nuevo valor value
.
/*clase Mobile*/ long getSkill ( String name )
Devuelve el valor numérico de la habilidad de nombre dado, por ejemplo mobile(“manolo”).getStat(“violín”) devolverá la habilidad de Manolo para tocar el violín. En este caso, si no se había fijado el valor de esa habilidad, el valor devuelto por defecto será 0 (¡nadie sabe tocar el violín sin aprender!)
/*clase Mobile*/ void setSkill ( String name , long value )
Cambia el valor numérico de la habilidad de nombre name
por el nuevo valor value
.
Nótese que, aunque los atributos y habilidades no están implementados como propiedades, se comportan de manera muy similar a éstas. De hecho, para implementar una habilidad que afecte a alguna actividad externa al combate, se podría implementar igualmente con una propiedad. Para las relacionadas con el combate, sin embargo, es necesario emplear este sistema de habilidades y atributos si se quiere aprovechar toda la funcionalidad de combate que se describirá más abajo.
El sistema de combate en AGE funciona, grosso modo, de la siguiente manera:
A la luz de esta descripción, si quieres (aunque no es realmente necesario para implementar combates, si no quieres hacer cosas avanzadas) puedes volver a mirar los estados relacionados con el combate presentes en la tabla de la sección sobre Las propiedades "state" y "target" de las criaturas. Como comprobarás, dichos estados se usan para implementar lo que acabamos de describir:
Pero en esta descripción todavía faltan varios cabos sueltos por explicar, concretamente:
Una respuesta rápida y resumida a los tres últimos cabos sueltos es que AGE calcula estos tres resultados usando una combinación de tres tipos de factores: factores dados por el arma, factores dados por las habilidades y atributos del jugador que maneja el arma, y por último factores aleatorios que sirven para dotar de un cierto grado de incertidumbre e imprevisibilidad al combate. Todos estos factores se explican en la siguiente sección, que trata de las armas.
Un arma es cualquier cosa (de la clase Item
) que un personaje puede utilizar para lanzar y/o bloquear ataques. Para crear un arma en PUCK, empezaremos creando una cosa de forma normal, con sus nombres, descripciones y demás valores que ya conocemos. Para indicar que se trata de un arma, seleccionaremos la pestaña “Arma” de su panel de entidad, y activaremos la casilla etiquetada “Es arma”, que indica que la cosa es un arma. El resto del formulario de la pestaña “Arma” nos permitirá configurar en detalle el arma, como explicaremos en el resto de esta sección.
Una primera cosa a tener en cuenta es que existen dos tipos de armas según la manera en que funcionan: armas naturales y armas externas. Las armas naturales son partes del cuerpo de una criatura que ésta puede usar como arma (como por ejemplo un puño, que sirve para dar puñetazos). Las armas externas, las más comunes, son objetos o herramientas que se usan como armas, como puede ser una espada, un hacha o un escudo.
Para que una cosa funcione como arma natural, necesitaremos definirla como miembro de una criatura (véase la sección sobre miembros para recordar cómo se hacía esto). AGE interpreta automáticamente que cualquier cosa que esté marcada como arma (casilla “Es arma”) y sea un miembro de una criatura es un arma natural. Por otra parte, cualquier cosa que esté marcada como arma pero no sea un miembro de una criatura funcionará como arma externa.
Para utilizar un arma externa, un personaje debe blandirla, cosa que ocupará uno o varios de sus miembros: por ejemplo, una daga se puede blandir en una mano, mientras que una espada de dos manos se podrá blandir -como su nombre indica- ocupando ambas manos. El sistema para blandir y enfundar armas es totalmente análogo al sistema visto en la sección de prendas para vestir y desvestir prendas, es decir:
blandir arma
y enfundar arma
, que funcionan de manera análoga a vestir prenda
y desvestir prenda
.true
y temporizador -1 (además de meter el arma en el inventario). /*clase Mobile*/ Inventory getWieldedWeapons()
Este método devuelve una lista de todas las armas que está blandiendo la criatura sobre la que se invoca. El inventario devuelto es de sólo lectura, es decir, quitarle y ponerle cosas no tendrá ningún efecto sobre lo que lleva puesta la criatura (para esto, debemos usar las relaciones).
/*clase Mobile*/ Item getWieldedItem( Item limb )
Devuelve el arma que lleva blandida la criatura sobre la que se invoca en el miembro dado por limb
o, si no lleva ningún arma ahí, devuelve null
.
/*clase Mobile*/ boolean wieldsItem( Item item )
Sirve para comprobar si la criatura sobre la que se invoca está blandiendo el arma dada como parámetro. Devuelve true
si la lleva blandida (en cualquiera de sus miembros) y false
de lo contrario.
Hasta aquí, hemos visto cómo hacer que los jugadores y criaturas puedan blandir y enfundar armas. Pero, por supuesto, lo más importante de las armas es… ¡combatir con ellas!
Los parámetros de combate de las armas se definen en la mitad inferior de la pestaña “Arma”, donde hay un formulario dividido a su vez en pestañas “Ataque” y “Bloqueo”. Como su nombre indica, la pestaña “Ataque” sirve para definir cómo se comportará el arma al lanzar ataques, mientras que en la pestaña “Bloqueo” se puede definir su comportamiento al bloquear con ella.
A continuación se explica brevemente lo que significa cada campo del formulario de “Ataque”:
double
e indica en qué medida influye cada una de las habilidades, de forma que la habilidad de un personaje para usar el arma será la suma del producto de la puntuación que tiene en cada una de estas habilidades por el campo “valor”.El formulario de bloqueo permite definir los mismos valores que el de ataque, sólo que referidos a la acción de bloquear con el arma. Así, el “uso mínimo” y la “pendiente de probabilidad” del bloqueo determinarán la probabilidad de bloquear con éxito. El “tiempo de bloqueo” y “tiempo de recuperación del bloqueo” definen el tiempo que se tarda en preparar un bloqueo y en volver a tomar la iniciativa tras llevarlo a cabo. El “daño bloqueado” nos permite especificar fórmulas para el daño que absorbemos con el bloqueo (que se resta al del ataque que nos hayan hecho, quedando en cero si el daño absorbido es mayor que el daño que ha hecho el ataque). Por último, el formulario de “habilidades” nos permite definir qué habilidades son relevantes para (y se entrenan al) bloquear con el arma, que podrían o no ser las mismas que las correspondientes al ataque.
Si bien con lo dicho en la sección anterior es suficiente para definir armas que se puedan usar en aventuras con combates, sin duda algunos usuarios estarán interesados en los detalles de las fórmulas matemáticas que AGE utiliza para calcular tiempos de acciones de combate y probabilidades de éxito.
Como se ha dicho anteriormente, se supone que el valor de una habilidad es una estimación lineal del tiempo de experiencia que tiene el personaje utilizando esa habilidad. Para obtener estimaciones realistas de tiempos y probabilidades de éxito de ataques a partir de ese valor, necesitaremos aplicar una función creciente pero de derivada negativa, de acuerdo con el concepto intuitivo de “ganancias decrecientes” que tenemos en la vida real: inicialmente el aprendizaje de una nueva habilidad suele ser muy rápido; pero con el tiempo el perfeccionamiento es mucho más lento y laborioso.
La fórmula que utiliza AGE para esto en el caso de los tiempos de ataque, bloqueo y otras acciones de combate es la siguiente:
tiempo = tiempoBase / ( ( (habilidad - uso mínimo) / 100 + 1 ) ^ ( e^pendiente ) )
que, para hacerse una idea, tiene la forma que sigue (para tiempos base de 25 y 50, y pendientes de -1, 0 y 1):
Como vemos, el jugador no tardará en cogerle el truco al arma y reducir significativamente el tiempo que le lleva usarla, pero más adelante el aprendizaje irá siendo cada vez más paulatino.
Sin embargo, esa fórmula no lo es todo: para introducir un cierto grado de variabilidad e incertidumbre en el combate, al tiempo obtenido de la fórmula se le aplica cada vez una variación aleatoria, sacada de una distribución gaussiana de media 0 y desviación típica 1/3 t, siendo t el tiempo obtenido de la fórmula. Con lo cual, el realidad, el comportamiento tendría más bien este aspecto:
Como podemos ver, la incertidumbre que añade la variación gaussiana hace que, aunque los combatientes experimentados de un arma ataquen más rápido en general, puede haber casos en los que por una cuestión de suerte un novato consiga un ataque más rápido que un veterano. Esto añade imprevisibilidad al combate.
Para la probabilidad de éxito de los ataques y bloqueos se utiliza una fórmula basada en el mismo principio, que es:
probabilidad = 1 - 1 / ( ( (habilidad - uso mínimo) / 100 + 1 ) ^ ( e^pendiente ) )
Dándonos unas gráficas como éstas (para pendientes de -1, -0.5, 0, 0.5 y 1):
Como vemos, la probabilidad tiende rápidamente a 1 (tener éxito siempre) en el caso de pendiente 1, pero no tan rápido con pendientes más difíciles.
En el caso de la probabilidad, ya que ella misma funciona como factor para añadir aleatoriedad e incertidumbre, no añadimos ninguna variación gaussiana.
Con lo visto en la sección sobre armas, ya sabemos cómo se calculan las probabilidades de éxito y tiempos de realización de ataques y bloqueos; pero no hemos visto cómo se calculan estos parámetros para las esquivadas.
Por el momento, la implementación de las esquivadas en AGE aún no está muy perfeccionada, así que es sencillo: la probabilidad de realizar una esquivada con éxito es siempre del 20%, y esquivar consume siempre 15 unidades de tiempo.
En futuras versiones, es posible que esto se complique un poco más.
Las armaduras no son más que prendas que tienen la capacidad especial de reducir el daño que quien las viste recibe de ataques dirigidos al miembro donde las lleva.
Para crear una armadura en PUCK, no tenemos más que hacer lo siguiente:
Una armadura entra en acción cuando un ataque impacta en el miembro donde el personaje la lleva. Si bien en AGE no existen por defecto comandos específicos para atacar a un miembro dado (“golpea a Fulano en la cabeza”, etc.); se supone que cada ataque se dirige a un miembro determinado. Este miembro se escoge de forma aleatoria, pero de modo que la probabilidad de elegir cada uno de los miembros de un personaje es proporcional al volumen del miembro. Se pueden definir miembros de volumen 0 si se quiere que nunca reciban ataques (por ejemplo, si queremos poder llevar un anillo en el dedo, pero que nunca nos lancen un ataque al dedo, cosa que sonaría bastante rara).
Esto quiere decir que, por ejemplo, si en un personaje definimos los miembros “cabeza” con volumen 10, “cuerpo” con volumen 15, “brazo izquierdo” con volumen 5 y “brazo derecho” con volumen 5, entonces de cada 35 ataques que reciba el personaje irán como promedio 10 dirigidos a la cabeza, 15 dirigidos al cuerpo, 5 al brazo izquierdo y 5 al brazo derecho. Si ese personaje lleva un casco puesto en la cabeza, ese casco le protegerá de esa proporción de ataques que van dirigidos a la cabeza.
La reducción de daño otorgada por una armadura se añade a la reducción otorgada por el bloqueo, si el personaje ha bloqueado.
Ahora que sabemos cómo funciona exactamente el combate, vamos a ver cómo podemos utilizarlo, es decir, cómo hacemos que un personaje de un juego en AGE luche contra malvados monstruos.
Cuando esté en la misma habitación que otra criatura, un personaje jugador siempre puede poner el comando atacar criatura
para lanzar un ataque. Por defecto, las criaturas de AGE están hechas de manera que, si tienen medios para defenderse (algún arma), siempre entrarán en combate si las atacan, lanzando ataques, bloqueando o esquivando según consideren oportuno.
Nótese que las órdenes por defecto para bloquear y esquivar son bloquear criatura
y esquivar
(no hace falta especificar una criatura concreta para esquivar, porque se supone que nos apartamos de cualquier ataque que nos estén lanzando en ese momento).
Si queremos que sea una criatura no jugadora la que empiece la pelea, podemos hacerlo agregándole enemigos. Toda criatura en AGE tiene una lista de enemigos con los que entrará en combate si los ve. Podemos manipular esta lista mediante los siguientes métodos:
/*clase Mobile*/ void addEnemy ( Mobile nuevo )
Añade a nuevo
como enemigo del Mobile
sobre el que invocamos este método.
/*clase Mobile*/ boolean removeEnemy ( Mobile viejo )
Quita viejo
de la lista de enemigos del Mobile
sobre el que invocamos este método y devuelve true
, si estaba en la lista. En caso de que no estuviese en la lista, este método no hace nada y devuelve false
.
Así, por ejemplo, si tenemos un orco y hacemos
mobile("orco").addEnemy(mobile("manolo"));
el orco atacará automáticamente a Manolo cuando éste aparezca en su localidad.
Por supuesto, utilizando los métodos asociados a las criaturas y los métodos update en conjunto con la propiedad "state", es posible definir comportamientos más complejos de los monstruos, como que persigan a su enemigo o patrullen una zona buscándolo.