Me he encontrado con otro problema en mi pequeño juego de pelota que rebota.
Mi pelota está rebotando bien, excepto en los últimos momentos cuando está a punto de descansar. El movimiento de la pelota es suave para la parte principal pero, hacia el final, la pelota se sacude un poco mientras se asienta en la parte inferior de la pantalla.
Puedo entender por qué sucede esto, pero parece que no puedo suavizarlo.
Le agradecería cualquier consejo que pueda ofrecer.
Mi código de actualización es:
public void Update()
{
// Apply gravity if we're not already on the ground
if(Position.Y < GraphicsViewport.Height - Texture.Height)
{
Velocity += Physics.Gravity.Force;
}
Velocity *= Physics.Air.Resistance;
Position += Velocity;
if (Position.X < 0 || Position.X > GraphicsViewport.Width - Texture.Width)
{
// We've hit a vertical (side) boundary
// Apply friction
Velocity *= Physics.Surfaces.Concrete;
// Invert velocity
Velocity.X = -Velocity.X;
Position.X = Position.X + Velocity.X;
}
if (Position.Y < 0 || Position.Y > GraphicsViewport.Height - Texture.Height)
{
// We've hit a horizontal boundary
// Apply friction
Velocity *= Physics.Surfaces.Grass;
// Invert Velocity
Velocity.Y = -Velocity.Y;
Position.Y = Position.Y + Velocity.Y;
}
}
Quizás también debería señalar eso Gravity
, Resistance
Grass
y Concrete
son todos del tipo Vector2
.
Respuestas:
Aquí los pasos necesarios para mejorar su ciclo de simulación física.
1. Timestep
El principal problema que puedo ver con su código es que no tiene en cuenta el tiempo del paso de física. Debería ser obvio que hay algo mal
Position += Velocity;
porque las unidades no coinciden. O enVelocity
realidad no es una velocidad, o falta algo.Incluso si sus valores de velocidad y gravedad están escalados de manera que cada cuadro ocurra en una unidad de tiempo
1
(lo que significa que, por ejemplo, enVelocity
realidad significa la distancia recorrida en un segundo), el tiempo debe aparecer en algún lugar de su código, ya sea implícitamente (arreglando las variables de manera que sus nombres reflejan lo que realmente almacenan) o explícitamente (al introducir un paso de tiempo). Creo que lo más fácil es declarar la unidad de tiempo:Y use ese valor donde sea necesario:
Tenga en cuenta que cualquier compilador decente simplificará las multiplicaciones
1.0
, por lo que esa parte no hará las cosas más lentas.Ahora
Position += Velocity * TimeStep
todavía no es del todo exacto (vea esta pregunta para entender por qué), pero probablemente lo hará por ahora.Además, esto debe tener en cuenta el tiempo:
Es un poco más complicado de arreglar; Una forma posible es:
2. actualizaciones dobles
Ahora verifique lo que hace cuando rebota (solo se muestra el código relevante):
Puede ver que
TimeStep
se usa dos veces durante el rebote. Básicamente, esto le da a la pelota el doble de tiempo para actualizarse. Esto es lo que debería suceder en su lugar:3. Gravedad
Verifique esta parte del código ahora:
Agrega gravedad durante toda la duración del cuadro. Pero, ¿qué pasa si la pelota realmente rebota durante ese cuadro? ¡Entonces la velocidad se invertirá, pero la gravedad que se agregó hará que la bola acelere lejos del suelo! Por lo tanto , se deberá eliminar el exceso de gravedad al rebotar y luego volver a agregarlo en la dirección correcta.
Puede suceder que incluso volver a agregar la gravedad en la dirección correcta provoque que la velocidad se acelere demasiado. Para evitar esto, puede omitir la adición de gravedad (después de todo, no es tanto y solo dura un fotograma) o fijar la velocidad a cero.
4. Código fijo
Y aquí está el código completamente actualizado:
5. Adiciones adicionales
Para mejorar aún más la estabilidad de la simulación, puede decidir ejecutar su simulación física a una frecuencia más alta. Esto se hace trivial por los cambios anteriores que involucran
TimeStep
, porque solo necesita dividir su marco en tantos trozos como desee. Por ejemplo:fuente
velocity += gravity
está mal y solovelocity += gravity * timestep
tiene sentido. Puede dar el mismo resultado al final, pero sin un comentario que diga "Sé lo que estoy haciendo aquí" todavía significa un error de codificación, un programador descuidado, una falta de conocimiento sobre física o simplemente un código prototipo que necesita Ser mejorado.gravity
es en realidad ... no la gravedad. Pero puedo aclarar eso en la publicación.Agregue una marca para detener el rebote, utilizando una velocidad vertical mínima. Y cuando consigas el mínimo rebote, coloca la pelota en el suelo.
fuente
Entonces, creo que el problema de por qué sucede esto es que su bola se está acercando a un límite. Matemáticamente, la pelota nunca se detiene en la superficie, se acerca a la superficie.
Sin embargo, tu juego no usa un tiempo continuo. Es un mapa, que utiliza una aproximación a la ecuación diferencial. Y esa aproximación no es válida en esta situación limitante (puede hacerlo, pero tendría que tomar pasos de tiempo más pequeños y más pequeños, lo que supongo que no es factible).
Físicamente hablando, lo que sucede es que cuando la pelota está muy cerca de la superficie, se adhiere a ella si la fuerza total está por debajo de un umbral dado.
La respuesta de @Zhen estaría bien si su sistema es homogéneo, lo cual no lo es. Tiene algo de gravedad en el eje y.
Entonces, diría que la solución no sería que la velocidad debería estar por debajo de un umbral dado, sino que la fuerza total aplicada sobre la pelota después de la actualización debería estar por debajo de un umbral dado.
Esa fuerza es la contribución de la fuerza ejercida por la pared sobre la pelota + la gravedad.
La condición debería ser algo así como
if (newVelocity + Physics.Gravity.Force <umbral)
observe que newVelocity.y es una cantidad positiva si el rebote está en la pared inferior, y la gravedad es una cantidad negativa.
Observe también que newVelocity and Physics.Gravity.Force no tienen las mismas dimensiones, como ha escrito en
lo que significa que, como usted, supongo que delta_time = 1 y ballMass = 1.
Espero que esto ayude
fuente
Tiene una actualización de posición dentro de su verificación de colisión, es redundante e incorrecta. Y agrega energía a la pelota, lo que puede ayudarla a moverse perpetuamente. Junto con la gravedad que no se aplica en algunos cuadros, esto le da un movimiento extraño. Quitarlo
Ahora puede ver un problema diferente, que la pelota se "atasca" fuera del área designada, rebotando constantemente hacia adelante y hacia atrás.
Una manera simple de resolver este problema es verificar que la pelota se mueva en la dirección correcta antes de cambiarla.
Por lo tanto, debe hacer:
Dentro:
Y similar para la dirección Y.
Para que la pelota se detenga bien, debes detener la gravedad en algún momento. Su implementación actual asegura que la bola siempre resurgirá ya que la gravedad no la frena siempre que esté bajo tierra. Debes cambiar a aplicar siempre la gravedad. Sin embargo, esto lleva a que la pelota se hunda lentamente en el suelo después de asentarse. Una solución rápida para esto es, después de aplicar la gravedad, si la pelota está por debajo del nivel de la superficie y se mueve hacia abajo, deténgala:
Estos cambios en total deberían darle una simulación decente. Pero tenga en cuenta que sigue siendo una simulación muy simple.
fuente
Tenga un método de mutación para todos y cada uno de los cambios de velocidad, luego dentro de ese método puede verificar la velocidad actualizada para determinar si se está moviendo lo suficientemente lento como para ponerla en reposo. La mayoría de los sistemas de física que conozco llaman a esto 'restitución'.
En el método anterior, limitamos el rebote siempre que esté en el mismo eje que la gravedad.
Algo más a considerar sería detectar cada vez que una pelota ha chocado con el suelo, y si se mueve bastante lento en el momento de la colisión, ajuste la velocidad a lo largo del eje de gravedad a cero.
fuente
Otra cosa: estás multiplicando por una constante de fricción. Cambie eso: reduzca la constante de fricción pero agregue una absorción de energía fija en un rebote. Esto amortiguará esos últimos rebotes mucho más rápido.
fuente