Crear un sistema de artículos robusto


12

Mi objetivo es crear un sistema de elementos modular / tan genérico como sea posible que pueda manejar cosas como:

  • Artículos actualizables (+6 Katana)
  • Modificadores de estadísticas (+15 de destreza)
  • Modificadores de objeto (% X posibilidad de hacer daño Y, posibilidad de congelar)
  • Artículos recargables (personal mágico con 30 usos)
  • Establecer elementos (Equipar 4 piezas de conjunto X para activar la función Y)
  • Rareza (común, única, legendaria)
  • Desencantable (se rompe en algunos materiales de artesanía)
  • Craftable (puede fabricarse con ciertos materiales)
  • Consumible (5min% de poder de ataque X, cura +15 hp)

* Pude resolver características que están en negrita en la siguiente configuración.

Ahora intenté agregar muchas opciones para reflejar lo que tengo en mente. No planeo agregar todas estas características necesarias, pero me gustaría poder implementarlas como mejor me parezca. Estos también deben ser compatibles con el sistema de inventario y la serialización de datos.

Estoy planeando no usar la herencia en absoluto, sino más bien un enfoque basado en datos / componente de entidad. Inicialmente pensé en un sistema que tiene:

  • BaseStat: una clase genérica que contiene estadísticas sobre la marcha (también se puede usar para artículos y estadísticas de personajes)
  • Elemento: una clase que contiene datos como la lista de, nombre, tipo de elemento y cosas relacionadas con la interfaz de usuario, actionName, descripción, etc.
  • IWeapon: interfaz para arma. Cada arma tendrá su propia clase con IWeapon implementado. Esto tendrá Ataque y una referencia a las estadísticas de los personajes. Cuando el arma está equipada, sus datos (estadística de la clase de artículo) se inyectarán en la estadística de personaje (cualquiera que sea el BaseStat que tenga, se agregará a la clase de personaje como una bonificación de estadística) Entonces, por ejemplo, queremos producir una espada (pensando en producir clases de ítems con json) para que espada agregue 5 ataques a las estadísticas de los personajes. Entonces tenemos un BaseStat como ("Attack", 5) (también podemos usar enum). Esta estadística se agregará a la estadística de "Ataque" del personaje como BonusStat (que sería una clase diferente) al equiparla. Entonces, una clase llamada Sword implementa IWeapon se creará cuando 'Se crea la clase de artículo . Por lo tanto, podemos inyectar estadísticas de personajes en esta espada y al atacar, puede recuperar la estadística de ataque total de la estadística de personaje e infligir daño en el método de ataque.
  • BonusStat: es una forma de agregar estadísticas como bonificaciones sin tocar BaseStat.
  • IConsumable: la misma lógica que con IWeapon. Agregar estadísticas directas es bastante fácil (+15 hp), pero no estoy seguro de agregar armas temporales con esta configuración (% x para atacar durante 5 min).
  • IUpgradeable: esto se puede implementar con esta configuración. Estoy pensando en UpgradeLevel como una estadística base, que se incrementa al actualizar el arma. Cuando se actualiza, podemos volver a calcular el BaseStat del arma para que coincida con su nivel de actualización.

Hasta este punto, puedo ver que el sistema es bastante bueno. Pero para otras funciones, creo que necesitamos algo más, porque, por ejemplo, no puedo implementar la función Craftable en esto, ya que mi BaseStat no podría manejar esta función y aquí es donde me quedé atascado. Puedo agregar todos los ingredientes como una estadística, pero eso no tendría sentido.

Para facilitarle su contribución, aquí hay algunas preguntas con las que puede ayudar:

  • ¿Debo continuar con esta configuración para implementar otras funciones? ¿Sería posible sin herencia?
  • ¿Hay alguna forma de pensar para implementar todas estas características sin herencia?
  • Acerca de los modificadores de elementos, ¿cómo podría uno lograr eso? Porque es muy genérico en su naturaleza.
  • ¿Qué se puede hacer para facilitar el proceso de construcción de este tipo de arquitectura, alguna recomendación?
  • ¿Hay alguna fuente que pueda cavar que esté relacionada con este problema?
  • Realmente trato de evitar la herencia, pero ¿crees que esto se resolvería / lograría con la herencia con facilidad y al mismo tiempo lo mantendría bastante sostenible?

No dude en responder una sola pregunta, ya que mantuve preguntas muy amplias para poder obtener conocimiento de diferentes aspectos / personas.


EDITAR


Siguiendo la respuesta de @ jjimenezg93, creé un sistema muy básico en C # para probar, ¡funciona! Vea si puede agregarle algo:

public interface IItem
{
    List<IAttribute> Components { get; set; }

    void ReceiveMessage<T>(T message);
}

public interface IAttribute
{
    IItem source { get; set; }
    void ReceiveMessage<T>(T message);
}

Hasta ahora, IItem e IAttribute son interfaces base. No había necesidad (que se me ocurriera) de tener una interfaz / atributo base para el mensaje, por lo que crearemos directamente una clase de mensaje de prueba. Ahora para las clases de prueba:


public class TestItem : IItem
{
    private List<IAttribute> _components = new List<IAttribute>();
    public List<IAttribute> Components
    {
        get
        {
            return _components;
        }

        set
        {
            _components = value;
        }
    }

    public void ReceiveMessage<T>(T message)
    {
        foreach (IAttribute attribute in Components)
        {
            attribute.ReceiveMessage(message);
        }
    }
}

public class TestAttribute : IAttribute
{
    string _infoRequiredFromMessage;

    public TestAttribute(IItem source)
    {
        _source = source;
    }

    private IItem _source;
    public IItem source
    {
        get
        {
            return _source;
        }

        set
        {
            _source = value;
        }
    }

    public void ReceiveMessage<T>(T message)
    {
        TestMessage convertedMessage = message as TestMessage;
        if (convertedMessage != null)
        {
            convertedMessage.Execute();
            _infoRequiredFromMessage = convertedMessage._particularInformationThatNeedsToBePassed;
            Debug.Log("Message passed : " + _infoRequiredFromMessage);

        }
    }
} 

public class TestMessage
{
    private string _messageString;
    private int _messageInt;
    public string _particularInformationThatNeedsToBePassed;
    public TestMessage(string messageString, int messageInt, string particularInformationThatNeedsToBePassed)
    {
        _messageString = messageString;
        _messageInt = messageInt;
        _particularInformationThatNeedsToBePassed = particularInformationThatNeedsToBePassed;
    }
    //messages should not have methods, so this is here for fun and testing.
    public void Execute()
    {
        Debug.Log("Desired Execution Method: \nThis is test message : " + _messageString + "\nThis is test int : " + _messageInt);
    }
} 

Estas son las configuraciones necesarias. Ahora podemos usar el sistema (lo siguiente es para Unity).

public class TestManager : MonoBehaviour
{

    // Use this for initialization
    void Start()
    {
        TestItem testItem = new TestItem();
        TestAttribute testAttribute = new TestAttribute(testItem);
        testItem.Components.Add(testAttribute);
        TestMessage testMessage = new TestMessage("my test message", 1, "VERYIMPORTANTINFO");
        testItem.ReceiveMessage(testMessage);
    }

}

Adjunte esta secuencia de comandos TestManager a un componente en la escena y podrá ver en la depuración que el mensaje se pasó con éxito.


Para explicar las cosas: cada elemento del juego implementará la interfaz IItem y cada atributo (el nombre no debe confundirte, significa que la característica / sistema del elemento. Al igual que Actualizable o desencantable) implementará IAttribute. Luego tenemos un método para procesar el mensaje (se explicará por qué necesitamos un mensaje en otro ejemplo). Entonces, en contexto, puede adjuntar atributos a un elemento y dejar que el resto lo haga por usted. Lo cual es muy flexible, porque puede agregar / eliminar atributos a gusto. Entonces un pseudoejemplo sería desencantable. Tendremos una clase llamada Disenchantable (IAttribute) y en el método Disenchant, pedirá:

  • Enumere los ingredientes (cuando el artículo está desencantado, qué artículo se debe dar al jugador) nota: IItem se debe extender para que tenga ItemType, ItemID, etc.
  • int resultModifier (si implementa una especie de impulso de la función desencantar, puede pasar un int aquí para aumentar los ingredientes recibidos cuando está desencantado)
  • int failureChance (si el proceso de desencantar tiene una posibilidad de falla)

etc.

Esta información será proporcionada por una clase llamada DisenchantManager, recibirá el elemento y formará este mensaje de acuerdo con el elemento (ingredientes del elemento cuando esté desencantado) y la progresión del jugador (resultModifier y failureChance). Para pasar este mensaje, crearemos una clase DisenchantMessage, que actuará como un cuerpo para este mensaje. Entonces DisenchantManager completará un DisenchantMessage y lo enviará al elemento. El artículo recibirá el mensaje y lo pasará a todos sus atributos adjuntos. Dado que el método ReceiveMessage de la clase Disenchantable buscará un DisenchantMessage, solo el atributo Disenchantable recibirá este mensaje y actuará en consecuencia. Espero que esto aclare las cosas tanto como lo hizo para mí :).


1
Puede encontrar alguna inspiración útil en los sistemas modificadores utilizados en For Honor
DMGregory

@DMGregory ¡Hola! Gracias por el enlace. Si bien parece muy ingenioso, desafortunadamente necesito la charla real para comprender el concepto. Y trato de averiguar la charla real, que desafortunadamente es contenido solo para miembros de GDCVault (¡495 $ por año es una locura!). (Puede encontrar la charla aquí si tiene membresía GDCVault -, -
Vandarthul

¿Cómo, exactamente, su concepto "BaseStat" excluiría armas fabricables?
Attackfarm

Realmente no excluye, pero realmente no encaja en el contexto en mi mente. Es factible agregar "Madera", 2 y "Hierro", 5 como BaseStat a una receta de elaboración, lo que produciría la espada. Creo que cambiar el nombre de BaseStat a BaseAttribute serviría mejor en este contexto. Pero aún así, el sistema no podrá cumplir su propósito. Piensa en un artículo consumible con 5 min -% 50 de poder de ataque. ¿Cómo lo pasaría como BaseStat? "Perc_AttackPower", 50 esto debe resolverse como "si es Perc, trate el entero como un porcentaje" y le faltará la información de los minutos. O espero que entiendas lo que quiero decir.
Vandarthul

@Attackfarm, pensándolo bien, este concepto de "BaseStat" se puede ampliar con una lista de entradas en lugar de solo una int. entonces, para la mejora de consumibles, puedo suministrar "Ataque", 50, 5, 1 e IConsumable buscaría 3 enteros, 1. - valor, 2. - minutos, 3. - si es un porcentaje o no. Pero se siente impelente a medida que otros sistemas entran y se ven obligados a explicarse solo en int.
Vandarthul

Respuestas:


6

Creo que puede lograr lo que desea en términos de escalabilidad y facilidad de mantenimiento mediante el uso de un sistema de entidad-componente con herencia básica y un sistema de mensajería. Por supuesto, tenga en cuenta que este sistema es el más modular / personalizable / escalable que se me ocurre, pero probablemente funcionará peor que su solución actual.

Explicaré más a fondo:

En primer lugar, crea una interfaz IItemy una interfaz IComponent. Cualquier elemento que desee almacenar debe heredar IItem, y cualquier componente del que desee afectar sus elementos debe heredar IComponent.

IItemtendrá una variedad de componentes y un método de manejo IMessage. Este método de manejo simplemente envía cualquier mensaje recibido a todos los componentes almacenados. Entonces, los componentes que estén interesados ​​en ese mensaje dado actuarán en consecuencia, y los demás lo ignorarán.

Un mensaje, por ejemplo, es de tipo daño e informa tanto al atacante como al atacado, para que sepa cuánto golpea y tal vez cargue su barra de furia en función de ese daño. O la IA del enemigo puede decidir correr si te golpea y hace menos de 2 HP de daño. Estos son ejemplos tontos, pero usando un sistema similar a este que estoy mencionando, no necesitará hacer nada más que crear un mensaje y los manejos apropiados para agregar la mayoría de este tipo de mecánica.

Tengo una implementación para un ECS con mensajes aquí , pero esto se usa para entidades en lugar de elementos y usa C ++. De todos modos, creo que puede ayudar si nos fijamos component.h, entity.hy messages.h. Hay muchas cosas que mejorar pero funcionó para mí en ese simple trabajo universitario.

Espero eso ayude.


Hola @ jjimenezg93, gracias por tu respuesta. Entonces, para elaborar con un ejemplo simple de lo que ha explicado: Queremos una espada que sea: - Desencantable [Componente] - Modificador de estadísticas [Componente] - Actualizable [Componente] Tengo una lista de acciones que contiene todas las cosas como - DESACTIVAR - MODIFICAR_STAT - ACTUALIZAR Cada vez que un artículo recibe este mensaje, revisa todos sus componentes y envía este mensaje, cada componente sabrá qué hacer con el mensaje dado. ¡En teoría, esto parece increíble! No revisé tu ejemplo pero lo haré, ¡muchas gracias!
Vandarthul

@Vandarthul Sí, esa es básicamente la idea. De esta manera, el elemento no sabrá nada acerca de sus componentes, por lo que no habrá ningún acoplamiento y, al mismo tiempo, tendrá toda la funcionalidad que desee, que también se puede compartir entre diferentes tipos de elementos. ¡Espero que se ajuste a tus necesidades!
jjimenezg93
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.