¿Cómo puedo implementar la gravedad? No para un idioma en particular, solo pseudocódigo ...
¿Cómo puedo implementar la gravedad? No para un idioma en particular, solo pseudocódigo ...
Respuestas:
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 newAcceleration
valor calculado usando la velocidad estimada y reutilizándolo como acceleration
para el siguiente paso de tiempo. También introducen un parámetro 0 ≤ λ ≤ 1 que se multiplica acceleration
en 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.
force(time, position, velocity)
en mi respuesta anterior es solo una abreviatura de "la fuerza que actúa sobre un objeto al position
moverse velocity
a 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.
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;
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.
position += velocity * timestep
arriba con position += (velocity - acceleration * timestep / 2) * timestep
(donde velocity - acceleration * timestep / 2
es 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 .
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.
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'
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.