Use uma instância ou classe para recursos do jogo (madeira, ferro, ouro)

31

Então, eu estou criando um jogo no qual você pode enviar navios para locais para vender ou comprar recursos como madeira, ferro, ouro etc.

Agora eu queria saber como os recursos devem ser criados no jogo. Eu vim com 2 opções

  1. Crie uma classe para cada recurso:

    public class ResourceBase {
        private int value;
        // other base properties
    }
    
    public class Gold : ResourceBase {
         public Gold {
             this.value = 40 // or whatever
         }
    }
    
  2. Criar instâncias de uma classe Resource

    public class Resource {
        string name;
        int value;
    
        public Resource(string name, int value) {
            this.name = name;
            this.value = value;
         }
     }
    
    // later on...
    
    Resource gold = new Resource("Gold",40);
    

Com a segunda opção, é possível preencher os recursos do jogo a partir de um arquivo resources.json, eu meio que gosto dessa estrutura.

Novas idéias / padrões / estruturas de design são sempre bem-vindos!

EDIT: É um pouco como o aplicativo complementar de Assassins Creed Black Flag. Veja a barra de recursos na imagem abaixo

EDIÇÃO 2: Pesquisei mais sobre o "carregar itens / recursos do arquivo JSON" e encontrei este blog: Poder do JSON no desenvolvimento de jogos - itens . Ele mostra o melhor das opções 1 e 2. Você ainda pode adicionar funcionalidades a cada recurso :)

insira a descrição da imagem aqui

MDijkstra
fonte
1
Minecraft tem classes separadas para cada recurso (não que você deve)
MCMastery
@MCMastery ohh é minecraft de código aberto? Eu gostaria de dar uma olhada nessa estrutura. E thx por mencionar #
MDijkstra
1
Não está, mas está sempre sendo ofuscado para servidores ... consulte github.com/Wolf-in-a-bukkit/Craftbukkit/tree/master/src/main/…
MCMastery
12
Não use uma string como essa. É muito fácil digitar "ouro" com "glod" ou similar, e é muito difícil depurar. Use um em enumvez disso.
Nolonar
O Minecraft não é tecnicamente de código aberto, mas foi quase completamente descompilado e desobstruído. Você não deve ser capaz de encontrar um lugar fonte deobfuscated na Internet, mas você pode baixar e executar o processo de fazê-lo a partir files.minecraftforge.net (o download MDK é o que você quiser)
JamEngulfer

Respostas:

51

Siga a segunda abordagem, simplesmente pelo fato de poder introduzir novos tipos ou itens de recursos a qualquer momento, sem precisar reescrever ou atualizar o código ( desenvolvimento orientado a dados ).


Editar:

Para elaborar um pouco mais sobre por que isso é uma boa prática geral, mesmo que você tenha 100% de certeza de que algum valor nunca será alterado.

Vamos dar o exemplo do jogo de console mencionado nos comentários, porque isso fornece uma boa razão para você não codificar nada, a menos que você realmente tenha (ou queira), por exemplo, chaves de criptografia, seu próprio nome, etc.

Ao lançar um jogo em consoles, eles geralmente precisam passar por um processo de revisão, onde o controle de qualidade do fabricante do console testará o jogo, jogará com ele, procurará por problemas etc. Isso é obrigatório e custa muito dinheiro. Acho que li uma vez que um release pode custar entre US $ 30.000 e US $ 50.000 apenas para a certificação.

Agora imagine que você está apostando no lançamento, pagando os US $ 30.000 e aguarde. E de repente você percebe que alguns dos valores do seu jogo estão literalmente quebrados. Digamos que você possa comprar barras de ferro por 50 de ouro e vendê-las por 55 de ouro.

O que você faz? Se você codificou essas coisas, terá que criar uma nova versão de atualização / lançamento, que precisará ser revisada mais uma vez; portanto, na pior das hipóteses, você pagará mais uma vez pela recertificação! Aposto que a Ubisoft não se importará, mas para o seu próprio bolso de desenvolvedor de jogos indie ... ai!

Mas imagine que o jogo apenas verifique ocasionalmente as definições atualizadas do jogo (por exemplo, na forma de um arquivo JSON). Seu jogo pode baixar e usar a versão mais recente a qualquer momento, sem a necessidade de uma nova atualização. Você pode corrigir esse desequilíbrio / exploração / bug a qualquer momento, sem exigir a recertificação ou qualquer outro dinheiro pago. Apenas uma pequena decisão de projeto, você não achou que valesse a pena, apenas economizou uma quantia em dinheiro de 5 dígitos! Isso não é incrível? :)

Só não me interpretem mal. Isso também se aplica a outros tipos de software e plataformas. O download de arquivos de dados atualizados é geralmente muito mais fácil e viável para o usuário padrão, não importa se é um jogo ou algum tipo de aplicativo / ferramenta. Imagine um jogo instalado em Arquivos de Programa no Windows. Seu atualizador precisa de direitos de administrador para modificar qualquer coisa e você não pode modificar os programas em execução. Com o DDD, seu programa apenas baixa os dados e os utiliza. O jogador pode nem perceber que houve uma atualização.

Mario
fonte
6
+1 para DDD. Isso é muito apropriado para coisas como recursos.
Kromster diz apoio Monica
@ Funnydutchman, se a resposta de alguém o ajudou, é habitual no Stack Exchange para votar novamente na resposta clicando na seta acima do número à esquerda. Se uma resposta o ajudou mais, clique na marca de seleção abaixo da seta inferior para aceitar a resposta. Ambas as ações dão ao respondente um bônus na reputação e tornam as respostas utilizáveis ​​mais visíveis para a comunidade.
Nzall
2
Não vejo como você teve que reescrever código da primeira maneira; tudo o que você faria é adicionar uma classe (a menos que eu esteja perdendo alguma coisa).
SirPython
4
E aqui está a diferença entre o código orientado a objetos e o código obcecado por objetos. Só porque você pode criar uma hierarquia de objetos não significa necessariamente que você deveria.
corsiKa
2
@ AgustínLado Se eu tivesse um dólar por cada vez que um cliente dissesse "Isso nunca muda" e isso mudasse, eu poderia nos levar a um restaurante decente para jantar (embora tenhamos que economizar um pouco na gorjeta, talvez apenas uma medíocre restaurante ... ainda bastante vezes para jantar para dois embora).
corsiKa
29

Uma regra prática é que você use classes diferentes quando os objetos requerem código diferente e instâncias da mesma classe quando os objetos exigem apenas valores diferentes.

Quando os recursos têm uma mecânica de jogo diferente, exclusiva para eles, pode fazer sentido representá-los com classes. Por exemplo, quando você tem plutônio com meia-vida e coelhos que procriam de acordo com a sequência de fibonacci , pode fazer sentido ter uma classe base Resourcecom um método Updateimplementado como no-op e duas classes derivadas HalfLifeResourcee SelfGrowingResourceque substitua esse método para diminuir / aumentar o valor (e talvez também inclua lógica para controlar o que acontece quando você armazena os dois próximos um do outro).

Mas quando seus recursos são realmente apenas números estúpidos, não faz sentido implementá-los com algo mais sofisticado do que uma variável simples.

Philipp
fonte
Obrigado @Phillipp pela sua resposta. Era isso que eu estava pensando. Mas os recursos realmente não mudam em termos de propriedades / métodos, por isso irei com a opção 2 por enquanto #
MDijkstra
14

Gostaria de acrescentar que há duas opções extras:
Interface: você pode considerar se a classe de recurso seria apenas um "armazenamento" para 5 números inteiros e cada outra entidade teria uma lógica diferente em relação aos recursos (por exemplo, gasto / saque do jogador, cidade produz recurso). Nesse caso, você pode não querer uma classe - apenas deseja expor que alguma entidade possui uma variável com a qual outras pessoas podem interagir:

interface IHasResources {
  int Gold { get; set; }
  int Wood { get; set; }
  int Cloth { get; set; }
  // ....
}

class Player : IHasResources { }
class City: IHasResources { }

Além disso, isso tem a vantagem de poder saquear uma cidade com exatamente o mesmo código que saquearia a frota inimiga, promovendo a reutilização do código.
A desvantagem é que a implementação dessa interface pode ser tediosa se muitas classes a implementarem, então você pode acabar usando interfaces, mas não para o problema original, resolvendo-o com as opções 1 ou 2:

interface IHasResources { 
   IEnumerable<Resource> Resources { get; }
}

Instâncias (com enum): em alguns casos, seria desejável usar enum em vez de string ao definir o tipo de recurso:

enum Resource {
   Gold, Wood, Cloth, //...
}

class ResourceStorage {
   public Resource ResourceType { get; set; }
   public int Value { get; set; }
}

isso fornecerá um pouco de "segurança", porque seu IDE fornecerá uma lista de todos os recursos válidos ao atribuí-lo após a gravação do ponto ResourceType = Resource.. Além disso, existem mais "truques" com enumerações que você pode precisar, como Tipo explícito ou composição / sinalizadores que posso imaginar ser útil ao criar:

[Flags]
enum Metal {
 Copper = 0x01/*01*/, Tin = 0x02/*10*/, Bronze = 0x03/*11*/
}
Metal alloy = Metal.Copper; //lets forget Value(s) for sake of simplicity
alloy |= Metal.Tin; //now add a bit of tin
Console.WriteLine(alloy); //will print "Bronze"


Quanto ao que é melhor - não existe uma bala de prata, mas pessoalmente eu me inclinaria para qualquer forma de instância, elas podem não ser tão sofisticadas quanto a interface, mas são simples, não desordenam a árvore de herança e são amplamente compreendidas.

wondra
fonte
2
Obrigado pela sua resposta @wondra I como a opção de enumeração e como você fez aquele bronze de cobre e estanho; p
MDijkstra
2
Entrei nesta comunidade especificamente para poder votar novamente na solução enum. Você também pode usar o atributo Descrição da enumeração para mapear de alguma representação JSON da enumeração ( my-great-enum) para alguma representação em C # da enumeração ( MyGreatEnum).
Dan Forbes
Aparentemente, estou fora do loop Java há muito tempo. Desde quando é permitido ter declarações de campo para interfaces? Tanto quanto sei, essas são automaticamente estáticas e finais e, portanto, não são muito úteis para os fins descritos na pergunta.
M.Herzkamp
2
@ M.Herzkamp não é um campo, é uma propriedade - e o Java não suporta propriedades. A pergunta está marcada como C #, C # permite propriedades nas interfaces - afinal, as propriedades são métodos subjacentes. Receio que o mesmo vale para a anotação de sinalizadores, não suportada em Java, embora não tenha certeza absoluta.
Wondra
Obrigado por esclarecer isso! Eu perdi o C # tag eo código em questão parecia tão terrivelmente iguais Java: D
M.Herzkamp
2

Você deve usar a opção 1, que possui 3 vantagens:

  1. É mais fácil instanciar um recurso - em vez de ter que passar o tipo do recurso como parâmetro, você faria, por exemplo, new Gold(50)
  2. Se você precisar dar um comportamento especial a um recurso, poderá alterá-lo de classe.
  3. É mais fácil verificar se um recurso é de um determinado tipo - resource instanceof Gold
Zac
fonte
Todas as respostas têm seus prós e contras, é difícil escolher qual é o melhor. Fiz uma edição da pergunta, o que você acha dessa estrutura?
MDijkstra
1

Se seus recursos não possuem lógica comercial própria ou cenários especiais de como eles interagem com o ambiente, outros recursos (além do comércio que você cobriu) ou o player, a opção dois facilita a adição de novos.

Se você precisar considerar situações como o que aconteceria se você misturasse dois recursos para a elaboração, se os recursos puderem ter propriedades muito diferentes (um balde de água é muito diferente de um pedaço de carvão) ou outras interações econômicas complexas, a abordagem 1 será muito mais flexível para o mecanismo, mas não os dados.

Kristian Williams
fonte