Em qualquer colisão, há dois GameObjects envolvidos, certo? O que eu quero saber é: Como decido qual objeto deve conter o meu OnCollision*
?
Como exemplo, vamos supor que eu tenho um objeto Player e um objeto Spike. Meu primeiro pensamento é colocar um script no player que contenha algum código como este:
OnCollisionEnter(Collision coll) {
if (coll.gameObject.compareTag("Spike")) {
Destroy(gameObject);
}
}
Obviamente, a mesma funcionalidade exata pode ser alcançada com um script no objeto Spike que contém código como este:
OnCollisionEnter(Collision coll) {
if (coll.gameObject.compareTag("Player")) {
Destroy(coll.gameObject);
}
}
Embora ambos sejam válidos, fazia mais sentido para mim ter o script no Player porque, nesse caso, quando a colisão acontece, uma ação está sendo executada no Player .
No entanto, o que me faz duvidar disso é que, no futuro, você poderá adicionar mais objetos que matarão o Player em colisão, como Inimigo, Lava, Laser Beam, etc. Esses objetos provavelmente terão tags diferentes. Então o script no Player se tornaria:
OnCollisionEnter(Collision coll) {
GameObject other = coll.gameObject;
if (other.compareTag("Spike") || other.compareTag("Lava") || other.compareTag("Enemy")) {
Destroy(gameObject);
}
}
Enquanto que, no caso em que o script estava no Spike, tudo o que você precisava fazer era adicionar esse mesmo script a todos os outros objetos que podem matar o Player e nomear o script como algo semelhante KillPlayerOnContact
.
Além disso, se você tiver uma colisão entre o jogador e um inimigo, provavelmente desejará executar uma ação em ambos . Então, nesse caso, qual objeto deve lidar com a colisão? Ou devem lidar com a colisão e executar ações diferentes?
Eu nunca construí um jogo de tamanho razoável antes e estou me perguntando se o código pode se tornar confuso e difícil de manter à medida que cresce, se você entender esse tipo de coisa errada no começo. Ou talvez todas as formas sejam válidas e isso realmente não importa?
Qualquer ideia é bem apreciada! Obrigado pelo seu tempo :)
fonte
Tag.SPIKE
?Respostas:
Pessoalmente, prefiro arquitetar sistemas de modo que nenhum objeto envolvido em uma colisão lide com isso, e, em vez disso, algum proxy de manipulação de colisão consegue lidar com ele com um retorno de chamada
HandleCollisionBetween(GameObject A, GameObject B)
. Dessa forma, não preciso pensar em qual lógica deveria estar onde.Se isso não é uma opção, como quando você não está no controle da arquitetura de resposta a colisões, as coisas ficam um pouco confusas. Geralmente a resposta é "depende".
Como os dois objetos receberão retornos de colisão, eu colocaria o código em ambos, mas apenas o código relevante para o papel desse objeto no mundo do jogo , enquanto também tentava manter a extensibilidade o máximo possível. Isso significa que, se alguma lógica faz sentido em ambos os objetos, prefira colocá-la no objeto que provavelmente levará a menos alterações de código a longo prazo à medida que novas funcionalidades forem adicionadas.
Dito de outra forma, entre dois tipos de objetos que podem colidir, um desses tipos de objetos geralmente tem o mesmo efeito em mais tipos de objetos que o outro. Colocar código para esse efeito no tipo que afeta mais outros tipos significa menos manutenção a longo prazo.
Por exemplo, um objeto de jogador e um objeto de marcador. Indiscutivelmente, "sofrer dano em colisão com balas" faz sentido lógico como parte do jogador. "Aplicar dano à coisa que ela atinge" faz sentido lógico como parte da bala. No entanto, é provável que uma bala cause danos a mais tipos diferentes de objetos do que um jogador (na maioria dos jogos). Um jogador não sofrerá dano de blocos de terreno ou NPCs, mas uma bala ainda poderá causar dano a eles. Então, eu prefiro colocar o tratamento de colisão para causar danos à bala, nesse caso.
fonte
TL; DR O melhor local para colocar o detector de colisão é o local em que você considera que é o elemento ativo na colisão, em vez do elemento passivo na colisão, porque talvez você precise de um comportamento adicional para ser executado nessa lógica ativa (e, seguindo boas diretrizes de POO, como o SOLID, é de responsabilidade do elemento ativo ).
Detalhado :
Vamos ver ...
Minha abordagem quando tenho a mesma dúvida, independentemente do mecanismo do jogo , é determinar qual comportamento está ativo e qual é passivo em relação à ação que quero desencadear na coleta.
Observe: eu uso comportamentos diferentes como abstrações das diferentes partes de nossa lógica para definir o dano e, como outro exemplo, o inventário. Ambos são comportamentos separados que eu adicionaria mais tarde a um Player, e não sobrecarregar meu Player personalizado com responsabilidades separadas que seriam mais difíceis de manter. Usando anotações como RequireComponent, eu garantiria que o comportamento do Player tivesse, também, os comportamentos que estou definindo agora.
Esta é apenas a minha opinião: evite executar a lógica dependendo da tag, mas use comportamentos e valores diferentes, como enumerações, para escolher .
Imagine este comportamento:
Comportamento para especificar um objeto como sendo uma pilha no chão. Jogos de RPG usam isso para itens no chão que podem ser apanhados.
Comportamento para especificar um objeto capaz de produzir danos. Pode ser lava, ácido, qualquer substância tóxica ou um projétil voador.
Nesta medida, considere
DamageType
eInventoryObject
como enumerações.Esses comportamentos não são ativos , da maneira que, estando no chão ou voando ao redor, eles não agem sozinhos. Eles não fazem nada. Por exemplo, se você tem uma bola de fogo voando em um espaço vazio, ela não está pronta para causar danos (talvez outros comportamentos devam ser considerados ativos em uma bola de fogo, como a bola de fogo consumindo seu poder enquanto viaja e diminuindo seu poder de dano no processo, mas até quando um
FuelingOut
comportamento em potencial pode ser desenvolvido, ainda é outro comportamento, e não o mesmo comportamento do DamageProducer), e se você vê um objeto no chão, ele não está verificando todos os usuários e pensando que eles podem me pegar?Precisamos de comportamentos ativos para isso. As contrapartes seriam:
Um comportamento para sofrer danos (exigiria um comportamento para lidar com a vida e a morte, já que diminuir a vida e receber danos geralmente são comportamentos separados, e porque eu quero manter esses exemplos o mais simples possível) e talvez resistir a danos.
Este código não foi testado e deve funcionar como um conceito . Qual é o propósito? Dano é algo que alguém sente e é relativo ao objeto (ou forma viva) sendo danificado, como você considera. Este é um exemplo: em jogos regulares de RPG, uma espada danifica você , enquanto em outros RPGs que a implementam (por exemplo, Evocar Night Swordcraft Story I e II ), uma espada também pode receber dano ao acertar uma parte difícil ou bloquear uma armadura e pode precisar reparos . Portanto, como a lógica de dano é relativa ao receptor e não ao remetente, apenas colocamos dados relevantes no remetente e a lógica no receptor.
O mesmo se aplica a um detentor de inventário (outro comportamento necessário seria o relacionado ao objeto estar no chão e a capacidade de removê-lo de lá). Talvez este seja o comportamento apropriado:
fonte
Como você pode ver pela variedade de respostas aqui, há um bom grau de estilo pessoal envolvido nessas decisões e julgamento baseado nas necessidades do seu jogo em particular - nenhuma abordagem absolutamente melhor.
Minha recomendação é pensar nisso em termos de como sua abordagem será escalada à medida que você progride , adicionando e iterando recursos. A colocação da lógica de manipulação de colisões em um só lugar levará a códigos mais complexos posteriormente? Ou ele suporta um comportamento mais modular?
Então, você tem picos que danificam o jogador. Pergunte a si mesmo:
Obviamente, não queremos nos deixar levar por isso e criar um sistema com excesso de engenharia que possa lidar com um milhão de casos, em vez de apenas o que precisamos. Mas se o trabalho é igual de qualquer maneira (por exemplo, coloque essas quatro linhas na classe A versus classe B), mas se a escala for melhor, é uma boa regra geral para a tomada de decisão.
Neste exemplo, especificamente no Unity, eu me inclinaria a colocar a lógica de causar danos nos picos , na forma de algo como um
CollisionHazard
componente, que após a colisão chama um método de causar danos em umDamageable
componente. Aqui está o porquê:Damageable
componente neles (se você precisar que alguns sejam imunes apenas a espinhos, você pode adicionar tipos de danos e deixe oDamageable
componente lidar com imunidades)fonte
Realmente não importa quem lida com a colisão, mas seja consistente com o trabalho de quem é.
Nos meus jogos, tento escolher o
GameObject
que está "fazendo" algo para o outro objeto como aquele que controla a colisão. O IE Spike danifica o jogador, então ele lida com a colisão. Quando os dois objetos "fazem" algo um com o outro, peça que cada um lide com sua ação no outro objeto.Além disso, eu sugeriria tentar projetar suas colisões para que elas sejam tratadas pelo objeto que reage às colisões com menos coisas. Um pico apenas precisará saber sobre colisões com o jogador, tornando o código de colisão no script Spike agradável e compacto. O objeto do jogador pode colidir com muito mais coisas que criarão um script de colisão inchado.
Por fim, tente tornar seus manipuladores de colisão mais abstratos. Um Spike não precisa saber que colidiu com um jogador, ele apenas precisa saber que colidiu com algo que precisa causar dano. Digamos que você tenha um script:
Você pode subclassificar
DamageController
no GameObject do seu jogador para lidar com o dano e, em seguida, noSpike
código de colisãofonte
Eu tive o mesmo problema quando comecei, então aqui vai: Neste caso específico, use o Inimigo. Você geralmente quer que a Colisão seja manipulada pelo Objeto que executa a ação (neste caso - o inimigo, mas se o seu jogador tiver uma arma, a arma deve lidar com a colisão), porque se você quiser ajustar os atributos (por exemplo, o dano do inimigo), você definitivamente quer mudá-lo no manuscrito e não no manuscrito (especialmente se você tiver vários jogadores). Da mesma forma, se você quiser alterar o dano de qualquer arma que o Jogador use, você definitivamente não deseja alterá-lo em todos os inimigos do seu jogo.
fonte
Ambos os objetos lidariam com a colisão.
Alguns objetos seriam imediatamente deformados, quebrados ou destruídos pela colisão. (balas, flechas, balões de água)
Outros simplesmente pulavam e rolavam para o lado. (pedras) Eles ainda poderiam sofrer danos se a coisa que atingissem fosse forte o suficiente. Rochas não sofrem dano de um ser humano atingindo-a, mas podem ser causadas por uma marreta.
Alguns objetos macios como humanos sofreram danos.
Outros estariam ligados um ao outro. (ímãs)
Ambas as colisões seriam colocadas em uma fila de manipulação de eventos para um ator atuando em um objeto em um determinado momento e local (e velocidade). Lembre-se de que o manipulador deve lidar apenas com o que acontece com o objeto. É até possível para um manipulador colocar outro manipulador na fila para lidar com efeitos adicionais da colisão com base nas propriedades do ator ou do objeto em que está sendo atuado. (A bomba explodiu e a explosão resultante agora precisa testar colisões com outros objetos.)
Ao criar scripts, use tags com propriedades que seriam traduzidas em implementações de interface pelo seu código. Em seguida, faça referência às interfaces no seu código de manipulação.
Por exemplo, uma espada pode ter um tag para Melee, que se traduz em uma interface chamada Melee, que pode estender uma interface DamageGiving que possui propriedades para o tipo de dano e uma quantidade de dano definida usando um valor fixo ou intervalos, etc. E uma bomba pode ter uma etiqueta explosiva cuja interface explosiva também estende a interface DamageGiving, que possui uma propriedade adicional de raio e possivelmente a rapidez com que o dano se difunde.
Seu mecanismo de jogo codificaria contra as interfaces, não tipos de objetos específicos.
fonte