Como posso melhorar a implementação do banco de dados inimigo?

7

Estou desenvolvendo um RPG e estou no ponto em que preciso começar a construir um banco de dados inimigo. Existem alguns desafios associados a isso e a algumas soluções que venho considerando.

Aqui está o que eu preciso fazer no meu banco de dados inimigo:

Eu tenho duas classes inimigas principais das quais preciso representar dados:

Uma classe inimiga base que inclui o seguinte:

Base Stats
Status Resistance Table
Elemental Resistance Table
Steal Table
Drop Table
Level
Unique ID
Base XP
AI Hook
Name
Display Name

E uma classe derivada que adiciona a capacidade de adicionar equipamentos, adicionando os seguintes campos:

Main Weapon
Secondary Weapon/Equipment
Armor
Accessories

Posso adicionar campos ou classes adicionais no futuro, se fizer sentido. Eu considerei dois formatos possíveis para inimigos de banco de dados.

Arquivos XML

Eu basicamente faria assim:

<?xml version="1.0" encoding="utf-8"?>
<Enemies>
  <Enemy name="Red Dragon" type="BaseEnemy" level="56" displayname="Red Dragon">
    <Stats HP="55000" MP="2500" SP="2500" Strength="212" Vitality="125" Magic="200" Spirit="162" Skill="111" Speed="109" Evasion="100" MgEvasion="100" Accuracy="100" Luck="55"/>
    <StatusResistances>
      <Resistance name="Sleep" value="100" />
      <Resistance name="Stop" value="100" />
    </StatusResistances>
    <ElementResistances>
      <Resistance name="Fire" value="75" />
    </ElementResistances>
    <LootTable>
      <Item name="Elixir" rate="0.03" count="1"/>
    </LootTable>
    <DropTable>
      <Item name="Elixir" rate="0.03" count="1"/>
    </DropTable>
    <AIScript value="BasicBehaviour.py" />
    <BaseXP value="4800"/>
  </Enemy>
  <Enemy name="Gaverick 1" type="HumanoidEnemy" level="33" displayname="Gaverick">
  <!--Same Stuff as above here-->
    <Equipment>
      <Weapon name="Dark Eclipse"/>
      <Armor name="Terra Defense"/>
      <Accessory name="Ribbon"/>
    </Equipment>
  </Enemy>
</Enemies>

Vantagens:

  • Fácil de estender se precisar adicionar / reorganizar parâmetros
  • fácil de atribuir valores padrão
  • Eu já tenho um analisador XML (pugixml) incluído para arquivos de configuração, mapas lado a lado e descrição de recursos

Desvantagens:

  • potencialmente lento (meu banco de dados provavelmente atingirá várias centenas de inimigos)
  • não pode procurar inimigos arbitrários, provavelmente precisará manter todos os inimigos na memória.
  • Isso significa que eu preciso reiniciar o jogo para carregar dados alterados do inimigo também

SQLite

Para isso, eu basicamente faria uma tabela com colunas representando todos os dados necessários e deixaria os campos desnecessários vazios

Vantagens

  • A consulta arbitrária pode manter os dados inimigos desnecessários fora da memória
  • Sente-se mais estruturado
  • Tamanho de arquivo menor

Desvantagens

  • Mais difícil estender / reorganizar pedidos de parâmetros
  • Sobrecarga desnecessária para campos não utilizados
  • Será necessário escrever um wrapper de interface de banco de dados para sqlite

Com isso em mente, fiquei curioso em obter alguma experiência externa sobre o que outras pessoas fizeram. Eu posso estar pensando sobre isso totalmente errado e, se assim for, sugira uma alternativa para as duas possibilidades que tenho aqui.

Além disso, qualquer sugestão sobre como melhorar uma dessas possibilidades seria apreciada. Realmente, eu só quero saber se estou no caminho certo.

Estou aberto a usar qualquer biblioteca gratuita e já estou incorporando o impulso

user127817
fonte

Respostas:

12

Não vejo necessidade da complexidade de um banco de dados totalmente relacional. Os bancos de dados relacionais existem para facilitar operações complexas de pesquisa e para lidar com pesquisas em vastos conjuntos de dados. Se tudo o que você faz no jogo é indexar uma delas toda vez que um inimigo gera, você está usando uma ferramenta incrivelmente complicada demais para fazer isso.

Em resumo, o SQLite é um exagero.

Nesse ponto, a questão não é SQLite x XML. É como você o armazena em disco e como deseja representar os dados no jogo. Se o único tipo de consulta que você faz no jogo é procurar pelo nome, uma simples std::mapseria suficiente. Na verdade, você pode colocá-los std::vectore classificá-los após o carregamento para uma pesquisa rápida.

Para a representação em disco, você deve se preocupar mais com o formato que facilita a edição , não a leitura. Ler é algo que você pode fazer com bastante facilidade no código; edição você tem que fazer uma vez por inimigo. Ao projetar novos inimigos, você precisará editar muito os dados deles.

Para mim, a dicotomia é entre XML e Lua , que é perfeitamente útil como uma linguagem de descrição de dados. Por exemplo, o arquivo XML de exemplo pode ser representado em Lua como:

return
{
    {
        name = "Red Dragon", type="BaseEnemy", level=56, displayname="Red Dragon",
        stats = {
            HP=55000, MP=2500, SP=2500, Strength=212, Vitality=125,
            Magic=200, Spirit=162, Skill=111, Speed=109,
            Evasion=100, MgEvasion=100, Accuracy=100, Luck=55
        },
        status_resistances = {
            {name="Sleep", value=100},
            {name="Stop", value=100},
        },
        element_resistances = {
            {name="Fire", value=75},
        },
        loot_table = {
            {name="Elixir", rate=0.03, count=1},
        },
        drop_table = {
            {name="Elixir", rate=0.03, count=1},
        },
        script = "BasicBehaviour.py",
        xp = 4800,
    },
    {
        name="Gaveric 1", type="HumanoidEnemy", level=33, displayname="Gaveric",
        --Same Stuff as above here
        equipment = {
            weapon = "Dark Eclipse",
            armor = "Terra Defense",
            accessory = "Ribbon",
        }
    }
}

A maior vantagem de uma abordagem baseada em script Lua é que ... é Lua. Embora possam ser dados puros, não precisa ser. Portanto, se você tiver algumas repetições de blocos de estatísticas, poderá facilmente fazer com que o script Lua gere esses dados para você. Tudo isso é um script Lua que retorna uma tabela; a tabela é o que você lê em suas estruturas de dados na memória.

A desvantagem dessa abordagem é principalmente se você não estiver usando uma ferramenta para gravar esses arquivos. Se você possui uma ferramenta usada para editar esses arquivos inimigos, a abordagem Lua é boa. Mas se você estiver editando manualmente, isso significa que seu código de carregamento que anda na tabela Lua precisa verificar a entrada. Ele precisa verificar se os campos necessários estão lá, se cada valor numérico é um número válido etc.

E, embora esse código não seja exatamente difícil de escrever, é extremamente entediante. A vantagem do XML é que você pode validá-lo com um esquema RelaxNG ou WXS. Existem até editores XML que possuem edição guiada por esquema, para que se torne impossível gravar um arquivo XML inválido .

Se você precisar adicionar um novo campo, basta ajustar seu esquema e você estará bem. Se você alterar a estrutura do arquivo, ajuste novamente seu esquema e revalide os arquivos, corrigindo os erros onde eles aparecem. Você pode até escrever uma ferramenta XSLT para converter automaticamente arquivos de uma versão em formato para outra.

Obviamente, você precisa saber como escrever esquemas e ter um formato XML orientado a esquemas. Caso contrário, o script Lua não será pior, pois você deve validar os dados de qualquer maneira. E o script Lua é sem dúvida mais fácil de analisar, pois permite consultar os dados diretamente. Dada uma tabela Lua que contém uma definição de inimigo, você pode consultar "elementos" específicos por nome. Com um analisador de XML, é necessário processar os elementos que chegam até você, o que geralmente é um pouco mais complexo de escrever.

não pode procurar inimigos arbitrários, provavelmente precisará manter todos os inimigos na memória.

... tão? As estruturas de dados, tiradas do seu exemplo XML, consumiriam (sem tentativas de compactação:

  • 14 estatísticas * 4 bytes por = 56 bytes.
  • Nome interno: sequência de 32 bytes.
  • Nome para exibição: sequência de 64 bytes.
  • tipo: parte da estrutura de dados; não precisa ser armazenado.

Cada arma também pode ser uma cadeia de 32 bytes. Então, um total geral no pior dos casos ... 248 bytes. Você pode armazenar mais de 3500 pessoas na metade de um MB de RAM. Eu não me importaria com isso.

Isso significa que eu preciso reiniciar o jogo para carregar dados alterados do inimigo também

Por quê? Isso é contigo. Não há nada que diga que você não pode despejar os dados do inimigo e recarregá-los no meio de um jogo. Se você não pode fazer isso, seria apenas porque você não estruturou adequadamente seu código para tornar isso possível.

Nicol Bolas
fonte
+1 para Lua. Além disso, não se esqueça do YAML.
Michael.bartnett 19/10/11
6

Eu tenho duas classes inimigas principais das quais preciso representar dados. Uma classe inimiga base ... e uma classe derivada ...

Não estou convencido de que você precise de duas aulas para isso; um único deve ser suficiente. Você poderia facilmente modelar isso com composição e não com herança ; técnicas modernas de design de software estão se afastando de hierarquias profundas de classe, porque tendem a ser frágeis e difíceis de manter. Embora sua hierarquia proposta não seja muito profunda, ela é desnecessária (que é o primeiro passo em direção a "profunda"). Sua classe derivada adiciona apenas propriedades que poderiam facilmente permanecer na classe base, mas deixadas em branco.

Isso não apenas permitirá iterar e ajustar melhor o comportamento e as propriedades de seus inimigos individuais posteriormente, como também permite que seu código seja mais simples, pois você lida com apenas uma única API pública e seu processamento.

Eu considerei dois formatos possíveis para inimigos de banco de dados.

Desses dois, eu escolheria XML. Em geral, você pode querer olhar para o JSON . Eu não acho que o uso de um banco de dados relacional real aqui seja garantido. Consulte esta pergunta para uma discussão sobre o porquê: a versão curta é complexa e menos direta para acessar ou editar e, como você não precisa das vantagens de um banco de dados relacional, é melhor seguir algo semelhante.

XML e JSON têm a enorme vantagem de serem trivialmente legíveis e editáveis ​​por humanos, sem a necessidade de ferramentas especializadas, o que pode realmente ajudar a aumentar o tempo de iteração. O XML permite que você use o XSLT para transformar em massa seus dados, caso você mude drasticamente o esquema ou precise fazer algum outro tipo de migração em larga escala.

Especificamente para suas três "vantagens" no SQLite:

  • Você pode facilmente manter os dados fora da memória quando estiverem em um arquivo em um disco (possivelmente dependendo do seu leitor XML), mas provavelmente não deseja fazer isso, pois o disco é "lento" e provavelmente não Não há dados suficientes para causar muita pressão na memória.
  • É tão estruturado quanto você o faz e projetar esquemas de banco de dados normalizados e realmente bons nem sempre é uma tarefa trivial. Geralmente, você obtém os ganhos máximos de um banco de dados quando possui um design de esquema adequado. É muito mais difícil mudar o esquema também.
  • Você sempre pode fazer a transição para um formato de arquivo binário para enviar seu jogo que reduz ou elimina a sobrecarga inerente a um formato mais detalhado e baseado em texto, como XML ou JSON. É bem fácil de fazer.
Comunidade
fonte
3

Aqui está outra maneira de analisar, criar uma estrutura de dados, preenchê-la com seus valores de monstro e salvar a estrutura de dados em um arquivo binário e colocá-la em um arquivo pak como:

ork_footsoldier.npcdat
ork_chief.npcdat
guard.npcdat
 Into..
NPCData.pak

Em seguida, carregue o pak na memória, analise os arquivos npcdat e analise os dados neles, mediante solicitação. É mais rápido que XML e SQLLite e ocupa muito menos espaço.

Matt Jensen
fonte
@Skeith, eu concordo. Um arquivo de texto simples delimitado fará o trabalho que user127817 está descrevendo sem muita dificuldade.
Christopher
@ Christopher: Só que você teria que escrever um analisador para ele. E algo para carregar os arquivos do pacote. E, bem, muitas coisas que você não tem a ver com apenas carregar um arquivo XML. O "texto simples delimitado" também é mais difícil de editar, pois não há ferramentas de edição guiadas para eles. Você pode facilmente errar o formato e não saberá até tentar carregá-lo.
Nicol Bolas
Escrever um analisador de dados binários é muito mais rápido e fácil do que um analisador de texto; além disso, em um jogo de produção, é muito mais difícil modificar os valores para evitar fraudes.
Matt Jensen
0

A única vantagem do SQLite em que consigo pensar é na redução de duplicação, ou seja, se cair na parte de vantagem "Sensação mais estruturada".

Mas você pode consultar XML com XPath, se desejar. Mas acho que para centenas (e até milhares) o armazenamento de todos os dados na memória não é crítico. Com essa quantidade de tamanho de dados do arquivo db ou XML, não é crítico.

Eu voto em XML.

Petr Abdulin
fonte