Procurando algumas dicas sobre o design de programação para tipos de ataque e ataque em um jogo

14

Então, eu estou começando a introduzir ataques ao nosso RTS espacial 2D (isso é no Unity, então é orientado por componentes). Inicialmente era tão simples quanto "inimigo à distância, dano aplicado". No entanto, haverá vários "tipos" de armas / ataques associados a sua nave ou estrutura específica. Assim como outros fatores envolvidos, apenas danos brutos, como o tipo de dano e, possivelmente, inércia no futuro.

Vocês teriam cada tipo de unidade e estrutura com seu próprio tipo de ataque. Ou seja, você cria um script para cada unidade / estrutura que define seu tipo de ataque, dano, efeitos, alcance, partículas, sprites ... etc. E anexa isso como um componente?

Ou faça um script que defina um tipo de ataque, um script para o tipo de projétil associado a esse ... etc E depois estenda-os e modifique-os para cada unidade, anexando cada script à unidade / estrutura.


Espero estar fazendo algum sentido, estou pensando nisso há tanto tempo que não tenho certeza se estou resolvendo um problema ou apenas inventando meus próprios problemas e me cavando em um buraco.

Quando você tem um jogo que pode ter vários tipos de ataque que podem ou não estar limitados a uma unidade / estrutura específica, como você projeta a estrutura que vincula isso às unidades / estruturas específicas em um ambiente de design orientado a componentes ?

Se isso não estiver claro o suficiente, avise-me.

Edit: Ótimas respostas, obrigado.

Pergunta expandida:

As respostas parecem variar de "cada objeto pode ter seu próprio script de ataque" a "Ter os tipos de ataque como seus próprios scripts e atribuí-los a cada objeto para uma solução mais reutilizável". Digamos que eu tenha um ataque "blaster", ele atira um projétil vermelho a uma certa velocidade. O dano, a taxa de tiro e o tamanho do projétil dependem da unidade que o disparou. É melhor criar um script de ataque para essa unidade ou tentar modificar um "ataque blaster" para se adequar ao objetivo de cada unidade que deseja usá-lo?

Douglas Gaskell
fonte
1
Para programação ideias gerais do jogo, eu gosto de se referir à especificação completa do RPG FFV - gamefaqs.com/snes/588331-final-fantasy-v/faqs/30040
Código Whisperer

Respostas:

12

Bem, honestamente não sou especialista nisso, mas ... acho que depende de quão complexo e variado você acha que os ataques se tornarão. Já que é um RTS, acho que você terá talvez de 10 a 50 unidades ou estruturas diferentes com seus próprios tipos de ataque.

Opção 1: Se houver um número relativamente baixo de unidades que terão ataques semelhantes, eu simplesmente colocaria tudo em um grande script e definiria os parâmetros usados ​​no inspetor.

Opção 2: Se, por outro lado, você visualizar um grande número de tipos de ataque com comportamento diferente, poderá dividir tudo para que cada unidade e edifício obtenha seu próprio script de ataque exclusivo. Estou pensando que, se você fizer isso, convém criar um script "auxiliar" que defina blocos de código comumente usados, dos quais muitos scripts individuais podem obter. Dessa forma, você não precisará reescrever tudo e saberá onde tudo está.

Opção 3: O que você provavelmente não deve fazer é ter certos grupos de unidades que compartilham scripts, isso provavelmente o confundirá e se tornará uma bagunça se o código necessário para um ataque estiver em 10 scripts diferentes.

Aqui, tirei uma foto para você.

insira a descrição da imagem aqui

Mir
fonte
2
Muito obrigado pela resposta. Por algum motivo, comecei a me inclinar para a opção 3 e estava com dificuldade para encontrar uma maneira de justificá-la. Provavelmente irei a segunda rota, cada unidade recebe seu próprio script de ataque personalizado com o código comum sendo compartilhado, tendo o código comum atacado como um componente de cada unidade / edifício. Não sei para onde estava indo com a linha de pensamento que me levou à opção 3, obrigado. Vou deixar isso em aberto até que eu acorde de manhã, caso haja outros pôsteres que desejem entrar.
Douglas Gaskell
Não tem problema, não é uma resposta definitiva, mas espero que ajude.
Mir
1
Você pode hibridar 1 e 2 colocando ataques semelhantes em um grande script e separando ataques diferentes
ratchet freak
4
Estou surpreso # 3 é recomendado contra? Não é o objetivo das classes modulares / genéricas para que cada unidade não precise definir seu próprio tipo? Se um jogo é um RTS e o dano de cerco (geralmente "de longo alcance") é um tipo de dano, você deve defini-lo uma vez e fazer com que várias unidades no estilo de artilharia se refiram a ele ao realizar seus cálculos de dano, de modo que se o dano de cerco já precisou ser nerfado (reequilibrado), você só precisaria atualizar uma classe?
HC_ 26/03/2015
1
"Here, I drew you a picture."me lembrou disso
FreeAsInBeer 26/03
4

Não sei muito sobre o Unity e não desenvolvo jogos há algum tempo, então deixe-me dar uma resposta geral de programação a esta pergunta. Baseei minha resposta no conhecimento que tenho sobre sistemas de componentes de entidade em geral, onde uma entidade é um número associado a N muitos componentes, um componente contém apenas dados e um sistema opera em conjuntos de componentes associados a a mesma entidade.

Seu espaço problemático é este:

  • Existem várias maneiras de atacar um inimigo no jogo como um todo.
  • Cada navio, estrutura etc. pode ter várias maneiras de atacar (cada uma determinada de alguma maneira)
  • Cada ataque pode ter seus próprios efeitos de partículas.
  • O ataque deve levar em consideração alguns fatores (como inércia ou armadura, por exemplo), que estão presentes no alvo e no usuário.

Eu estruturaria a solução da seguinte maneira:

  • Um ataque tem um identificador - isso pode ser uma string.
  • Uma entidade 'sabe' que pode usar um ataque (com base no identificador do ataque).
  • Quando o ataque é usado pela entidade, o componente de exibição correspondente é adicionado à cena.
  • Você tem alguma lógica que sabe sobre o alvo do ataque, o atacante e o ataque que está sendo usado - essa lógica deve decidir quanto dano você causa (e ter acesso à inércia ou a qualquer uma das duas entidades).

É importante que o ponto de contato entre os ataques e as entidades seja o menor possível - isso manterá seu código reutilizável e impedirá que você precise criar um código duplicado para cada tipo diferente de entidade que usa o mesmo tipo de ataque . Em outras palavras, aqui estão alguns pseudo-códigos JavaScript para lhe dar uma idéia.

// components
var bulletStrength = { strength: 50 };
var inertia = { inertia: 100 };
var target = { entityId: 0 };
var bullets = {};
var entity = entityManager.create([bulletStrength, inertia, target, bullets]);

var bulletSystem = function() {
  this.update = function(deltaTime, entityId) {
    var bulletStrength = this.getComponentForEntity('bulletStrength', entityId);
    var targetComponent = this.getComponentForEntity('target', entityId);
    // you may instead elect to have the target object contain properties for the target, rather than expose the entity id
    var target = this.getComponentForEntity('inertia', targetComponent.entityId);

    // do some calculations based on the target and the bullet strength to determine what damage to deal
    target.health -= ....;
  }
};

register(bulletSystem).for(entities.with(['bullets']));

Desculpe, esta resposta é um pouco "aguada". Eu só tenho uma hora de almoço e é difícil inventar algo sem conhecer completamente o Unity :(

Dan Pantry
fonte
3

Quando uma unidade / estrutura / arma ataca, eu provavelmente criaria um ataque (subclassificado com todos os seus detalhes divertidos) que leva o atacante e o defensor (ou defensores). O ataque pode então interagir com o alvo / defensor (lento, veneno, dano, mudar de estado), desenhar-se (raio, raio, bala) e se desfazer quando terminar. Posso prever alguns problemas, como vários ataques com veneno, então talvez seus alvos implementem uma interface Danificável com a qual o Attack interage, mas acho que é uma abordagem viável, modular e flexível para mudar.

Resposta expandida
É assim que eu abordaria o ataque do blaster com essa abordagem . Vou deixar os outros responderem por si mesmos.

Minhas unidades implementariam uma interface ou classe do IAttacker com estatísticas / métodos básicos de ataque. Quando um IAttacker ataca um IDamageable, ele cria seu ataque específico passando por si e por seu alvo (o IAttacker e o IDamageable, ou talvez uma coleção de IDamageables). O Attack pega as estatísticas necessárias no IAttacker (para evitar alterações durante as atualizações ou algo assim - não queremos que o Attack altere suas estatísticas após o lançamento) e, se precisar de estatísticas especializadas, lança o IAttacker para o tipo necessário (por exemplo, IBlasterAttacker) e obtém as estatísticas especializadas dessa maneira.

Seguindo essa abordagem, um BlasterAttacker só precisa criar um BlasterAttack, e o BlasterAttack cuida do resto. Você pode subclassificar BlasterAttack ou criar FastBlasterAttacker, MegaBlasterAttacker, SniperBlasterAttacker, etc, e o código de ataque para cada um é o mesmo (e possivelmente herdado do BlasterAttack): Crie o BlasterAttack e passe a mim e aos meus alvos. O BlasterAttack lida com os detalhes .

ricksmt
fonte
Essencialmente, a unidade herda de uma interface IAttacker (eu já tenho isso), e existe uma interface IDamageable para o "inimigo" (também tenho isso). Quando o atacante ataca, um BlasterAttack (ou classe derivada) é chamado. Esse "ataque" recuperará os dados necessários do IAttacker e os aplicará no IDamageable quando o projétil atingir? O projétil em si contém a classe BlasterAttack, para que, uma vez disparado, não seja mais afetado por alterações no IAttacker e possa aplicar seus danos / efeitos no IDamageable apenas se o projétil realmente atingir.
Douglas Gaskell 27/03
Quando você diz "um BlasterAttack (ou classe derivada) é chamado", eu diria que um BlasterAttack é criado. Esta instância recém-criada do BlasterAttack representa o feixe (ou bala ou raio ou o que for), portanto é o projétil. O BlasterAttack copia as estatísticas de que precisa dos objetos IAttacker e IDamageable: as posições, as estatísticas do atacante etc. O BlasterAttack rastreia sua própria posição e, se aplicável, causa dano ao "contato". Você precisará descobrir o que fazer se ele errar ou atingir seu destino (a antiga posição do alvo). Queimar o chão? Desaparecer? Sua chamada.
Rickmt
Para um ataque de área de efeito, você pode querer acessar uma coleção global de unidades (inimigas), pois quem está dentro do alcance e quem está fora do alcance pode mudar entre fogo e impacto. Obviamente, um argumento semelhante poderia ser feito para o BlasterAttack: você errou seu alvo inicial, mas acertou o cara atrás dele. Minha única preocupação é que você possa ter muitos ataques repetindo vários inimigos tentando descobrir se e o que eles acertaram. Essa é uma preocupação de desempenho.
Rickmt
Ah, isso faz sentido. Para um ataque perdido, o projétil terá seu próprio intervalo / vida útil predefinida. Se atingir outra coisa antes do final dessa vida útil, receberá uma referência a qualquer objeto que possua o corpo rígido com o qual colide e o dano será aplicado dessa maneira. Na verdade, é assim que todos os projéteis funcionarão, eles não sabem "para que" estão viajando, apenas que estão viajando (excluindo projéteis semelhantes a mísseis, como mísseis). Os efeitos AEO podem apenas ativar um colisor de esferas no destino e obter todos os objetos que estão dentro dele. Obrigado pela ajuda.
Douglas Gaskell 27/03
Sem problemas. Ainda bem que pude. Esqueceu que o Unity facilita todo esse material de colisão.
Rickmt