Esta é uma pergunta de "acompanhamento" da minha anterior, sobre detecção e resolução de colisões, que você pode encontrar aqui .
Se você não quiser ler a pergunta anterior, aqui está uma breve descrição de como meu mecanismo de física funciona:
Toda entidade física é armazenada em uma classe chamada SSSPBody.
Apenas AABBs são suportados.
Todo SSSPBody é armazenado em uma classe chamada SSSPWorld, que atualiza todos os corpos e lida com a gravidade.
A cada quadro, o SSSPWorld atualiza todos os corpos.
Todo corpo atualizado procura corpos próximos em um hash espacial, verifica se eles precisam detectar colisões com eles. Se sim, eles invocam um evento de "colisão" e verificam se precisam resolver colisões com eles. Se sim, eles calculam o vetor de penetração e a sobreposição direcional e alteram sua posição para resolver a penetração.
Quando um corpo colide com outro, transfere sua velocidade para o outro simplesmente ajustando a velocidade do corpo à sua.
Um corpo com velocidade é definido como 0 quando não mudou de posição a partir do último quadro. Se também colidir com um corpo em movimento (como um elevador ou uma plataforma em movimento), calcula a diferença de movimento do elevador para ver se o corpo não se moveu da sua última posição.
Além disso, um corpo chama um evento "esmagado" quando todos os seus cantos da AABB se sobrepõem a algo em um quadro.
Este é o código fonte COMPLETO do meu jogo. Está dividido em três projetos. SFMLStart é uma biblioteca simples que trata de entrada, desenho e atualização de entidades. SFMLStartPhysics é o mais importante, onde estão as classes SSSPBody e SSSPWorld. PlatformerPhysicsTest é o projeto do jogo, contendo toda a lógica do jogo.
E este é o método "update" na classe SSSPBody, comentado e simplificado. Você pode dar uma olhada apenas nisso se não estiver com vontade de olhar para todo o projeto SFMLStartSimplePhysics. (E mesmo que você faça isso, você ainda deve dar uma olhada nisso, pois é comentado.)
O gif mostra dois problemas.
- Se os corpos são colocados em uma ordem diferente, resultados diferentes acontecem. As caixas à esquerda são idênticas às caixas à direita, colocadas apenas na ordem inversa (no editor).
- Ambas as caixas devem ser movidas para o topo da tela. Na situação à esquerda, nenhuma caixa é lançada. À direita, apenas um deles é. Ambas as situações não são intencionais.
Primeiro problema: ordem da atualização
Isso é bastante simples de entender. Na situação à esquerda, a caixa superior é atualizada antes da outra. Mesmo que a caixa no fundo "transfira" a velocidade para a outra, ela precisa esperar o próximo quadro para se mover. Como não se moveu, a velocidade da caixa inferior é definida como 0.
Eu não tenho nenhuma idéia de como consertar isso. Prefiro que a solução não dependa da "classificação" da lista de atualizações, porque sinto que estou fazendo algo errado em todo o design do mecanismo de física.
Como os principais mecanismos de física (Box2D, Bullet, Chipmunk) lidam com a ordem de atualização?
Segundo problema: apenas uma caixa é lançada em direção ao teto
Ainda não entendo por que isso acontece. O que a entidade "mola" faz é definir a velocidade do corpo para -4000 e reposicioná-la sobre a própria mola. Mesmo se eu desativar o código de reposicionamento, o problema ainda ocorre.
Minha idéia é que, quando a caixa inferior colide com a caixa superior, sua velocidade é definida como 0. Não sei por que isso acontece.
Apesar da chance de parecer alguém que desiste do primeiro problema, publiquei todo o código-fonte do projeto acima. Não tenho nada para provar isso, mas acredite, tentei consertar isso, mas não consegui encontrar uma solução e não tenho nenhuma experiência anterior com física e colisões. Estou tentando resolver esses dois problemas há mais de uma semana e agora estou desesperada.
Acho que não consigo encontrar uma solução sozinho sem retirar muitos recursos do jogo (transferência de velocidade e molas, por exemplo).
Muito obrigado pelo tempo gasto lendo esta pergunta e ainda mais se você tentar encontrar uma solução ou sugestão.
Respostas:
Na verdade, problemas de ordem de atualização são bastante comuns para os motores de física de impulso normais, você não pode simplesmente atrasar a aplicação da força como sugere Vigil, acabaria quebrando a preservação de energia quando um objeto colidir simultaneamente com outros 2. Geralmente eles conseguem fazer algo que parece bastante real, mesmo que uma ordem diferente de atualização tivesse feito um resultado significativamente diferente.
De qualquer forma, para o seu propósito, existem soluços suficientes em um sistema de impulso que eu sugiro que você construa um modelo de mola em massa.
A idéia básica é que, em vez de tentar resolver uma colisão em uma etapa, você aplique uma força aos objetos que colidem, essa força deve ser equivalente à quantidade de sobreposição entre os objetos; isso é comparável ao modo como os objetos reais durante uma colisão transformam seus objetos. movendo a energia para a deformação e, em seguida, de volta ao movimento, a grande vantagem desse sistema é que ele permite que a força viaje através de um objeto sem que esse objeto precise saltar para frente e para trás, e isso pode ser feito de maneira completamente independente da atualização.
Para que os objetos parem, em vez de se movimentarem indefinidamente, você precisará aplicar alguma forma de amortecimento. Você pode afetar bastante o estilo e a sensação do seu jogo, dependendo de como fazê-lo, mas uma abordagem muito básica seria: aplicar uma força a dois objetos tocantes equivalentes ao seu movimento interno; você pode optar por aplicá-la apenas quando eles estiverem se movendo um para o outro, ou também quando eles se afastarem um do outro; este último pode ser usado para impedir completamente que objetos retornem. quando atingem o chão, mas também os tornam um pouco pegajosos.
Você também pode fazer um efeito de atrito travando um objeto na direção perpendicular de uma colisão, a quantidade de frenagem deve ser equivalente à quantidade de sobreposição.
Você pode contornar o conceito de massa com bastante facilidade, fazendo com que todos os objetos tenham a mesma massa, e objetos imóveis funcionarão como ter massa infinita se você simplesmente deixar de acelerá-los.
Algum pseudo-código, apenas no caso de o acima não ser suficientemente claro:
O objetivo das propriedades addXvelocity e addYvelocity é que elas sejam adicionadas à velocidade do objeto depois que todo o tratamento de colisão for concluído.
Editar:
você pode fazer coisas na seguinte ordem, onde cada marcador deve ser executado em todos os elementos antes que o próximo seja executado:
Além disso, percebo que o seguinte pode não estar completamente claro no meu post inicial, sob a influência de objetos de gravidade se sobreporem quando descansarem um sobre o outro, o que sugere que a caixa de colisão deve ser um pouco maior que a representação gráfica para evitar sobreposição. visualmente. Esse problema será menor se a física for executada em uma taxa de atualização mais alta. Eu sugiro que você tente rodar a 120Hz para um compromisso razoável entre o tempo da CPU e a precisão da física.
Edit2:
Fluxo do motor de física muito básico:
acceleration = [Complicated formulas]
velocity += acceleration
position += velocity
fonte
Bem, obviamente você não é alguém que desiste facilmente, você é um verdadeiro homem de ferro, eu teria jogado minhas mãos no ar muito antes, já que este projeto tem uma forte semelhança com uma floresta de algas :)
Primeiro de tudo, posições e velocidades são definidas em todo o lugar, do ponto de vista do subsistema de física é uma receita para um desastre. Além disso, ao alterar itens integrais por vários subsistemas, crie métodos particulares como "ChangeVelocityByPhysicsEngine", "ChangeVelocityBySpring", "LimitVelocity", "TransferVelocity" ou algo parecido. Ele adicionará a capacidade de verificar as alterações feitas por uma parte específica da lógica e fornecerá um significado adicional a essas alterações de velocidade. Dessa forma, a depuração seria mais fácil.
Primeiro problema.
Sobre a própria pergunta. Agora você está apenas aplicando correções de posição e velocidade "à medida que avançam" em ordem de aparência e lógica do jogo. Isso não funcionará para interações complexas sem codificar cuidadosamente a física de cada coisa complexa. Um mecanismo de física separado não é necessário então.
Para fazer interações complexas sem hacks, é necessário adicionar uma etapa adicional entre a detecção de colisões com base nas posições que foram alteradas pelas velocidades iniciais e as mudanças finais de posições com base na "pós-velocidade". Eu imagino que seria assim:
Coisas adicionais podem surgir, como lidar com empurrões, recusa em empilhar quando o FPS é pequeno ou outras coisas assim, esteja preparado :)
Segundo problema
A velocidade vertical de ambas as caixas de "peso morto" nunca muda de zero. Estranhamente, no loop de atualização do PhysSpring você atribui velocidade, mas no loop de atualização do PhysCrate já é zero. É possível encontrar uma linha onde a velocidade dá errado, mas parei de depurar aqui, pois é a situação "Reap What You Sew". É hora de parar de codificar e começar a repensar tudo quando a depuração se tornar difícil. Mas se chegar a um ponto em que é impossível até mesmo para o autor do código entender o que está acontecendo no código, sua base de códigos já estará morta sem que você perceba :)
Terceiro problema
Eu acho que algo está errado quando você precisa recriar uma parte do Farseer para fazer um simples jogo de plataforma baseado em blocos. Pessoalmente, eu pensaria no seu mecanismo atual como uma tremenda experiência e depois o abandonaria completamente para uma física mais simples e direta baseada em blocos. Ao fazer isso, seria sensato pegar coisas como Debug.Assert e talvez até, oh, o horror, testes de unidade, pois seria possível capturar coisas inesperadas mais cedo.
fonte
Seu problema é que essas são suposições fundamentalmente erradas sobre o movimento; portanto, o que você está recebendo não se parece com o movimento que você conhece.
Quando um corpo colide com outro, o momento é conservado. Pensar nisso como "A acerta B" versus "B acerta A" é aplicar um verbo transitivo a uma situação intransitiva. A e B colidem; o momento resultante deve ser igual ao momento inicial. Ou seja, se A e B são massa igual, agora eles estão viajando com a média de suas velocidades originais.
Provavelmente, você também precisará de alguma correção de colisão e um solucionador iterativo, ou terá problemas de estabilidade. Você provavelmente deve ler algumas das apresentações do GDC de Erin Catto.
fonte
Eu acho que você fez um esforço realmente nobre, mas parece que há problemas fundamentais na forma como o código está estruturado. Como outros sugeriram, pode ajudar a separar as operações em partes discretas, por exemplo:
Ao separar as fases, todos os objetos são atualizados progressivamente em sincronia e você não terá as dependências de pedidos com as quais está lutando atualmente. O código também geralmente se torna mais simples e mais fácil de alterar. Cada uma dessas fases é bastante genérica, e geralmente é possível substituir algoritmos melhores depois que você tiver um sistema em funcionamento.
Dito isto, cada uma dessas partes é uma ciência em si mesma e pode ocupar muito tempo tentando encontrar a solução ideal. Pode ser melhor começar com alguns dos algoritmos mais usados:
Um bom (e óbvio) lugar para começar é com as leis do movimento de Newton .
fonte