Considere um jogo de cartas, como Hearthstone .
Existem centenas de cartões que fazem uma grande variedade de coisas, algumas das quais são únicas até para um único cartão! Por exemplo, existe um cartão (chamado Nozdormu) que reduz o turno do jogador para apenas 15 segundos!
Quando você tem uma variedade tão grande de efeitos em potencial, como evita números mágicos e verificações pontuais em todo o seu código? Como evitar um método "Check_Nozdormu_In_Play" na classe PlayerTurnTime? E como organizar o código de forma que, quando você adiciona ainda mais efeitos, não precise refatorar os sistemas principais para dar suporte a coisas que eles nunca tiveram que suportar antes?
architecture
software-engineering
scripting
Sonhador Sable
fonte
fonte
Respostas:
Você analisou os sistemas de componentes da entidade e as estratégias de mensagens de eventos?
Os efeitos de status devem ser componentes de algum tipo que possam aplicar seus efeitos persistentes em um método OnCreate (), expirar seus efeitos em OnRemoved () e assinar mensagens de eventos do jogo para aplicar efeitos que ocorrem como uma reação a algo acontecendo.
Se o efeito for persistentemente condicional (dura X turnos, mas se aplica apenas em determinadas circunstâncias), talvez seja necessário verificar essas condições em várias fases.
Então, apenas verifique se o seu jogo também não possui números mágicos padrão. Certifique-se de que tudo o que pode ser alterado seja uma variável orientada a dados, em vez de padrões codificados com variáveis usadas para quaisquer exceções.
Dessa forma, você nunca assume qual será o comprimento do turno. É sempre uma variável constantemente verificada que pode ser alterada por qualquer efeito e possivelmente desfeita posteriormente pelo efeito quando ela expira. Você nunca verifica exceções antes de usar o número mágico como padrão.
fonte
RobStone está no caminho certo, mas eu queria elaborar, pois foi exatamente o que fiz quando escrevi Dungeon Ho !, um Roguelike que tinha um sistema de efeitos muito complexo para armas e feitiços.
Cada carta deve ter um conjunto de efeitos, definidos de forma que possam indicar qual é o efeito, o que é direcionado, como e por quanto tempo. Por exemplo, um efeito "prejudicar o oponente" pode ser algo assim;
Em seguida, quando o efeito for acionado, faça com que uma rotina genérica processe o processamento do efeito. Como um idiota, usei uma enorme declaração case / switch:
Mas uma maneira muito melhor e mais modular de fazer isso é via polimorfismo. Crie uma classe Effect que agrupe todos esses dados, crie uma subclasse para cada tipo de efeito e faça com que essa classe substitua um método onExecute () específico para a classe.
Portanto, teríamos uma classe Effect básica, depois uma classe DamageEffect com um método onExecute (), portanto, em nosso código de processamento, apenas iríamos;
A maneira de lidar com o que está em jogo é criar uma lista Vector / Array / vinculada / etc. dos efeitos ativos (do tipo Efeito, a classe base) anexados a qualquer objeto (incluindo o campo de jogo / "jogo"), portanto, em vez de verificar se um efeito específico está em jogo, basta percorrer todos os efeitos associados a o (s) objeto (s) e deixe-os executar. Se um efeito não está anexado a um objeto, ele não está em jogo.
fonte
Vou oferecer algumas sugestões. Alguns deles se contradizem. Mas talvez alguns sejam úteis.
Considere listas versus sinalizadores
Você pode percorrer o mundo e verificar uma bandeira em cada item para decidir se deve fazer a bandeira. Ou você pode manter uma lista apenas dos itens que devem fazer o sinalizador.
Considere listas e enumerações
Você pode continuar adicionando campos booleanos à sua classe de item, isAThis e isAThat. Ou você pode ter uma lista de strings ou elementos de enumeração, como {"isAThis", "isAThat"} ou {IS_A_THIS, IS_A_THAT}. Dessa forma, você pode adicionar novos na enumeração (ou string consts) sem adicionar campos. Não que haja algo realmente errado em adicionar campos ...
Considere ponteiros de função
Em vez de uma lista de sinalizadores ou enumerações, pode haver uma lista de ações a serem executadas para esse item em diferentes contextos. (Entidade-ish…)
Considere objetos
Algumas pessoas preferem abordagens de entidade orientada a dados, com script ou componente. Mas também vale a pena considerar as hierarquias de objetos à moda antiga. A classe base precisa aceitar as ações, como "jogar esta carta na fase B do turno" ou o que for. Cada tipo de cartão pode substituir e responder conforme apropriado. Provavelmente também existe um objeto de jogador e um objeto de jogo, para que o jogo possa fazer coisas como, se (player-> isAllowedToPlay ()) {faça a jogada ...}.
Considere a capacidade de depuração
Uma coisa interessante sobre uma pilha de campos de bandeira é que você pode examinar e imprimir o estado de cada item da mesma maneira. Se o estado é representado por diferentes tipos, bolsas de componentes ou ponteiros de função, ou estar em listas diferentes, pode não ser suficiente apenas olhar para os campos do item. São todas as trocas.
Eventualmente, refatoração: considere testes de unidade
Não importa o quanto você generalize sua arquitetura, poderá imaginar coisas que ela não cobre. Então você terá que refatorar. Talvez um pouco, talvez muito.
Uma maneira de tornar isso mais seguro é com um corpo de testes de unidade. Dessa forma, você pode ter certeza de que, mesmo reorganizando as coisas por baixo (talvez muito!), A funcionalidade existente ainda funciona. Cada teste de unidade é, geralmente, assim:
Como você pode ver, manter essas chamadas de API de nível superior no jogo (ou jogador, cartão etc.) é essencial para a estratégia de teste de unidade.
fonte
Em vez de pensar em cada cartão individualmente, comece a pensar em termos de categorias de efeitos, e os cartões contêm uma ou mais dessas categorias. Por exemplo, para calcular a quantidade de tempo em um turno, você pode percorrer todas as cartas em jogo e verificar a categoria "manipular duração do turno" de cada carta que contém essa categoria. Cada carta aumenta ou sobrescreve a duração do turno com base nas regras que você decidiu.
Este é essencialmente um sistema de minicomponentes, onde cada objeto "cartão" é simplesmente um contêiner para vários componentes de efeito.
fonte