¿Cómo puedo implementar la gravedad?

Respuestas:

52

Como otros han señalado en los comentarios, el método básico de integración de Euler descrito en la respuesta de tenpn tiene algunos problemas:

  • Incluso para el movimiento simple, como el salto balístico bajo gravedad constante, introduce un error sistemático.

  • El error depende del paso de tiempo, lo que significa que cambiar el paso de tiempo cambia las trayectorias de los objetos de una manera sistemática que los jugadores pueden notar si el juego usa un paso de tiempo variable. Incluso para juegos con un paso de tiempo de física fijo, cambiar el paso de tiempo durante el desarrollo puede afectar notablemente la física del juego, como la distancia a la que volará un objeto lanzado con una fuerza determinada, lo que podría romper niveles previamente diseñados.

  • No conserva energía, incluso si la física subyacente debería hacerlo. En particular, los objetos que deberían oscilar constantemente (por ejemplo, péndulos, resortes, planetas en órbita, etc.) pueden acumular energía de manera constante hasta que todo el sistema se desmorone.

Afortunadamente, no es difícil reemplazar la integración de Euler con algo que sea casi tan simple, pero que no tenga ninguno de estos problemas, específicamente, un integrador simpléctico de segundo orden, como la integración de salto de rana o el método de Verlet de velocidad estrechamente relacionado . En particular, donde la integración básica de Euler actualiza la velocidad y la posición como:

aceleración = fuerza (tiempo, posición) / masa;
tiempo + = paso de tiempo;
posición + = paso de tiempo * velocidad;
velocidad + = paso de tiempo * aceleración;

el método de Verlet de velocidad lo hace así:

aceleración = fuerza (tiempo, posición) / masa;
tiempo + = paso de tiempo;
posición + = paso de tiempo * ( velocidad + paso de tiempo * aceleración / 2) ;
newAcceleration = fuerza (tiempo, posición) / masa; 
velocidad + = paso de tiempo * ( aceleración + nueva aceleración ) / 2 ;

Si tiene varios objetos que interactúan, debe actualizar todas sus posiciones antes de volver a calcular las fuerzas y actualizar las velocidades. Las nuevas aceleraciones se pueden guardar y usar para actualizar las posiciones en el siguiente paso de tiempo, reduciendo el número de llamadas force()a uno (por objeto) por paso de tiempo, al igual que con el método Euler.

Además, si la aceleración es normalmente constante (como la gravedad durante el salto balístico), podemos simplificar lo anterior a solo:

tiempo + = paso de tiempo;
posición + = paso de tiempo * ( velocidad + paso de tiempo * aceleración / 2) ;
velocidad + = paso de tiempo * aceleración;

donde el término extra en negrita es el único cambio en comparación con la integración básica de Euler.

En comparación con la integración de Euler, los métodos de velocidad Verlet y leapfrog tienen varias propiedades agradables:

  • Para una aceleración constante, dan resultados exactos (de todos modos, hasta errores de redondeo de punto flotante), lo que significa que las trayectorias de salto balístico se mantienen igual incluso si se cambia el paso de tiempo.

  • Son integradores de segundo orden, lo que significa que, incluso con aceleración variable, el error de integración promedio es solo proporcional al cuadrado del paso de tiempo. Esto puede permitir tiempos más largos sin comprometer la precisión.

  • Son simplécticos , lo que significa que conservan energía si la física subyacente lo hace (al menos mientras el paso de tiempo sea constante). En particular, esto significa que no obtendrás cosas como planetas que vuelan espontáneamente fuera de sus órbitas, u objetos unidos entre sí con resortes que se tambalean gradualmente más y más hasta que todo explota.

Sin embargo, el método de velocidad Verlet / leapfrog es casi tan simple y rápido como la integración básica de Euler, y ciertamente mucho más simple que alternativas como la integración Runge-Kutta de cuarto orden (que, aunque generalmente es un integrador muy agradable, carece de la propiedad simpléctica y requiere cuatro evaluaciones de la force()función por paso de tiempo). Por lo tanto, los recomendaría encarecidamente para cualquiera que escriba cualquier tipo de código de física del juego, incluso si es tan simple como saltar de una plataforma a otra.


Editar: Si bien la derivación formal del método Verlet de velocidad solo es válida cuando las fuerzas son independientes de la velocidad, en la práctica puede usarla bien incluso con fuerzas dependientes de la velocidad, como el arrastre de fluido . Para obtener los mejores resultados, debe usar el valor de aceleración inicial para estimar la nueva velocidad para la segunda llamada force(), de esta manera:

aceleración = fuerza (tiempo, posición, velocidad) / masa;
tiempo + = paso de tiempo;
posición + = paso de tiempo * ( velocidad + paso de tiempo * aceleración / 2) ;
velocidad + = paso de tiempo * aceleración;
newAcceleration = fuerza (tiempo, posición, velocidad) / masa; 
velocidad + = paso de tiempo * (nueva aceleración - aceleración) / 2 ;

No estoy seguro de si esta variante particular del método de velocidad Verlet tiene un nombre específico, pero lo he probado y parece funcionar muy bien. No es tan preciso como el Runge-Kutta de orden inicial (como cabría esperar de un método de segundo orden), pero es mucho mejor que Euler o Verlet de velocidad ingenua sin la estimación de velocidad intermedia, y aún conserva la propiedad simpléctica de la normalidad Verlet de velocidad para fuerzas conservadoras no dependientes de la velocidad.

Edición 2: Groot y Warren ( J. Chem. Phys. 1997) describen un algoritmo muy similar , aunque, al leer entre líneas, parece que sacrificaron algo de precisión por la velocidad extra al guardar el newAccelerationvalor calculado usando la velocidad estimada y reutilizándolo como accelerationpara el siguiente paso de tiempo. También introducen un parámetro 0 ≤ λ ≤ 1 que se multiplica accelerationen la estimación de velocidad inicial; por alguna razón, recomiendan λ = 0.5, aunque todas mis pruebas sugieren que λ= 1 (que es efectivamente lo que uso arriba) funciona igual o mejor, con o sin la reutilización de aceleración. Tal vez tenga algo que ver con el hecho de que sus fuerzas incluyen un componente de movimiento browniano estocástico.

Ilmari Karonen
fuente
Velocity Verlet es agradable, pero no puede tener un potencial dependiente de la velocidad, por lo que la fricción no se puede implementar. Creo que Runge-Kutta 2 es el mejor para mi propósito;)
Pizzirani Leonardo
1
@PizziraniLeonardo: Puedes usar (una variante de) Verlet de velocidad muy bien incluso para fuerzas dependientes de la velocidad; mira mi edición arriba.
Ilmari Karonen
1
La literatura no le da a esta interpretación de Velocity Verlet un nombre diferente. Se basa en una estrategia predictor-correctora, como también se indica en este documento fire.nist.gov/bfrlpubs/build99/PDF/b99014.pdf .
teodron el
3
@ Unit978: Eso depende del juego, y específicamente del modelo de física que implementa. El force(time, position, velocity)en mi respuesta anterior es solo una abreviatura de "la fuerza que actúa sobre un objeto al positionmoverse velocitya las time". Por lo general, la fuerza dependería de cosas como si el objeto está en caída libre o sentado en una superficie sólida, si cualquier otro objeto cercano ejerce una fuerza sobre él, qué tan rápido se mueve sobre una superficie (fricción) y / o a través de un líquido o gas (arrastre), etc.
Ilmari Karonen
1
Esta es una gran respuesta, pero está incompleta sin hablar del paso de tiempo fijo ( gafferongames.com/game-physics/fix-your-timestep ). Agregaría una respuesta por separado, pero la mayoría de las personas se detienen en la respuesta aceptada, especialmente cuando tiene la mayoría de los votos por un margen tan grande, como es el caso aquí. Creo que la comunidad está mejor atendida al aumentar esta.
Jibb Smart el
13

Cada ciclo de actualización de tu juego, haz esto:

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

velocity.y += gravity;

Por ejemplo, en un juego de plataformas, una vez que saltas, la gravedad estaría habilitada (colisionar a continuación te indica si hay o no suelo justo debajo de ti) y una vez que tocas el suelo, se desactivará.

Además de esto, para implementar saltos, haga esto:

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

Y, obviamente, en el ciclo de actualización también debe actualizar su posición:

position += velocity;
Pecant
fuente
66
¿Qué quieres decir? Simplemente elija su propio valor de gravedad y, dado que cambia su velocidad, no solo su posición, se ve natural.
Pecant
1
No me gusta apagar la gravedad nunca. Creo que la gravedad debería ser constante. Lo que debería cambiar (en mi humilde opinión) es tu habilidad para saltar.
ultifinitus
2
Si ayuda, considérelo como 'caída' en lugar de 'gravedad'. La función como un todo controla si el objeto está cayendo o no debido a la gravedad. La gravedad en sí misma existe como eso [inserte el valor de la gravedad aquí]. Entonces, en ese sentido, la gravedad es constante, simplemente no la usas para nada a menos que el objeto esté en el aire.
Jason Pineo
2
Este código depende de la velocidad de fotogramas, lo cual no es bueno, pero si tiene una actualización constante, entonces se está riendo.
Tenpn
1
-1, lo siento. Agregar velocidad y gravedad, o posición y velocidad, simplemente no tiene sentido. Entiendo el atajo que estás haciendo, pero está mal. Golpearía a cualquier estudiante, aprendiz o colega que hiciera eso con el mayor golpe de pista que pudiera encontrar. La consistencia de las unidades sí importa.
Sam Hocevar
8

Una integración de física newtoniana independiente de la velocidad de fotogramas adecuada *:

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;

Ajustar la gravedad constante, el movimiento constante y la masa constante hasta que se sienta bien. Es algo intuitivo y puede llevar un tiempo sentirse bien.

Es fácil extender el vector de fuerzas para agregar una nueva jugabilidad; por ejemplo, agregue una fuerza lejos de cualquier explosión cercana o hacia agujeros negros.

* editar: estos resultados serán incorrectos con el tiempo, pero pueden ser "lo suficientemente buenos" para su fidelidad o aptitud. Consulte este enlace http://lol.zoy.org/blog/2011/12/14/understanding-motion-in-games para obtener más información.

tenpn
fuente
44
No uses la integración de Euler. Vea este artículo de Glenn Fiedler que explica los problemas y las soluciones mejor que yo. :)
Martin Sojka
1
Entiendo que Euler es inexacto con el tiempo, pero creo que hay escenarios en los que realmente no importa. Mientras las reglas sean consistentes para todos, y "se sienta" bien, está bien. Y si solo está aprendiendo sobre phyiscs, es muy fácil de recordar e implicar.
Tenpn
... buen enlace sin embargo. ;)
tenpn
44
Puede solucionar la mayoría de los problemas con la integración de Euler simplemente reemplazando position += velocity * timesteparriba con position += (velocity - acceleration * timestep / 2) * timestep(donde velocity - acceleration * timestep / 2es simplemente el promedio de las velocidades antiguas y nuevas). En particular, este integrador da resultados exactos si la aceleración es constante, como suele ser para la gravedad. Para una mejor precisión bajo aceleración variable, puede agregar una corrección similar a la actualización de velocidad para obtener la integración Verlet de velocidad .
Ilmari Karonen
Sus argumentos tienen sentido, y la inexactitud a menudo no es gran cosa. Pero no debe afirmar que es una integración “independiente de la velocidad de fotogramas adecuada”, porque simplemente no lo es (independiente de la velocidad de fotogramas).
Sam Hocevar
3

Si desea implementar la gravedad en una escala ligeramente mayor, puede usar este tipo de cálculo en cada ciclo:

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

Sin embargo, para escalas aún más grandes (galácticas), la gravedad por sí sola no será suficiente para crear un movimiento "real". La interacción de los sistemas estelares es, en gran medida, muy visible, dictada por las ecuaciones de Navier-Stokes para la dinámica de fluidos, y también tendrás que tener en cuenta la velocidad finita de la luz y, por lo tanto, la gravedad.

Martin Sojka
fuente
1

El código proporcionado por Ilmari Karonen es casi correcto, pero hay una pequeña falla. Realmente calculas la aceleración 2 veces por tic, esto no sigue las ecuaciones de los libros de texto.

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

El siguiente mod es correcto:

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

Salud'

Satanfu
fuente
Creo que te equivocas, ya que la aceleración depende de la velocidad
super
-4

El contestador de Pecant ignoró el tiempo del cuadro y eso hace que su comportamiento físico sea diferente de vez en cuando.

Si va a hacer un juego muy simple, puede hacer su propio pequeño motor de física: asigne masa y todo tipo de parámetros de física para cada objeto en movimiento, y detecte colisiones, luego actualice su posición y velocidad en cada cuadro. Para acelerar este progreso, debe simplificar la malla de colisión, reducir las llamadas de detección de colisión, etc. En la mayoría de los casos, eso es un dolor.

Es mejor usar un motor de física como physix, ODE y bullet. Cualquiera de ellos será lo suficientemente estable y eficiente para usted.

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

http://bulletphysics.org/wordpress/

Raymond
fuente
44
-1 Respuesta inútil que no responde la pregunta.
doppelgreener
44
bueno, si desea ajustarlo por tiempo, puede escalar la velocidad por el tiempo transcurrido desde la última actualización ().
Pecant