Como posso implementar a gravidade? Não para um idioma específico, apenas pseudocódigo ...
Como posso implementar a gravidade? Não para um idioma específico, apenas pseudocódigo ...
Respostas:
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 newAcceleration
valor calculado usando a velocidade estimada. e reutilizá-lo como acceleration
para o próximo timestep. Eles também introduzem um parâmetro 0 ≤ λ ≤ 1 que é multiplicado acceleration
na 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.
force(time, position, velocity)
resposta acima é apenas uma abreviação de "a força que age sobre um objeto ao se position
mover velocity
em 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.
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;
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.
position += velocity * timestep
acima 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 .
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 -.
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'
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ê.