Estou fazendo um jogo 2D de cima para baixo e quero ter vários tipos de ataque diferentes. Eu gostaria de tornar os ataques muito flexíveis e combináveis da maneira que The Binding of Isaac funciona. Aqui está uma lista de todos os itens colecionáveis do jogo . Para encontrar um bom exemplo, vejamos o item Spoon Bender .
Spoon Bender dá a Isaac a capacidade de atirar em lágrimas.
Se você olhar a seção "sinergias", verá que ela pode ser combinada com outros itens colecionáveis para obter efeitos interessantes, porém intuitivos. Por exemplo, se combinar com o Olho Interior , "permitirá que Isaac dê vários tiros de volta ao mesmo tempo". Isso faz sentido, porque The Inner Eye
Dá um tiro triplo a Isaac
O que é uma boa arquitetura para projetar coisas assim? Aqui está uma solução de força bruta:
if not spoon bender and not the inner eye then ...
if spoon bender and not the inner eye then ...
if not spoon bender and the inner eye then ...
if spoon bender and the inner eye then ...
Mas isso ficará fora de controle muito rápido. Qual é a melhor maneira de projetar um sistema como esse?
fonte
Respostas:
Você não precisa totalmente de combinações de códigos de mão. Em vez disso, você pode se concentrar nas propriedades que cada item fornece. Por exemplo, o item A é definido
Projectile=Fireball,Targetting=Homing
. Item B conjuntosFireMode=ArcShot,Count=3
. AArcShot
lógica é responsável por enviar oCount
número deProjectile
itens em um arco.Esses dois itens podem ser combinados com outros itens que modificam essas (ou outras) propriedades livremente. Se você adicionar um novo tipo de projétil, ele funcionará automaticamente
ArcShot
e, se você adicionar um novo modo de disparo, ele funcionará automaticamente comFireball
projéteis. Da mesma forma,Targetting
é uma propriedade que define o controlador para os projéteis enquantoFireMode
cria os projéteis, para que eles possam ser combinados de maneira fácil e trivial em qualquer combinação que faça sentido.Você também pode definir dependências de propriedade e tal. Por exemplo,
ArcShot
requer que você tenha um provedor deProjectile
(que pode ser apenas o padrão). Você pode definir prioridades para que, se você tiver dois itens ativos, ambos forneçamProjectile
o código, saiba qual deles usar. Ou você pode fornecer uma interface do usuário para permitir que o usuário selecione qual tipo de projétil usar, ou simplesmente exigir que o jogador desequipar itens de alta prioridade que ele não deseja, ou usar o item mais recente etc. Você também pode permitir um sistema de incompatibilidades , por exemplo, para que dois itens que ambos modificamProjectile
não possam ser equipados simultaneamente.Em geral, quando possível, prefira qualquer tipo de abordagem orientada a dados (ou declarativa ) do que as abordagens procedurais (o grande se-else mexe) quando se trata de objetos e coisas do seu jogo. A lógica genérica de nível superior configurável por dados simples é muito preferível a listas codificadas de regras de casos especiais.
fonte
Se você estiver usando uma linguagem OOP, isso parece um bom lugar para empregar o Padrão Decorador . Quando você quiser modificar como um ataque ocorre, decore-o com o aumento apropriado.
Exemplo bruto de c ++:
Esse método seria melhor se você tiver um número muito grande de ataques e precisar que todos eles se comportem da mesma maneira. Se você quiser alterar substancialmente a maneira como o ataque acontece com o modificador (por exemplo, nova animação com modificador), esse método não é para você.
fonte
Attack
método do objeto que agrega. ATripleAttack
turma não deve saber sobre aTearAttack
turma. Se isso fosse verdade, levaria a tantas dores de cabeça quanto oelse-if
bloqueio. Isso significa que qualquer animação de lágrima precisa residir dentro doTearAttackBehaviour
objeto. Este objeto não (e não deveria) saber que foi decorado por umTripleAttack
objeto. O resultado é que as três animações de lágrima procedem independentemente, porque são independentes.Como fã de Binding of Isaac, eu também me perguntei como fazer algo assim. O sistema no jogo é robusto o suficiente, onde comportamentos emergentes surgem da combinação de efeitos (o que me vem à mente é obter espelho, dobrador de colher e alguns reforçadores de alcance resultam em uma parede de lágrimas em volta de Isaac, estilo Magneto ) O grande número deles tornaria um bloco "se" impraticável.
Minha conclusão é que Isaac e suas lágrimas são duas entidades no centro de uma enorme estrutura componente-entidade . As entidades têm algumas estatísticas básicas (velocidade de movimento, vida, alcance, dano, sprite, etc.) e cada componente traria um modificador de estatística e um verbo.
No código, Isaac e suas lágrimas teriam uma lista que conteria coisas de uma interface. Isaac teria uma lista de coisas que assinam a interface do IsaacMutator, e suas lágrimas rasgam oMutator. IsaacMutator teria funções para modificar a saúde, velocidade, alcance, aparência e algum verbo especial do Isaacs. O TearMutator seria semelhante. Uma vez por loop de jogo, Isaac passaria por todos os IsaacMutators que ele possui, e todas as lágrimas vivas também. Para seguir seu exemplo em inglês, seria como:
e assim por diante. Como os tipos são aditivos, você pode empilhar, adicionar e remover o conteúdo do seu coração.
fonte
Eu acho que seu caminho funciona melhor. Cada um desses tipos de itens fornece uma condição; se usados juntos, produzem uma condição diferente, você precisaria efetivamente das três condições possíveis definidas.
Você também pode criar um novo tipo de definição quando os dois itens estiverem presentes, mas isso realmente aumenta a convolução:
fonte