paint-brush
Un sistema de habilidades más complejo, pero un flujo de trabajo de desarrollo de juegos más fluido: ¿cómo?por@pastaman
1,150 lecturas
1,150 lecturas

Un sistema de habilidades más complejo, pero un flujo de trabajo de desarrollo de juegos más fluido: ¿cómo?

por Vladimir Popov9m2024/02/08
Read on Terminal Reader

Demasiado Largo; Para Leer

El sistema de habilidades de War Robots experimentó una evolución significativa, pasando de una estructura simplista a un modelo sofisticado. El nuevo sistema empodera a los diseñadores de juegos y reduce la dependencia de los programadores, lo que resulta en habilidades mecánicas más diversas y experiencias de jugador enriquecidas.
featured image - Un sistema de habilidades más complejo, pero un flujo de trabajo de desarrollo de juegos más fluido: ¿cómo?
Vladimir Popov HackerNoon profile picture


¡Hola! Soy Vladimir Popov, desarrollador de clientes del proyecto War Robots. En el momento de escribir este artículo, War Robots existe desde hace varios años y durante este tiempo han aparecido en el juego docenas de nuevos mechs. Naturalmente, las variadas habilidades de los robots son importantes porque, sin ellas, los robots pierden su singularidad, lo que hace que el juego sea más interesante.


En esta publicación, compartiré cómo funciona el sistema de habilidades de juego en nuestro juego y cómo ha evolucionado. Y, para hacerlo más accesible, lo explicaré en términos simples y sin demasiados detalles técnicos.


Cómo solían implementarse las habilidades

Primero, profundicemos en el historial del proyecto y observemos una implementación anterior que ya no se utiliza.


Anteriormente, las habilidades se diseñaban de una manera muy trivial: tenían un componente que estaba conectado al robot. Esta fue una construcción donde el programador describió completamente cómo funciona la habilidad: tanto su flujo como cómo interactúa con otras habilidades. Toda la lógica se describe dentro de un componente, y el diseñador del juego podría simplemente conectarlo al robot y configurar los parámetros según sea necesario. Vale la pena mencionar que no fue posible cambiar el flujo de habilidades: los diseñadores del juego solo pudieron cambiar los parámetros y los tiempos.


Una habilidad antigua solo podría existir en dos estados: activo e inactivo, y a cada estado se le podría asignar su acción.




Veamos un ejemplo de la habilidad "Jammer", que anteriormente poseía el robot "Stalker"; funcionó así:


  1. Si la habilidad está activa, se muestra una animación y el robot ingresa al estado Jammer; En este estado, otros no pueden apuntar al robot que activa la habilidad.
  2. Si la habilidad está inactiva, no pasa nada.
  3. Al intentar activar una habilidad, comprobamos si habían pasado más de n segundos desde la última activación.
  4. La desactivación se produjo automáticamente después de m segundos.


Durante mucho tiempo, esta funcionalidad fue suficiente para nosotros, pero con el tiempo tanto los diseñadores del juego como los programadores ya no estaban satisfechos con este enfoque: a los programadores les resultaba difícil soportar estas capacidades porque el código se había vuelto monstruoso; Esto implicó una cadena de legado muy larga, donde cada situación tenía que ser descrita. Además, los diseñadores de juegos carecían de flexibilidad: para realizar cualquier cambio en una habilidad, tenían que ordenar modificaciones a los programadores, incluso si una habilidad adyacente tenía la misma funcionalidad.


Ingrese al nuevo sistema de habilidades

Entonces nos dimos cuenta de que algo necesitaba cambiar. Por lo tanto, desarrollamos un nuevo sistema, donde cada habilidad se representaba como un conjunto de varios objetos relacionados. La funcionalidad se dividió en habilidades estatales y componentes estatales.


¿Como funciona? Cada habilidad tiene un objetivo principal. Este objeto central conecta otros objetos de habilidad con el mundo exterior y viceversa; también toma todas las decisiones principales.


Puede haber cualquier número de estados. Esencialmente, el estado en esta iteración no es muy diferente de los estados activo/inactivo en la versión anterior, pero ahora puede haber cualquier número de ellos y su propósito se ha vuelto más abstracto. Notaremos que una habilidad solo puede tener un estado activo a la vez.


La principal innovación en comparación con el antiguo sistema fueron los componentes: un componente describe alguna acción y cada estado puede tener cualquier número de componentes.


¿Cómo funcionan las nuevas habilidades? Una habilidad sólo puede estar en uno de los estados; el objeto principal es responsable de cambiarlos. Los componentes que se vinculan a un estado reaccionan a la activación/desactivación del estado y, dependiendo de esto, pueden comenzar a realizar alguna acción o dejar de realizarla.


Todos los objetos se han vuelto personalizables; un diseñador de juegos puede mezclar estados y componentes como quiera, componiendo así una nueva habilidad a partir de bloques preinstalados. Ahora, los programadores solo necesitan entrar en escena para crear un nuevo componente o estado, lo que hace que escribir código sea mucho más fácil: trabajan con entidades pequeñas, describen algunos elementos simples y ya no construyen la capacidad ellos mismos: los diseñadores de juegos hacen esto ahora.


El flujo se ha vuelto así:

  1. El objeto principal activa el primer estado.
  2. El Estado activa todos sus componentes.
  3. El estado determina el momento en que la habilidad cambia a otro estado.
  4. El objeto principal desactiva el estado anterior.
  5. El estado anterior desactiva sus componentes.
  6. El objeto principal activa un nuevo estado.
  7. El nuevo Estado activa sus componentes


Posteriormente, este procedimiento se repite una y otra vez. Para facilitar su uso, un estado no solo sirve como contenedor de componentes, sino que también determina cuándo cambiar a otro estado y solicita al objeto principal que realice el cambio. Con el tiempo, esto resultó no ser suficiente para nosotros y el diagrama de habilidades se transformó en el siguiente:




El objeto principal, el estado y los componentes permanecieron en sus lugares, pero también se agregaron nuevos elementos.


Lo primero que llama la atención es que agregamos condiciones a cada estado y componente: para los estados, estas definen requisitos adicionales para salir del estado; para los componentes, determinan si el componente puede realizar su acción.


El contenedor de carga contiene cargas, las recarga, detiene la recarga si es necesario y proporciona cargas para que las utilicen los estados.


Se utiliza un temporizador cuando varios estados deben tener un tiempo de ejecución común, pero su propio tiempo de ejecución no está definido.


Es importante tener en cuenta que todos los objetos de habilidad son opcionales. Técnicamente, para que la capacidad funcione, sólo se necesitan un objeto principal y un estado.


Ahora bien, aunque no hay muchas capacidades construidas enteramente sin la participación de los programadores, el desarrollo en general se ha vuelto notablemente más barato, porque los programadores ahora sólo necesitan escribir cosas muy pequeñas: por ejemplo, un nuevo estado o dos componentes – el resto. se reutiliza.


Resumamos los componentes de nuestras habilidades:


  • El objeto principal realiza las funciones de una máquina de estados. Proporciona a los estados y componentes información sobre el mundo y proporciona al mundo información sobre habilidades. El objeto principal sirve como vínculo entre estados, componentes y partes de servicio de la capacidad: cargas y temporizadores externos.


  • El estado escucha los comandos de activación y desactivación del objeto principal y, en consecuencia, activa y desactiva los componentes, y también solicita al objeto principal que cambie a otro estado. El estado determina cuándo debe pasar al siguiente; para hacer esto, utiliza su condición interna: si el jugador hizo clic en el botón de habilidad, si ha pasado un cierto tiempo desde la activación del estado, etc., y condiciones externas vinculadas al estado.


  • El componente escucha los comandos de activación y desactivación del estado y realiza alguna acción: discreta o de largo plazo. Las acciones pueden ser completamente diferentes: pueden causar daño, curar a un aliado, activar una animación, etc.


  • La condición verifica en qué estado se encuentra el elemento deseado y lo informa al estado o componente. Las condiciones pueden ser complejas. Un estado no solicita una transición a otro estado si no se cumple la condición. El componente tampoco realiza una acción si no se cumple la condición. Las condiciones son una entidad opcional; no todas las habilidades las tienen.


  • El contenedor de carga contiene cargas, las recarga, deja de recargar cuando es necesario y proporciona cargas a los estados. Se usa en habilidades de carga múltiple, cuando necesitas permitir que el jugador lo use varias veces, pero no más de n veces seguidas.


  • El temporizador se utiliza cuando varios estados tienen una duración común, pero no se sabe cuánto durará cada uno de ellos. Cualquier estado puede iniciar un temporizador durante n segundos. Todos los estados relevantes se suscriben al evento de finalización del temporizador y hacen algo cuando finaliza.


Ahora volvamos al diagrama de habilidades. ¿Cómo cambió su funcionalidad?


  1. Al comienzo del juego, el objeto principal selecciona el primer estado y lo activa.
  2. El Estado activa todos sus componentes.
  3. El componente comprueba si se ha cumplido la condición y sólo entonces realiza una acción.
  4. El estado comienza a verificar las condiciones para la transición a otro estado
  5. Si se cumple la condición y se cumple la condición adicional vinculada a ella, el estado solicita que el objeto principal se mueva a otro estado.
  6. El objeto principal desactiva este estado y activa otro
  7. Se repite todo el procedimiento.


Los estados pueden utilizar los cargos como condición de transición adicional. Si se produce tal transición, el número de cargas disminuye. Los estados también pueden utilizar un cronómetro común. En este caso, el tiempo total para su ejecución estará determinado por un temporizador, y cada estado individualmente puede durar en cualquier momento.


UI de habilidad

No reinventamos completamente la rueda para las nuevas interfaces de usuario de habilidades.


El objeto principal tiene su propia interfaz de usuario. Define algunos elementos que siempre deberían estar en la interfaz de usuario y que no dependen del estado activo actualmente.


Cada estado tiene su propio par en la interfaz de usuario y la interfaz de usuario del estado se muestra solo cuando su estado está activo. Recibe datos sobre su estado y puede mostrarlos de una forma u otra. Por ejemplo, los estados de duración suelen tener una barra y un texto en su interfaz de usuario que muestra el tiempo restante.


En el caso de que el estado esté esperando un comando externo para continuar con una habilidad, su interfaz de usuario muestra un botón y al presionarlo se envía el comando al estado.





Ejemplos de habilidades

Veremos cómo funcionan las habilidades usando ejemplos específicos; Primero, veamos un robot llamado "Inquisidor". Tenemos cuatro estados que se suceden entre sí; encima de los estados puede ver su visualización en la interfaz de usuario. Para dos de ellos, también vemos los componentes que les pertenecen; los otros dos estados simplemente no tienen componentes.


Aquí está el flujo de la habilidad:

  1. Todo comienza con el estado "WaitForClick". En este momento, la habilidad no hace nada; solo espera comandos.


  2. Tan pronto como se recibe dicho comando, el objeto principal cambia de estado. El siguiente estado activo es "WaitForGrounded".


  3. Este estado tiene algunos componentes y, por tanto, cuando se activa, el robot salta y reproduce un sonido y una animación. Entre otras cosas, mientras el estado está activo, el robot se ve afectado por el efecto Jammer, que prohíbe apuntar al robot.


  4. Cuando el robot aterriza, su habilidad pasa al siguiente estado.


  5. Este estado tiene tres componentes: el ya familiar Sound y Jammer, así como Shake, que hace que la cámara vibre para todos los jugadores dentro de un radio de n .


  6. Dado que este estado tiene Duración , funciona durante n segundos, luego la habilidad pasa al siguiente estado.


  7. El último estado también viene con una duración, pero no tiene ningún componente: tiene un tiempo de reutilización regular.


  8. Al finalizar, la habilidad vuelve al primer estado.





Otro ejemplo es "Fantasma". Se parece mucho a Inquisitor, pero hay algunos matices:


  1. Empezamos con WaitForClick.


  2. Luego, la Duración, en la que se instala el teletransporte, se cambian las estadísticas del robot y se reproducen sonidos y animaciones.


  3. Después de esto: DurationOrClick, en el que se cambian las estadísticas del robot, se reproducen animaciones y efectos.


  4. Si se hizo un clic, pasamos a otra Duración, en la que el mech se teletransporta, cambian las estadísticas y se reproducen animaciones, efectos y sonidos.


  5. Después de este estado (o después de que expire el tiempo de DurationOrClick), pasamos a Duration.


La principal diferencia aquí es que vemos estados con ramificaciones: DurationOrClick va al estado A si ha pasado el tiempo especificado, o al estado B si el jugador ha presionado previamente el botón de habilidad.



Conclusiones

Si bien parecería que nuestro sistema ha evolucionado de algo simple a algo bastante complejo, este cambio ha simplificado la vida tanto de los programadores como de los diseñadores de juegos. La ayuda de los programadores ahora se necesita principalmente al agregar componentes pequeños, mientras que el último grupo de miembros del equipo ha ganado mayor autonomía y ahora puede ensamblar de forma independiente nuevas habilidades a partir de estados y componentes existentes. Como beneficio adicional, al mismo tiempo los jugadores también recibieron ganancias en forma de habilidades más diversas y complejas de los mechs.