Como posso implementar a gravidade?

Respostas:

52

Como outros observaram nos comentários, o método básico de integração de Euler descrito na resposta da tenpn sofre de alguns problemas:

  • Mesmo para movimentos simples, como o salto balístico sob gravidade constante, ele introduz um erro sistemático.

  • O erro depende do timestep, o que significa que alterar o timestep altera as trajetórias dos objetos de uma maneira sistemática que pode ser notada pelos jogadores se o jogo usar um timestep variável. Mesmo para jogos com timestamp de física fixo, alterar o timestap durante o desenvolvimento pode afetar visivelmente a física do jogo, como a distância que um objeto lançado com uma determinada força voará, potencialmente quebrando níveis previamente projetados.

  • Não economiza energia, mesmo que a física subjacente deva. Em particular, objetos que devem oscilar constantemente (por exemplo, pêndulos, molas, planetas em órbita, etc.) podem acumular energia continuamente até que todo o sistema se desfaça.

Felizmente, não é difícil substituir a integração do Euler por algo que é quase tão simples, mas sem nenhum desses problemas - especificamente, um integrador simplético de segunda ordem, como a integração com salto ou o método Verlet de velocidade intimamente relacionado . Em particular, onde a integração básica do Euler atualiza a velocidade e a posição como:

aceleração = força (tempo, posição) / massa;
time + = timestep;
posição + = timestep * velocidade;
velocidade + = timestep * aceleração;

o método de velocidade Verlet faz assim:

aceleração = força (tempo, posição) / massa;
time + = timestep;
posição + = timestep * ( velocidade + timestep * aceleração / 2) ;
newAcceleration = força (tempo, posição) / massa; 
velocidade + = timestep * ( aceleração + nova aceleração ) / 2 ;

Se você tiver vários objetos em interação, atualize todas as suas posições antes de recalcular as forças e atualizar as velocidades. As novas acelerações podem ser salvas e usadas para atualizar as posições no próximo intervalo de tempo, reduzindo o número de chamadas force()para uma (por objeto) por intervalo de tempo, assim como no método Euler.

Além disso, se a aceleração é normalmente constante (como a gravidade durante o salto balístico), podemos simplificar o acima para apenas:

time + = timestep;
posição + = timestep * ( velocidade + timestep * aceleração / 2) ;
velocidade + = timestep * aceleração;

onde o termo extra em negrito é a única alteração em comparação à integração básica do Euler.

Comparado à integração do Euler, os métodos de velocidade Verlet e leapfrog têm várias propriedades interessantes:

  • Para aceleração constante, eles fornecem resultados exatos (até erros de arredondamento de ponto flutuante, de qualquer maneira), o que significa que as trajetórias de salto balístico permanecem as mesmas, mesmo que o timestep seja alterado.

  • Eles são integradores de segunda ordem, o que significa que, mesmo com acelerações variadas, o erro médio de integração é proporcional apenas ao quadrado do passo temporal. Isso pode permitir timestados maiores sem comprometer a precisão.

  • Eles são simpléticos , o que significa que economizam energia se a física subjacente o fizer (pelo menos enquanto o passo temporal for constante). Em particular, isso significa que você não conseguirá coisas como planetas que voam espontaneamente de suas órbitas, ou objetos presos uns aos outros com molas gradualmente oscilando cada vez mais até que tudo exploda.

No entanto, o método de velocidade Verlet / leapfrog é quase tão simples e rápido quanto a integração básica de Euler, e certamente muito mais simples do que alternativas como a integração Runge-Kutta de quarta ordem (que, embora geralmente seja um ótimo integrador, carece da propriedade simplética e requer quatro avaliações da force()função por etapa de tempo). Portanto, eu os recomendo fortemente para qualquer pessoa que escreva qualquer tipo de código de física de jogos, mesmo que seja tão simples quanto saltar de uma plataforma para outra.


Edit: Embora a derivação formal do método Verlet da velocidade seja válida apenas quando as forças são independentes da velocidade, na prática você pode usá-lo bem mesmo com forças dependentes da velocidade, como o arrasto do fluido . Para obter melhores resultados, você deve usar o valor de aceleração inicial para estimar a nova velocidade da segunda chamada para force(), assim:

aceleração = força (tempo, posição, velocidade) / massa;
time + = timestep;
posição + = timestep * ( velocidade + timestep * aceleração / 2) ;
velocidade + = timestep * aceleração;
newAcceleration = força (tempo, posição, velocidade) / massa; 
velocidade + = timestep * (newAcceleration - aceleração) / 2 ;

Não tenho certeza se essa variante específica do método Verlet de velocidade tem um nome específico, mas eu o testei e parece funcionar muito bem. Não é tão preciso quanto o Runge-Kutta de ordem bucal (como seria de esperar de um método de segunda ordem), mas é muito melhor que Euler ou Verlet de velocidade ingênua sem a estimativa de velocidade intermediária, e ainda mantém a propriedade simplética da normalidade. Verlet de velocidade para forças conservadoras e não dependentes da velocidade.

Edit 2: Um algoritmo muito semelhante é descrito, por exemplo, por Groot & Warren ( J. Chem. Phys. 1997) , embora, lendo nas entrelinhas, parece que eles sacrificaram alguma precisão por velocidade extra ao salvar o newAccelerationvalor calculado usando a velocidade estimada. e reutilizá-lo como accelerationpara o próximo timestep. Eles também introduzem um parâmetro 0 ≤ λ ≤ 1 que é multiplicado accelerationna estimativa de velocidade inicial; por alguma razão, eles recomendam λ = 0,5, embora todos os meus testes sugiram que λ= 1 (que é efetivamente o que eu uso acima) funciona tão bem ou melhor, com ou sem a reutilização da aceleração. Talvez tenha algo a ver com o fato de que suas forças incluem um componente estocástico do movimento browniano.

Ilmari Karonen
fonte
O Velocity Verlet é bom, mas não pode ter potencial dependente da velocidade; portanto, o atrito não pode ser implementado. Eu acho que Runge-Kutta 2 é o melhor para o meu propósito;)
Pizzirani Leonardo
1
@PizziraniLeonardo: Você pode usar (uma variante de) velocidade Verlet muito bem, mesmo para forças dependentes da velocidade; veja minha edição acima.
Ilmari Karonen
1
A literatura não atribui a essa interpretação do Velocity Verlet um nome diferente. Baseia-se em uma estratégia preditora-corretora, como também declarado neste documento fire.nist.gov/bfrlpubs/build99/PDF/b99014.pdf .
Teodron #
3
@ Unit978: Isso depende do jogo e, especificamente, do modelo de física que ele implementa. A force(time, position, velocity)resposta acima é apenas uma abreviação de "a força que age sobre um objeto ao se positionmover velocityem time". Normalmente, a força dependeria de coisas como se o objeto está em queda livre ou sentado em uma superfície sólida, se outros objetos próximos estão exercendo uma força sobre ele, quão rápido ele está se movendo sobre uma superfície (atrito) e / ou através de um líquido ou gás (arraste) etc.
Ilmari Karonen 23/11
1
Esta é uma ótima resposta, mas está incompleta sem falar sobre etapa de tempo fixo ( gafferongames.com/game-physics/fix-your-timestep ). Eu adicionaria uma resposta separada, mas a maioria das pessoas para na resposta aceita, especialmente quando ela tem mais votos por uma margem tão grande, como é o caso aqui. Eu acho que a comunidade está melhor servida aumentando esta.
Jibb inteligente
13

A cada atualização do seu jogo, faça o seguinte:

if (collidingBelow())
    gravity = 0;
else gravity = [insert gravity value here];

velocity.y += gravity;

Por exemplo, em um jogo de plataformas, uma vez que você pula, a gravidade seria ativada (collidingBelow diz se existe ou não terreno logo abaixo de você) e uma vez que você atingir o solo, ele será desativado.

Além disso, para implementar saltos, faça o seguinte:

if (pressingJumpButton() && collidingBelow())
    velocity.y = [insert jump speed here]; // the jump speed should be negative

E, obviamente, no loop de atualização, você também precisa atualizar sua posição:

position += velocity;
Pecant
fonte
6
O que você quer dizer? Basta escolher seu próprio valor de gravidade e, uma vez que ele altera sua velocidade, não apenas sua posição, parece natural.
Pecant
1
Eu não gosto de desligar a gravidade. Eu acho que a gravidade deve ser constante. O que deve mudar (imho) é a sua capacidade de pular.
Ultifinitus
2
Se ajudar, pense nisso como "queda", em vez de "gravidade". A função como um todo controla se o objeto está caindo ou não devido à gravidade. A própria gravidade existe exatamente como essa [insira o valor da gravidade aqui]. Portanto, nesse sentido, a gravidade é constante, você não a usa para nada, a menos que o objeto esteja no ar.
Jason Pineo
2
Esse código depende da taxa de quadros, o que não é ótimo, mas se você tiver uma atualização constante, estará rindo.
tenpn
1
-1, desculpe. Adicionando velocidade e gravidade, ou posição e velocidade, isso simplesmente não faz sentido. Entendo o atalho que você está fazendo, mas está errado. Eu atingia qualquer aluno, estagiário ou colega que fizesse isso com o maior cluebat que eu pudesse encontrar. A consistência das unidades é importante.
sam hocevar
8

Uma integração física newtoniana independente * da taxa de quadros apropriada:

Vector forces = 0.0f;

// gravity
forces += down * m_gravityConstant; // 9.8m/s/s on earth

// left/right movement
forces += right * m_movementConstant * controlInput; // where input is scaled -1..1

// add other forces in for taste - usual suspects include air resistence
// proportional to the square of velocity, against the direction of movement. 
// this has the effect of capping max speed.

Vector acceleration = forces / m_massConstant; 
m_velocity += acceleration * timeStep;
m_position += velocity * timeStep;

Ajuste a gravidadeConstante, movimentoConstante e massaConstante até que pareça certo. É uma coisa intuitiva e pode demorar um pouco para se sentir bem.

É fácil estender o vetor de forças para adicionar uma nova jogabilidade - por exemplo, adicione uma força para longe de qualquer explosão próxima ou em direção a buracos negros.

* editar: esses resultados estarão errados ao longo do tempo, mas podem ser "bons o suficiente" para sua fidelidade ou aptidão. Veja este link http://lol.zoy.org/blog/2011/12/14/understanding-motion-in-games para mais informações.

tenpn
fonte
4
Não use a integração do Euler. Veja este artigo de Glenn Fiedler, que explica os problemas e soluções melhor do que eu. :)
Martin Sojka
1
Entendo como o Euler é impreciso ao longo do tempo, mas acho que há cenários em que isso realmente não importa. Desde que as regras sejam consistentes para todos e "pareça" certo, tudo bem. E se você está apenas aprendendo sobre phyiscs, é muito fácil lembrar e implementar.
tenpn
... bom link embora. ;)
tenpn 26/08/11
4
Você pode corrigir a maioria dos problemas com a integração do Euler simplesmente substituindo position += velocity * timestepacima por position += (velocity - acceleration * timestep / 2) * timestep(onde velocity - acceleration * timestep / 2é simplesmente a média das velocidades antiga e nova). Em particular, esse integrador fornece resultados exatos se a aceleração for constante, como normalmente ocorre na gravidade. Para obter uma precisão melhor sob aceleração variável, você pode adicionar uma correção semelhante à atualização de velocidade para obter a integração do Verlet com a velocidade .
Ilmari Karonen
Seus argumentos fazem sentido, e a imprecisão geralmente não é grande coisa. Mas você não deve afirmar que é uma integração "independente da taxa de quadros adequada", porque simplesmente não é (independente da taxa de quadros).
sam hocevar 02/11/12
3

Se você deseja implementar a gravidade em uma escala um pouco maior, use este tipo de cálculo a cada loop:

for each object in the scene
  for each other_object in the scene not equal to object
    if object.mass * other_object.mass / object.distanceSquaredBetweenCenterOfMasses(other_object) < epsilon
      abort the calculation for this pair
    if object.mass is much, much bigger than other_object.mass
      abort the calculation for this pair
    force = gravitational_constant
            * object.mass * other_object.mass
            / object.distanceSquaredBetweenCenterOfMasses(other_object)
    object.addForceAtCenterOfMass(force * object.normalizedDirectionalVectorTo(other_object))
  end for loop
end for loop

Para escalas ainda maiores (galácticas), a gravidade por si só não é suficiente para criar um movimento "real". A interação dos sistemas estelares é em uma extensão significativa e muito visível ditada pelas equações de Navier-Stokes para a dinâmica de fluidos, e você terá que manter em mente a velocidade finita da luz - e, portanto, a gravidade -.

Martin Sojka
fonte
1

O código fornecido por Ilmari Karonen está quase correto, mas há uma pequena falha. Na verdade, você calcula a aceleração 2 vezes por tick, isso não segue as equações do livro.

acceleration = force(time, position) / mass; // Here
time += timestep;
position += timestep * (velocity + timestep * acceleration / 2);
newAcceleration = force(time, position) / mass;
velocity += timestep * (acceleration + newAcceleration) / 2;

O seguinte mod está correto:

time += timestep;
position += timestep * (velocity + timestep * acceleration / 2);
oldAcceletation = acceleration; // Store it
acceleration = force(time, position) / mass;
velocity += timestep * (acceleration + oldAcceleration) / 2;

Felicidades'

Satanfu
fonte
Eu acho que você está errado, como a aceleração depende da velocidade
Super
-4

O respondente da Pecant ignorou o tempo de exibição, e isso torna seu comportamento físico diferente de tempos em tempos.

Se você for fazer um jogo muito simples, poderá criar seu próprio mecanismo de física - atribua massa e todos os tipos de parâmetros físicos para cada objeto em movimento, faça uma detecção de colisão e atualize sua posição e velocidade a cada quadro. Para acelerar esse progresso, você precisa simplificar a malha de colisão, reduzir as chamadas de detecção de colisão, etc. Na maioria dos casos, isso é uma dor.

É melhor usar o mecanismo de física como physix, ODE e bullet. Qualquer um deles será estável e eficiente o suficiente para você.

http://www.nvidia.com/object/physx_new.html

http://bulletphysics.org/wordpress/

Raymond
fonte
4
-1 Resposta inútil que não responde à pergunta.
Doppelgreener
4
bem, se você quiser ajustá-lo para o tempo, basta dimensionar a velocidade pelo tempo decorrido desde a última atualização ().
Pecant