language agnostic - libro - ¿Cómo aplico la gravedad a mi aplicación de pelota que rebota?
gravity force value (9)
He escrito una aplicación java bastante simple que te permite arrastrar el mouse y, en función de la duración del arrastre del mouse que hiciste, disparará una bola en esa dirección, rebotando en las paredes a medida que avanza.
Aquí hay una captura de pantalla rápida:
texto alternativo http://img222.imageshack.us/img222/3179/ballbouncemf9.png
Cada uno de los círculos en la pantalla es un objeto Ball. El movimiento de las bolas se divide en un vector xey;
public class Ball {
public int xPos;
public int yPos;
public int xVector;
public int yVector;
public Ball(int xPos, int yPos, int xVector, int yVector) {
this.xPos = xPos;
this.yPos = yPos;
this.xVector = xVector;
this.yVector = yVector;
}
public void step()
{
posX += xVector;
posY += yVector;
checkCollisions();
}
public void checkCollisions()
{
// Check if we have collided with a wall
// If we have, take the negative of the appropriate vector
// Depending on which wall you hit
}
public void draw()
{
// draw our circle at it''s position
}
}
Esto funciona genial Todas las bolas rebotan de pared a pared.
Sin embargo, he decidido que quiero poder incluir los efectos de la gravedad. Sé que los objetos se aceleran hacia la Tierra a 9.8 m / s, pero no sé directamente cómo esto debería traducirse en código. Me doy cuenta de que el yVector se verá afectado, pero mi experimentación con esto no tuvo el efecto deseado que yo quería.
Idealmente, me gustaría poder agregar algún efecto de gravedad a este programa y también permitir que las bolas rebote un par de veces antes de establecerse en el "suelo".
¿Cómo puedo crear este efecto gravitatorio de rebote-elástico? ¿Cómo debo manipular los vectores de velocidad de la pelota en cada paso? ¿Qué se debe hacer cuando golpea el "suelo" para que pueda permitir que rebote nuevamente, pero algo más corto que la vez anterior?
Se agradece cualquier ayuda al señalarme en la dirección correcta.
Gracias por los comentarios a todos! ¡Ya está funcionando genial!
En mi paso () estoy agregando una constante de gravedad a mi yVector como la gente sugirió y este es mi checkCollision ():
public void checkCollision()
{
if (posX - radius < 0) // Left Wall?
{
posX = radius; // Place ball against edge
xVector = -(xVector * friction);
}
else if (posX + radius > rightBound) // Right Wall?
{
posX = rightBound - radius; // Place ball against edge
xVector = -(xVector * friction);
}
// Same for posY and yVector here.
}
Sin embargo, las bolas continuarán deslizándose / rodando por el suelo. Supongo que esto es porque simplemente estoy tomando un porcentaje (90%) de sus vectores cada rebote y nunca es realmente cero. ¿Debo agregar una verificación de que si el xVector se convierte en un cierto valor absoluto, debería simplemente cambiarlo a cero?
Qué se debe hacer cuando golpea el "suelo" para que pueda permitir que rebote de nuevo
Si supone una colisión perfecta (es decir, toda la energía se conserva), todo lo que tiene que hacer es invertir el signo de uno de los escalares de velocidad, dependiendo de qué pared fue golpeada.
Por ejemplo, si la pelota golpea las paredes derecha o izquierda, revele el componente x escalar y deje el componente escalar y y de la misma manera:
this.xVector = -this.xVector;
Si la pelota golpea las paredes superiores e inferiores invierta el componente escalar y deje el componente x escalar igual:
this.yVector = -this.yVector;
pero algo más corto que el tiempo anterior?
En este escenario, parte de la energía se perderá en la colisión con la pared, así que simplemente agregue un factor de pérdida para tomar parte de la velocidad cada vez que golpee la pared:
double loss_factor = 0.99;
this.xVector = -(loss_factor * this.xVector);
this.yVector = -(loss_factor * this.yVector;
Cada vez que se corta, debe aplicar los efectos de la gravedad acelerando la bola en la dirección y hacia abajo. Como Bill K sugirió, eso es tan simple como hacer una resta de su "yVector". Cuando la pelota toca el fondo, yVector = -yVector, entonces ahora se mueve hacia arriba pero sigue acelerándose hacia abajo. Si quieres hacer que las bolas finalmente dejen de rebotar, necesitas hacer las colisiones ligeramente inelásticas, básicamente eliminando algo de velocidad en la dirección y-up, posiblemente en lugar de "yVector = -yVector", hazlo "yVector = -0.9 * yVector ".
Estoy de acuerdo con lo que dijo "Bill K", y agregaría que si quieres que se "asienten" necesitarás reducir los vectores xey en el tiempo (aplicar resistencia). Esto tendrá que ser una cantidad muy pequeña a la vez, por lo que puede tener que cambiar sus vectores de int a un tipo de punto flotante, o solo reducirlos en 1 cada pocos segundos.
Lo que quiere hacer es cambiar los valores de xVector y yVector para simular la gravedad y la fricción. Esto es realmente bastante simple de hacer. (Necesita cambiar todas sus variables a flotadores. Cuando llega el momento de dibujar, simplemente rodee las carrozas).
En su función de paso, después de actualizar la posición de la bola, debe hacer algo como esto:
yVector *= 0.95;
xVector *= 0.95;
yVector -= 2.0;
Esto escala la velocidad X e Y ligeramente hacia abajo, permitiendo que tus bolas finalmente dejen de moverse, y luego aplica una constante "aceleración" hacia abajo al valor Y, que se acumulará más rápido que la "ralentización" y hará que las bolas caigan.
Esta es una aproximación de lo que realmente quieres hacer. Lo que realmente quieres es mantener un vector que represente la aceleración de tus bolas. En cada paso, entonces se salpica el producto con un vector gravitatorio constante para cambiar ligeramente la aceleración de la bola. Pero creo que puedo ser más complejo de lo que quieres, a menos que estés buscando una simulación de física más realista.
Realmente deseas simular lo que hace la gravedad: todo lo que hace es crear una fuerza que actúa con el tiempo para cambiar la velocidad de un objeto. Cada vez que das un paso, cambias la velocidad de tu bola un poco para "tirarla" hacia la parte inferior del widget.
Para lidiar con el problema de la ausencia de fricción / rebote, es necesario que la colisión "terrestre" ejerza un efecto diferente al reflejo estricto: debe eliminar cierta cantidad de energía de la bola, haciendo que rebote en una menor velocidad después de que golpea el suelo de lo que lo haría de otra manera.
Otra cosa que generalmente desea hacer en este tipo de visualizaciones saltarinas es ceder al suelo también la fricción lateral, de modo que cuando toque el suelo todo el tiempo, eventualmente se detenga.
public void step()
{
posX += xVector;
posY += yVector;
yVector += g //some constant representing 9.8
checkCollisions();
}
en checkCollisions (), debe invertir y multiplicar yVector por un número entre 0 y 1 cuando rebota en el suelo. Esto debería darte el efecto deseado
Cuando las bolas están rodando por el suelo, sí, verifique si la velocidad está por debajo de un cierto valor mínimo y, en caso afirmativo, ajústelo a cero. Si observas la física detrás de este tipo de movimiento idealizado y lo comparas con lo que sucede en el mundo real, verás que una sola ecuación no puede usarse para explicar el hecho de que una bola real deja de moverse.
Por cierto, lo que estás haciendo se llama el método de Euler para la integración numérica. Dice así:
Comience con las ecuaciones de movimiento cinemáticas:
x (t) = x0 + vx * t + 0.5 * ax t ^ 2
y (t) = y0 + vy t + 0.5 * ay t ^ 2
vx (t) = vx0 + ax t
vy (t) = vy0 + ay * t
Donde xey son posición, vx y vy son velocidad, ax y ay son aceleración, y t es tiempo. x0, y0, vx0 y vy0 son los valores iniciales. Esto describe el movimiento de un objeto en ausencia de cualquier fuerza externa.Ahora aplica la gravedad:
ay = -9.8 m / s ^ 2
Hasta este punto, no hay necesidad de hacer nada complicado. Podemos resolver la posición de cada bola usando esta ecuación para cualquier momento.Ahora agregue fricción de aire: dado que es una bola esférica, podemos suponer que tiene un coeficiente de fricción c. Normalmente hay dos opciones de cómo modelar la fricción del aire. Puede ser proporcional a la velocidad o al cuadrado de velocidad. Usemos el cuadrado:
ax = -c vx ^ 2
ay = -c vy ^ 2 - 9.8
Debido a que la aceleración ahora depende de la velocidad, que no es constante, debemos integrar. Esto es malo, porque no hay forma de resolver esto a mano. Tendremos que integrar numéricamente.- Tomamos pasos de tiempo discretos, dt. Para el método de Euler, simplemente reemplazamos todas las ocurrencias de t en las ecuaciones anteriores con dt, y usamos el valor del paso de tiempo anterior en lugar de los valores iniciales, x0, y0, etc. Así que ahora nuestras ecuaciones se ven así (en pseudocódigo) :
// Guarda los valores anteriores
xold = x;
yold = y;
vxold = vx;
vyold = vy;
// Actualización aceleración
ax = -c vxold ^ 2;
ay = -c vyold ^ 2 - 9.8;
// Actualización de velocidad
vx = vxold + ax dt;
vy = vyold + ay dt;
// Actualizar posición
x = xold + vxold * dt + 0.5 * ax dt ^ 2;
y = yold + vyold dt + 0.5 * ay * dt ^ 2;
Esta es una aproximación, por lo que no será exactamente correcta, pero se verá bien. El problema es que a mayores tiempos, el error aumenta, por lo que si queremos modelar con precisión cómo se movería una bola real, tendríamos que usar valores muy pequeños para dt, lo que causaría problemas con la precisión en una computadora. Para resolver eso, hay técnicas más complicadas. Pero si solo quieres ver un comportamiento que se parece a la gravedad y la fricción al mismo tiempo, entonces el método de Euler está bien.
Lo que tienes que hacer es restar constantemente una pequeña constante (algo que representa tus 9.8 m / s) de tu yVector. Cuando la bola está bajando (yVector ya es negativo), esto lo haría ir más rápido. Cuando está subiendo (yVector es positivo) lo ralentizaría.
Esto no explicaría la fricción, por lo que las cosas deberían rebotar casi para siempre.
edit1: Para tener en cuenta la fricción, siempre que se invierta (y se invierta el signo), baje un poco el número absoluto. Como si golpea en yVector = -500, cuando inviertes el signo, hazlo +480 en lugar de +500. Probablemente deberías hacer lo mismo con xVector para evitar que rebote de lado a lado.
edit2: Además, si desea que reaccione a la "fricción del aire", reduzca ambos vectores en una cantidad muy pequeña en cada ajuste.
edit3: Sobre la cosa que gira en el fondo para siempre: dependiendo de qué tan altos sean tus números, podría ser una de dos cosas. O bien tus números son grandes y parece que tardará una eternidad en terminar, o estás redondeando y tus vectores son siempre 5 o algo así. (90% de 5 es 4.5, por lo que puede redondear hasta 5).
Imprimía una declaración de depuración y veo cómo son los números de Vector. Si van a un lugar alrededor de 5 y simplemente permanecen allí, entonces puedes usar una función que trunca tu fracción a 4 en lugar de redondear a 5. Si sigue bajando y finalmente se detiene, entonces podrías tener que aumentar tu coeficiente de fricción .
Si no puede encontrar una función de "redondeo" fácil, podría usar (0.9 * Vector) - 1, restando 1 de su ecuación existente debería hacer lo mismo.
Es un movimiento balístico. Entonces tienes un movimiento lineal en el eje X y un movimiento uniforme acelerado en el eje y.
La idea básica es que el eje y seguirá la ecuación:
y = y0 + v0 * t + (0.5)*a*t^2
O, en código C, por ejemplo:
float speed = 10.0f, acceleration = -9.8f, y = [whatever position];
y += speed*t + 0.5f*acceleration*t^2;
Donde aquí uso la parametrización de tiem. Pero podrías usar Torricelli:
v = sqrt(v0^2 + 2*acceleration*(y-y0));
Y, en este modelo, debe mantener los últimos valores de v e y.
Finalmente, he hecho algo similar usando el primer modelo con dt (diferencial de tiempo) fijado en 1/60 segundos (60 FPS).
Bueno, ambos modelos dan buenos resultados reales, pero sqrt (), por ejemplo, es costoso.