Visión de conjunto:
Muchos juegos con estadísticas de tipo RPG permiten "beneficios" de los personajes, que van desde el simple "Infligir un 25% de daño extra" hasta cosas más complicadas como "Infligir 15 daños a los atacantes cuando son golpeados".
Los detalles de cada tipo de beneficio no son realmente relevantes. Estoy buscando una forma (presumiblemente orientada a objetos) para manejar beneficios arbitrarios.
Detalles:
En mi caso particular, tengo varios personajes en un entorno de batalla por turnos, por lo que imaginé que los buff estaban vinculados a eventos como "OnTurnStart", "OnReceiveDamage", etc. Quizás cada buff es una subclase de una clase abstracta de Buff principal, donde solo los eventos relevantes están sobrecargados. Entonces cada personaje podría tener un vector de beneficios actualmente aplicado.
¿Tiene sentido esta solución? Ciertamente puedo ver que docenas de tipos de eventos son necesarios, parece que crear una nueva subclase para cada beneficio es excesivo, y no parece permitir ninguna "interacción" de beneficios. Es decir, si quisiera implementar un límite en los aumentos de daño para que, incluso si tuviera 10 beneficios diferentes, todos con un 25% de daño adicional, solo haría un 100% adicional en lugar de un 250% adicional.
Y hay situaciones más complicadas que idealmente podría controlar. Estoy seguro de que todos pueden encontrar ejemplos de cómo los aficionados más sofisticados pueden interactuar entre sí de una manera que, como desarrollador de juegos, puede que no quiera.
Como programador de C ++ relativamente inexperto (generalmente he usado C en sistemas embebidos), siento que mi solución es simplista y probablemente no aprovecha al máximo el lenguaje orientado a objetos.
Pensamientos? ¿Alguien aquí ha diseñado un sistema de mejora bastante robusto antes?
Editar: con respecto a la (s) respuesta (s):
Seleccioné una respuesta basada principalmente en buenos detalles y una respuesta sólida a la pregunta que hice, pero leer las respuestas me dio más información.
Quizás, como era de esperar, los diferentes sistemas o sistemas modificados parecen aplicarse mejor a ciertas situaciones. El sistema que funcione mejor para mi juego dependerá de los tipos, la varianza y la cantidad de beneficios que tengo la intención de aplicar.
Para un juego como Diablo 3 (mencionado a continuación), donde casi cualquier parte del equipo puede cambiar la fuerza de un beneficio, los beneficios son solo un sistema de estadísticas de personajes que parece una buena idea siempre que sea posible.
Para la situación por turnos en la que me encuentro, el enfoque basado en eventos puede ser más adecuado.
En cualquier caso, todavía espero que alguien venga con una elegante bala mágica "OO" que me permitirá aplicar una distancia de movimiento de +2 por beneficio de turno , un 50% del daño recibido al beneficio de atacante , y un telepuerto automáticamente a una baldosa cercana al ser atacados a partir de 3 o más fichas de distancia aficionado en un solo sistema sin necesidad de encender un 5 fuerza aficionado en su propia subclase.
Creo que lo más parecido es la respuesta que marqué, pero el piso aún está abierto. Gracias a todos por el aporte.
fuente
Respuestas:
Este es un tema complicado, porque estás hablando de algunas cosas diferentes que (en estos días) se agrupan como 'beneficios':
Siempre implemento el primero con una lista de efectos activos para un determinado personaje. La eliminación de la lista, ya sea en función de la duración o explícitamente, es bastante trivial, por lo que no lo trataré aquí. Cada efecto contiene una lista de modificadores de atributos y puede aplicarlo al valor subyacente mediante una simple multiplicación.
Luego lo envuelvo con funciones para acceder a los atributos modificados. p.ej.:
Eso te permite aplicar efectos multiplicativos con bastante facilidad. Si también necesita efectos aditivos, decida en qué orden los aplicará (probablemente el último aditivo) y revise la lista dos veces. (Probablemente tendría listas de modificadores separadas en Efecto, una para multiplicativo, una para aditivo).
El valor del criterio es permitirle implementar "+ 20% vs No Muerto": establezca el valor NO MUERTO en el Efecto y solo pase el valor NO MUERTO
get_current_attribute_value()
cuando esté calculando una tirada de daño contra un enemigo no muerto.Por cierto, no estaría tentado a intentar escribir un sistema que aplique y no aplique los valores directamente al valor del atributo subyacente; el resultado final es que es muy probable que sus atributos se desvíen del valor deseado debido a un error. (por ejemplo, si multiplica algo por 2, pero luego lo limita, cuando lo divida nuevamente por 2, será más bajo de lo que comenzó).
En cuanto a los efectos basados en eventos, como "Infligir 15 daños a los atacantes cuando son golpeados", puede agregar métodos en la clase Efecto para eso. Pero si desea un comportamiento distinto y arbitrario (por ejemplo, algunos efectos para el evento anterior podrían reflejar daños, algunos podrían curarlo, podría teletransportarse al azar, lo que sea) necesitará funciones o clases personalizadas para manejarlo. Puede asignar funciones a los controladores de eventos en el efecto, luego puede llamar a los controladores de eventos en cualquier efecto activo.
Obviamente, su clase de efectos tendrá un controlador de eventos para cada tipo de evento, y puede asignar funciones de controlador a todas las que necesite en cada caso. No necesita subclasificar Effect, ya que cada uno está definido por la composición de los modificadores de atributos y controladores de eventos que contiene. (Probablemente también contendrá un nombre, una duración, etc.)
fuente
En un juego en el que trabajé con un amigo para una clase, creamos un sistema de beneficio / desventaja para cuando el usuario queda atrapado en la hierba alta y acelera los azulejos y lo que no, y algunas cosas menores como sangrados y venenos.
La idea era simple, y aunque la aplicamos en Python, fue bastante efectiva.
Básicamente, así es como fue:
Ahora, cómo aplicar los beneficios del mundo es una historia diferente. Aquí está mi comida para pensar sin embargo.
fuente
No estoy seguro si todavía estás leyendo esto, pero he luchado con este tipo de problema durante mucho tiempo.
He diseñado numerosos tipos diferentes de sistemas de afecto. Los revisaré brevemente ahora. Todo esto se basa en mi experiencia. No pretendo saber todas las respuestas.
Modificadores estáticos
Este tipo de sistema se basa principalmente en enteros simples para determinar cualquier modificación. Por ejemplo, +100 a Max HP, +10 a atacar y así sucesivamente. Este sistema también podría manejar porcentajes también. Solo necesita asegurarse de que el apilamiento no se salga de control.
Realmente nunca almacené en caché los valores generados para este tipo de sistema. Por ejemplo, si quisiera mostrar la salud máxima de algo, generaría el valor en el acto. Esto evitó que las cosas fueran propensas a errores y simplemente más fácil de entender para todos los involucrados.
(Trabajo en Java, por lo que lo que sigue está basado en Java, pero debería funcionar con algunas modificaciones para otros idiomas). Este sistema se puede hacer fácilmente usando enumeraciones para los tipos de modificación, y luego enteros. El resultado final se puede colocar en algún tipo de colección que tenga pares ordenados de clave y valor. Esto será una búsqueda rápida y cálculos, por lo que el rendimiento es muy bueno.
En general, funciona muy bien con solo modificadores estáticos. Sin embargo, el código debe existir en los lugares adecuados para que se usen los modificadores: getAttack, getMaxHP, getMeleeDamage, y así sucesivamente.
Donde este método falla (para mí) es una interacción muy compleja entre aficionados. No hay una manera realmente fácil de interactuar, excepto por un poco de gueto. Tiene algunas posibilidades simples de interacción. Para hacerlo, debe realizar una modificación en la forma en que almacena los modificadores estáticos. En lugar de utilizar una enumeración como clave, utiliza una cadena. Esta cadena sería el nombre Enum + variable adicional. 9 de cada 10 veces, la variable adicional no se utiliza, por lo que aún conserva el nombre de enumeración como clave.
Hagamos un ejemplo rápido: si quisiera poder modificar el daño contra criaturas no muertas, podría tener un par ordenado como este: (DAMAGE_Undead, 10) El DAÑO es la enumeración y los no muertos es la variable adicional. Entonces, durante tu combate, puedes hacer algo como:
De todos modos, funciona bastante bien y es rápido. Pero falla en interacciones complejas y tener código "especial" en todas partes. Por ejemplo, considere la situación de "25% de posibilidades de teletransportarse en caso de muerte". Este es uno "bastante" complejo. El sistema anterior puede manejarlo, pero no fácilmente, ya que necesita lo siguiente:
Entonces esto me lleva a la siguiente:
El último sistema de mejora compleja
Una vez intenté escribir un MMORPG 2D solo. ¡Fue un error terrible, pero aprendí mucho!
Reescribí el sistema de afecto 3 veces. El primero utilizó una variación menos poderosa de lo anterior. El segundo fue de lo que voy a hablar.
Este sistema tenía una serie de clases para cada modificación, por lo que cosas como: ChangeHP, ChangeMaxHP, ChangeHPByPercent, ChangeMaxByPercent. Tenía un millón de estos tipos, incluso cosas como TeleportOnDeath.
Mis clases tenían cosas que harían lo siguiente:
Aplicar y eliminar explicarse a sí mismos (aunque para cosas como porcentajes, el efecto mantendría un registro de cuánto aumentó el HP para garantizar que cuando el efecto desapareciera, solo eliminaría la cantidad que agregó. Esto fue buggy, lol y Me llevó mucho tiempo asegurarme de que era correcto. Todavía no tenía un buen presentimiento al respecto).
El método checkForInteraction fue un código horrendísimo complejo. En cada una de las clases de afectos (es decir: ChangeHP), tendría un código para determinar si esto debería ser modificado por el afecto de entrada. Entonces, por ejemplo, si tuvieras algo como ...
El método checkForInteraction manejaría todos estos efectos. ¡Para hacer esto, cada efecto en TODOS los jugadores cercanos tenía que ser verificado! Esto se debe a la clase de afectos que tuve con varios jugadores en un área. Esto significa que el código NUNCA TENÍA ninguna declaración especial como la anterior: "si acabamos de morir, debemos verificar el teletransporte en caso de muerte". Este sistema lo manejaría automáticamente correctamente en el momento adecuado.
Intentar escribir este sistema me tomó como 2 meses y explotó varias veces por la cabeza. SIN EMBARGO, era REALMENTE poderoso y podía hacer una cantidad increíble de cosas, especialmente cuando se tienen en cuenta los siguientes dos hechos para las habilidades en mi juego: 1. Tenían rangos objetivo (es decir, solo, auto, solo grupo, PB AE auto , PB AE objetivo, AE objetivo, etc.). 2. Las habilidades podrían tener más de 1 efecto sobre ellas.
Como mencioné anteriormente, este fue el segundo sistema de afecto tercero para este juego. ¿Por qué me alejé de esto?
¡Este sistema tuvo el peor rendimiento que he visto!Fue muy lento, ya que tenía que hacer muchas comprobaciones para cada cosa que sucedía. Traté de mejorarlo, pero lo consideré un fracaso.
Entonces llegamos a mi tercera versión (y otro tipo de sistema de mejora):
Clase de afecto complejo con manejadores
Así que esto es más o menos una combinación de los dos primeros: podemos tener variables estáticas en una clase de Afecto que contiene mucha funcionalidad y datos adicionales. Luego solo llame a los controladores (para mí, más o menos algunos métodos de utilidad estáticos en lugar de subclases para acciones específicas. Pero estoy seguro de que podría ir con subclases para acciones si también lo desea) cuando queremos hacer algo.
La clase Affect tendría todas las cosas buenas y jugosas, como los tipos de objetivos, la duración, el número de usos, la posibilidad de ejecutar, etc.
Todavía tendríamos que agregar códigos especiales para manejar las situaciones, por ejemplo, teletransportarse en caso de muerte. Todavía tendríamos que verificar esto manualmente en el código de combate, y luego, si existiera, obtendríamos una lista de afectos. Esta lista de efectos contiene todos los efectos aplicados actualmente en el jugador que se ocupó de teletransportarse al morir. Luego solo miraríamos cada uno y verificaríamos si se ejecutó y tuvo éxito (Nos detendríamos en el primero). Si fue exitoso, simplemente llamaríamos al manejador para encargarse de esto.
La interacción se puede hacer, si quieres también. Solo tendría que escribir el código para buscar beneficios específicos en los jugadores / etc. Debido a que tiene un buen rendimiento (ver más abajo), debería ser bastante eficiente hacerlo. Simplemente necesitaría controladores más complejos, etc.
Por lo tanto, tiene mucho rendimiento del primer sistema y aún mucha complejidad como el segundo (pero no tanto). Al menos en Java, puede hacer algunas cosas difíciles para obtener el rendimiento de casi el primero en la MAYORÍA de los casos (es decir, tener un mapa de enumeración ( http://docs.oracle.com/javase/6/docs/api/java /util/EnumMap.html ) con Enums como las claves y ArrayList de los afectos como los valores. Esto le permite ver si tiene efectos rápidamente [ya que la lista sería 0 o el mapa no tendría la enumeración] y no tener iterar continuamente sobre las listas de afectos del jugador sin ninguna razón. No me importa iterar sobre los afectos si los necesitamos en este momento. Lo optimizaré más adelante si se convierte en un problema).
Actualmente estoy volviendo a abrir (reescribiendo el juego en Java en lugar de la base de código FastROM en la que estaba originalmente) mi MUD que terminó en 2005 y recientemente me he encontrado con ¿cómo quiero implementar mi sistema de mejora? Voy a usar este sistema porque funcionó muy bien en mi juego fallido anterior.
Bueno, espero que alguien, en algún lugar, encuentre algunas de estas ideas útiles.
fuente
Una clase diferente (o función direccionable) para cada beneficio no es exagerada si el comportamiento de esos beneficios es diferente el uno del otro. Una cosa sería tener + 10% o + 20% de beneficios (que, por supuesto, estaría mejor representado como dos objetos de la misma clase), otra sería implementar efectos muy diferentes que requerirían un código personalizado de todos modos. Sin embargo, creo que es mejor tener formas estándar de personalizar la lógica del juego lugar de dejar que cada aficionado haga lo que le plazca (y posiblemente interferir entre sí de manera imprevista, lo que perturba el equilibrio del juego).
Sugeriría dividir cada "ciclo de ataque" en pasos, donde cada paso tiene un valor base, una lista ordenada de modificaciones que se pueden aplicar a ese valor (tal vez con un tope) y un tope final. Cada modificación tiene una transformación de identidad por defecto, y puede verse influenciada por cero o más ventajas / desventajas. Los detalles de cada modificación dependerán del paso aplicado. La forma en que se implementa el ciclo depende de usted (incluida la opción de una arquitectura basada en eventos, como ha estado discutiendo).
Un ejemplo de ciclo de ataque podría ser:
Lo importante a tener en cuenta es que cuanto más temprano en el ciclo se aplique un beneficio, más efecto tendrá en el resultado . Entonces, si quieres un combate más "táctico" (donde la habilidad del jugador es más importante que el nivel de personaje) crea muchos beneficios / desventajas en las estadísticas básicas. Si quieres un combate más "equilibrado" (donde el nivel es más importante, importante en los MMOG para limitar la tasa de progreso), solo usa mejoras / desventajas más adelante en el ciclo.
La distinción entre "Modificaciones" y "Beneficios" que mencioné anteriormente tiene un propósito: las decisiones sobre las reglas y el equilibrio se pueden implementar en el primero, por lo que cualquier cambio en esos no tiene que reflejarse en los cambios en cada clase de este último. OTOH, los números y tipos de beneficios solo están limitados por su imaginación, ya que cada uno de ellos puede expresar su comportamiento deseado sin tener que tener en cuenta cualquier interacción posible entre ellos y los demás (o incluso la existencia de otros).
Entonces, respondiendo la pregunta: no crees una clase para cada Buff, sino una para cada (tipo de) Modificación, y vincula la Modificación al ciclo de ataque, no al personaje. Los beneficios pueden ser simplemente una lista de tuplas (Modificación, clave, valor), y puedes aplicar un beneficio a un personaje simplemente agregándolo / eliminándolo al conjunto de beneficios del personaje. Esto también reduce la ventana de error, ya que las estadísticas del personaje no necesitan cambiarse en absoluto cuando se aplican los beneficios (por lo que hay menos riesgo de restaurar una estadística al valor incorrecto después de que expire un beneficio).
fuente
No sé si todavía lo estás leyendo, pero así es como lo estoy haciendo ahora (el código se basa en UE4 y C ++). Después de reflexionar sobre el problema durante más de dos semanas (!!), finalmente encontré esto:
http://gamedevelopment.tutsplus.com/tutorials/using-the-composite-design-pattern-for-an-rpg-attributes-system--gamedev-243
Y pensé que, bueno, encapsular un solo atributo dentro de la clase / estructura no es una mala idea después de todo. Sin embargo, tenga en cuenta que estoy aprovechando realmente la gran ventaja del sistema de reflexión de código integrado UE4, por lo que, sin algunos cambios, esto podría no ser adecuado en todas partes.
De todos modos, comencé a envolver el atributo en una sola estructura:
Todavía no está terminado, pero la idea base es que esta estructura realiza un seguimiento de su estado interno. Los atributos solo pueden ser modificados por Efectos. Intentar modificarlos directamente no es seguro y no está expuesto a los diseñadores. Supongo que todo lo que puede interactuar con los atributos es Efecto. Incluyendo bonificaciones planas de artículos. Cuando se equipa un nuevo elemento, se crea un nuevo efecto (junto con el mango), y se agrega al mapa dedicado, que maneja bonificaciones de duración infinita (aquellas que el jugador debe eliminar manualmente). Cuando se aplica un nuevo efecto, se crea un nuevo identificador para él (el identificador es solo int, envuelto con struct), y luego ese identificador se pasa por todos lados como un medio para interactuar con este efecto, así como para realizar un seguimiento si el efecto es Aún en activo. Cuando se elimina el efecto, su asa se transmite a todos los objetos interesados,
La parte realmente importante de esto es TMap (TMap es un mapa hash). FGAModifier es una estructura muy simple:
Contiene tipo de modificación:
Y el valor, que es el valor final calculado, lo vamos a aplicar al atributo.
Agregamos un nuevo efecto usando una función simple, y luego llamamos:
Se supone que esta función recalculará toda la pila de bonos, cada vez que se agregue o elimine el efecto. La función aún no está terminada (como puede ver), pero puede obtener la idea general.
Mi mayor queja en este momento es manejar el atributo Damaging / Healing (sin involucrar volver a calcular la pila completa), creo que tengo algo resuelto, pero aún requiere más pruebas para ser 100%.
En cualquier caso, los atributos se definen así (+ macros irreales, omitidos aquí):
etc.
Además, no estoy 100% seguro de manejar el CurrentValue del atributo, pero debería funcionar. Como están ahora.
En cualquier caso, espero que salve a algunas personas de la memoria caché, no estoy seguro de si esta es la mejor o incluso la mejor solución, pero me gusta más que rastrear los efectos independientemente de los atributos. Hacer que cada atributo rastree su propio estado es mucho más fácil en este caso, y debería ser menos propenso a errores. Esencialmente, solo hay un punto de falla, que es una clase bastante corta y simple.
fuente
Trabajé en un pequeño MMO y todos los elementos, poderes, beneficios, etc. tuvieron "efectos". Un efecto era una clase que tenía variables para 'AddDefense', 'InstantDamage', 'HealHP', etc. Los poderes, elementos, etc. manejarían la duración de ese efecto.
Cuando lanzas un poder o te pones un objeto, aplicaría el efecto al personaje durante la duración especificada. Luego, el ataque principal, etc., los cálculos tendrían en cuenta los efectos aplicados.
Por ejemplo, tienes un beneficio que agrega defensa. Habría como mínimo un EffectID y una Duración para ese beneficio. Al lanzarlo, aplicaría el EffectID al personaje durante la duración especificada.
Otro ejemplo para un artículo, tendría los mismos campos. Pero la duración sería infinita o hasta que se elimine el efecto quitando el elemento del personaje.
Este método le permite iterar sobre una lista de efectos que se aplican actualmente.
Espero haber explicado este método con suficiente claridad.
fuente
Estoy usando ScriptableOjects como beneficios / hechizos / talentos
usando UnityEngine; usando System.Collections.Generic;
public enum BuffType {Buff, Debuff} [System.Serializable] public class BuffStat {public Stat Stat = Stat.Strength; flotante público ModValueInPercent = 0.1f; }
BuffModul:
fuente
Esta fue una pregunta real para mí. Tengo una idea al respecto.
Buff
lista y un actualizador lógico para los aficionados.Buff
clase.De esta manera, puede ser fácil agregar nuevas estadísticas de jugador, sin cambios en la lógica de las
Buff
subclases.fuente
Sé que esto es bastante antiguo, pero estaba vinculado en una publicación más reciente y tengo algunas ideas que me gustaría compartir. Desafortunadamente, no tengo mis notas conmigo en este momento, así que intentaré dar una visión general de lo que estoy hablando y editaré los detalles y algún código de ejemplo cuando lo tenga delante yo.
En primer lugar, creo que desde una perspectiva de diseño, la mayoría de las personas están demasiado atrapadas en los tipos de beneficios que se pueden crear y cómo se aplican y olvidando los principios básicos de la programación orientada a objetos.
¿Que quiero decir? Realmente no importa si algo es un beneficio o una desventaja, ambos son modificadores que solo afectan algo de manera positiva o negativa. Al código no le importa cuál es cuál. En realidad, no importa si algo está agregando estadísticas o multiplicándolas, esos son solo operadores diferentes y nuevamente al código no le importa cuál es cuál.
Entonces, ¿a dónde voy con esto? Que diseñar una buena (léase: simple, elegante) clase buff / debuff no es tan difícil, lo que es difícil es diseñar los sistemas que calculan y mantienen el estado del juego.
Si estuviera diseñando un sistema buff / debuff aquí hay algunas cosas que consideraría:
Algunos detalles sobre qué tipos de beneficios / desventajas deberían contener:
Eso es solo un comienzo, pero a partir de ahí, solo estás definiendo lo que quieres y actuando según tu estado de juego normal. Por ejemplo, supongamos que desea crear un objeto maldito que reduzca la velocidad de movimiento ...
Siempre que haya establecido los tipos adecuados, es simple crear un registro de beneficio que diga:
Y así sucesivamente, y cuando creo un beneficio solo le asigno el BuffType of Curse y todo lo demás depende del motor ...
fuente