Criando um sistema robusto de itens

12

Meu objetivo é criar um sistema de itens modular / o mais genérico possível que possa lidar com coisas como:

  • Itens atualizáveis ​​(+6 Katana)
  • Modificadores de estatística (+15 de destreza)
  • Modificadores de Item (% X de chance de causar dano em Y, chance de congelar)
  • Itens Recarregáveis ​​(equipe Magic com 30 usos)
  • Definir itens (Equipe 4 peças de X para ativar o recurso Y)
  • Raridade (comum, única, lendária)
  • Desencantável (quebra em alguns materiais de artesanato)
  • Artesanato (pode ser fabricado com certos materiais)
  • Consumível (5min% X de poder de ataque, cura +15 hp)

* Consegui resolver recursos que são ousados ​​na configuração a seguir.

Agora, tentei adicionar muitas opções para refletir o que tenho em mente. Não pretendo adicionar todos esses recursos necessários, mas gostaria de poder implementá-los como achar melhor. Estes também devem ser compatíveis com o sistema de inventário e serialização de dados.

Estou planejando não usar a herança , mas uma abordagem orientada a componentes / dados da entidade. Inicialmente, pensei em um sistema que tivesse:

  • BaseStat: uma classe genérica que contém estatísticas on-the-go (pode ser usada para itens e estatísticas de personagens também)
  • Item: uma classe que contém dados como lista de, nome, tipo de item e itens relacionados à interface do usuário, actionName, descrição etc.
  • IWeapon: interface para arma. Toda arma terá sua própria classe com o IWeapon implementado. Isso terá Attack e uma referência às estatísticas do personagem. Quando a arma é equipada, seus dados (stat da classe Item) serão injetados no status do personagem (qualquer BaseStat que possua, será adicionado à classe do personagem como um bônus Stat). Por exemplo, queremos produzir uma espada (pensando em produza classes de itens com json), então a espada adicionará 5 de ataque às estatísticas do personagem. Portanto, temos um BaseStat como ("Attack", 5) (também podemos usar enum). Esta estatística será adicionada à estatística "Ataque" do personagem como um BonusStat (que seria uma classe diferente) ao equipá-lo. Portanto, uma classe chamada Sword implementa IWeapon será criada quandoA classe do item é criada. Assim, podemos injetar estatísticas de personagem nesta espada e, ao atacar, ela pode recuperar o total de estatísticas de ataque do status de personagens e causar dano no método de ataque.
  • BonusStat: é uma maneira de adicionar estatísticas como bônus sem tocar no BaseStat.
  • IConsumable: A mesma lógica do IWeapon. Adicionar estatísticas diretas é bastante fácil (+15 hp), mas não tenho certeza sobre adicionar armas temporárias com essa configuração (% x para atacar por 5 min).
  • IUpgradeable: Isso pode ser implementado com esta configuração. Estou pensando em UpgradeLevel como uma estatística básica, que é aumentada após a atualização da arma. Quando atualizado, podemos recalcular o BaseStat da arma para corresponder ao seu nível de atualização.

Até este ponto, posso ver que o sistema é razoavelmente bom. Mas, para outros recursos, acho que precisamos de outra coisa, porque, por exemplo, não consigo implementar o recurso Craftable nisso, pois o meu BaseStat não seria capaz de lidar com esse recurso e foi aí que fiquei preso. Posso adicionar todos os ingredientes como Stat, mas isso não faria sentido.

Para facilitar sua contribuição, aqui estão algumas perguntas que você pode ajudar:

  • Devo continuar com esta instalação para implementar outros recursos? Seria possível sem herança?
  • Existe alguma maneira em que você possa pensar para implementar todos esses recursos sem herança?
  • Sobre os modificadores de itens, como alguém pode conseguir isso? Porque é muito genérico em sua natureza.
  • O que pode ser feito para facilitar o processo de construção desse tipo de arquitetura, alguma recomendação?
  • Existem fontes que eu possa cavar relacionadas a esse problema?
  • Eu realmente tento evitar a herança, mas você acha que isso seria resolvido / alcançado com facilidade com facilidade, mantendo-a razoavelmente sustentável?

Sinta-se à vontade para responder a apenas uma pergunta, pois mantive as perguntas muito amplas para que eu possa obter conhecimento de diferentes aspectos / pessoas.


EDITAR


Seguindo a resposta de @jjimenezg93, criei um sistema muito básico em C # para teste, ele funciona! Veja se você pode adicionar algo a ele:

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);
}

Até agora, IItem e IAttribute são interfaces base. Não havia necessidade (que eu possa pensar) de ter uma interface / atributo base para a mensagem, portanto, criaremos diretamente uma classe de mensagem de teste. Agora para as classes de teste:


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 são as configurações necessárias. Agora podemos usar o sistema (a seguir é para o 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);
    }

}

Anexe esse script do TestManager a um componente na cena e você poderá ver na depuração que a mensagem foi passada com êxito.


Para explicar as coisas: Todos os itens do jogo implementam a interface IItem e todos os Atributos (o nome não deve confundi-lo, significa o recurso / sistema do item. Como Atualizável ou Desencantável) implementará o IAttribute. Então, temos um método para processar a mensagem (por que precisamos de uma mensagem será explicado em mais um exemplo). Portanto, no contexto, você pode anexar atributos a um item e deixar o resto fazer por você. O que é muito flexível, porque você pode adicionar / remover atributos com facilidade. Portanto, um pseudo-exemplo seria desencantável. Teremos uma classe chamada Disenchantable (IAttribute) e, no método Disenchant, solicitará:

  • Liste os ingredientes (quando o item estiver desencantado, qual item deve ser entregue ao jogador) nota: O item II deve ser estendido para ter ItemType, ItemID etc.
  • int resultModifier (se você implementar um tipo de aumento no recurso desencantador, poderá passar um int aqui para aumentar os ingredientes recebidos quando desencantado)
  • int failChance (se o processo de desencanto tiver uma chance de falha)

etc.

Essas informações serão fornecidas por uma classe chamada DisenchantManager, ele receberá o item e formará esta mensagem de acordo com o item (ingredientes do item quando desencantado) e a progressão do jogador (resultModifier e failChance). Para passar esta mensagem, criaremos uma classe DisenchantMessage, que atuará como um corpo para esta mensagem. Portanto, o DisenchantManager preencherá uma DisenchantMessage e a enviará ao item. O item receberá a mensagem e a passará para todos os Atributos anexados. Como o método ReceiveMessage da classe Disenchantable procurará uma DisenchantMessage, apenas o atributo Disenchantable receberá essa mensagem e atuará nela. Espero que isso esclareça tanto as coisas quanto para mim :).

Vandarthul
fonte
1
Você pode encontrar alguma inspiração útil para os sistemas modificadoras usadas em Para Honra
DMGregory
@DMGregory Hey! Obrigado pelo link. Embora pareça muito engenhoso, infelizmente eu preciso da conversa para entender o conceito. E tento descobrir a conversa real, que infelizmente é o conteúdo exclusivo para membros do GDCVault (495 $ por um ano é insano!). (Você pode encontrar a palestra aqui se você for membro do GDCVault -, -
Vandarthul
Como exatamente seu conceito "BaseStat" impediria armas fabricáveis?
Attackfarm
Realmente não impede, mas realmente não se encaixa no contexto em minha mente. É possível adicionar "Madeira", 2 e "Ferro", 5 como BaseStat a uma receita de artesanato, que daria a espada. Eu acho que mudar o nome de BaseStat para BaseAttribute serviria melhor nesse contexto. Mas, ainda assim, o sistema não poderá atender a seu objetivo. Pense em itens consumíveis com 5min -% 50 de poder de ataque. Como eu o passaria como BaseStat? "Perc_AttackPower", 50 isso precisa ser resolvido como "se for Perc, trate um número inteiro como uma porcentagem" e perca as informações de minutos. Espero que você entenda o que quero dizer.
Vandarthul 1/09/17
@ Attackfarm pensando bem, esse conceito "BaseStat" pode ser expandido com uma lista de entradas em vez de apenas um int. então, para o bônus consumível, eu posso fornecer "Attack", 50, 5, 1 e o IConsumable procuraria 3 números inteiros, 1. - valor, 2. - minutos, 3. - se é uma porcentagem ou não. Mas parece impulsivo à medida que outros sistemas entram e são forçados a se explicar apenas em int.
Vandarthul

Respostas:

6

Eu acho que você pode conseguir o que deseja em termos de escalabilidade e capacidade de manutenção usando um Sistema de Componentes de Entidades com herança básica e um sistema de mensagens. Obviamente, lembre-se de que esse sistema é o mais modular / personalizável / escalável que consigo imaginar, mas provavelmente terá um desempenho pior do que sua solução atual.

Vou explicar melhor:

Primeiro de tudo, você cria uma interface IIteme uma interface IComponent. Qualquer item do qual você deseja armazenar deve herdar IIteme qualquer componente do qual você deseja afetar seus itens deve herdar IComponent.

IItemterá uma matriz de componentes e um método de manipulação IMessage. Esse método de manipulação simplesmente envia qualquer mensagem recebida para todos os componentes armazenados. Em seguida, os componentes que estão interessados ​​nessa mensagem agem de acordo e os outros a ignoram.

Uma mensagem, por exemplo, é do tipo dano e informa tanto o atacante quanto o atacado, para que você saiba o quanto acertou e talvez carregue sua barra de fúria com base nesse dano. Ou a IA do inimigo pode decidir correr se atingir você e causar menos de 2HP de dano. Estes são exemplos idiotas, mas, usando um sistema semelhante ao que estou mencionando, você não precisará fazer nada além de criar uma mensagem e os procedimentos apropriados para adicionar a maior parte desse tipo de mecânica.

Eu tenho uma implementação para um ECS com mensagens aqui , mas isso é usado para entidades em vez de itens e usa C ++. Enfim, acho que ele pode ajudar se você der uma olhada component.h, entity.he messages.h. Há muitas coisas a serem melhoradas, mas funcionou para mim nesse simples trabalho universitário.

Espero que ajude.

jjimenezg93
fonte
Hey @ jjimenezg93, obrigado pela sua resposta. Então, para elaborar com um exemplo simples do que você explicou: Queremos uma espada que seja: - Desencantável [Componente] - Stat Modifier [Componente] - Atualizável [Componente] Eu tenho uma lista de ações que contém todas as coisas como - DISENCHANT - MODIFY_STAT - UPGRADE Sempre que um item recebe essa mensagem, passa por todos os seus componentes e envia essa mensagem, cada componente saberá o que fazer com a mensagem especificada. Em teoria, isso parece incrível! Não verifiquei o seu exemplo, mas vou, muito obrigado!
Vandarthul 1/09/17
@ Vandarthul Sim, essa é basicamente a ideia. Dessa forma, o item não saberá nada sobre seus componentes; portanto, não há acoplamento e, ao mesmo tempo, terá toda a funcionalidade desejada, que também pode ser compartilhada entre diferentes tipos de itens. Espero que atenda às suas necessidades!
Jjimenezg93 01/09/19