La extrapolación rompe la detección de colisión

10

Antes de aplicar la extrapolación al movimiento de mi sprite, mi colisión funcionó perfectamente. Sin embargo, después de aplicar la extrapolación al movimiento de mi sprite (para suavizar las cosas), la colisión ya no funciona.

Así es como funcionaban las cosas antes de la extrapolación:

ingrese la descripción de la imagen aquí

Sin embargo, después de implementar mi extrapolación, se rompe la rutina de colisión. Supongo que esto se debe a que está actuando sobre la nueva coordenada producida por la rutina de extrapolación (que se encuentra en mi llamada de renderizado).

Después de aplicar mi extrapolación

ingrese la descripción de la imagen aquí

¿Cómo corregir este comportamiento?

Intenté poner un control de colisión adicional justo después de la extrapolación; esto parece aclarar muchos de los problemas, pero lo descarté porque poner la lógica en mi renderizado está fuera de discusión.

También he intentado hacer una copia de la posición spritesX, extrapolando eso y dibujando usando eso en lugar del original, dejando así el original intacto para que la lógica lo adquiera; esta parece una mejor opción, pero aún produce algunos efectos extraños cuando choca con paredes. Estoy bastante seguro de que esta tampoco es la forma correcta de lidiar con esto.

He encontrado un par de preguntas similares aquí, pero las respuestas no me han ayudado.

Este es mi código de extrapolación:

public void onDrawFrame(GL10 gl) {


        //Set/Re-set loop back to 0 to start counting again
        loops=0;

        while(System.currentTimeMillis() > nextGameTick && loops < maxFrameskip){

        SceneManager.getInstance().getCurrentScene().updateLogic();
        nextGameTick+=skipTicks;
        timeCorrection += (1000d/ticksPerSecond) % 1;
        nextGameTick+=timeCorrection;
        timeCorrection %=1;
        loops++;
        tics++;

     }

        extrapolation = (float)(System.currentTimeMillis() + skipTicks - nextGameTick) / (float)skipTicks; 

        render(extrapolation);
}

Aplicando extrapolación

            render(float extrapolation){

            //This example shows extrapolation for X axis only.  Y position (spriteScreenY is assumed to be valid)
            extrapolatedPosX = spriteGridX+(SpriteXVelocity*dt)*extrapolation;
            spriteScreenPosX = extrapolationPosX * screenWidth;

            drawSprite(spriteScreenX, spriteScreenY);           


        }

Editar

Como mencioné anteriormente, he intentado hacer una copia de las coordenadas del sprite específicamente para dibujar ... esto tiene sus propios problemas.

En primer lugar, independientemente de la copia, cuando el sprite se está moviendo, es súper suave, cuando se detiene, se tambalea ligeramente hacia la izquierda / derecha, ya que todavía extrapola su posición en función del tiempo. ¿Es este comportamiento normal y podemos "apagarlo" cuando el sprite se detiene?

Intenté tener banderas para izquierda / derecha y solo extrapolar si alguno de estos está habilitado. También he intentado copiar las últimas y actuales posiciones para ver si hay alguna diferencia. Sin embargo, en lo que respecta a la colisión, estos no ayudan.

Si el usuario presiona decir, el botón derecho y el sprite se mueve hacia la derecha, cuando golpea una pared, si el usuario continúa presionando el botón derecho, el sprite seguirá animando a la derecha, mientras se detiene por la pared ( por lo tanto, en realidad no se mueve), sin embargo, debido a que la bandera derecha todavía está configurada y también porque la rutina de colisión está constantemente moviendo el sprite fuera de la pared, todavía le parece al código (no al jugador) que el sprite todavía se está moviendo, y por lo tanto La extrapolación continúa. Entonces, lo que el jugador vería es el sprite 'estático' (sí, está animando, pero en realidad no se mueve por la pantalla), y de vez en cuando se sacude violentamente cuando la extrapolación intenta hacer su cosa ..... .. Espero que esto ayude

BungleBonce
fuente
1
Esto requerirá algo de digestión para comprenderlo completamente ('interpolación' aparentemente tiene una docena de significados diferentes y a primera vista no está del todo claro lo que quieres decir con esto), pero mi primer instinto es 'no deberías hacer nada para afectar tu posición del objeto en su rutina de renderizado '. Su renderizador debe dibujar su objeto en la posición dada del objeto, y manipular el objeto allí es una receta para problemas, ya que inherentemente combina el renderizador con la física del juego. En un mundo ideal, tu renderizador debería ser capaz de llevar punteros constantes a los objetos del juego.
Steven Stadnicki
Hola @StevenStadnicki, muchas gracias por tu comentario, hay una multitud de ejemplos que muestran un valor de interpolación que se pasa al renderizador, mira esto: mysecretroom.com/www/programming-and-software/… que es de donde adapté mi código. Tengo un conocimiento limitado de que esto interpola la posición entre la última y las próximas actualizaciones en función del tiempo transcurrido desde la última actualización. ¡Estoy de acuerdo en que es una pesadilla! Le agradecería si pudiera proponerme una alternativa con la que sea más fácil trabajar con mí. Saludos :-)
BungleBonce
66
Bueno, diré 'solo porque puedes encontrar código para algo no lo convierte en una mejor práctica'. :-) En este caso, sin embargo, sospecho que la página a la que ha vinculado utiliza el valor de interpolación para averiguar dónde mostrar sus objetos, pero en realidad no actualiza las posiciones de los objetos con ellos; también puede hacerlo, simplemente teniendo una posición específica de dibujo que se calcula en cada cuadro, pero manteniendo esa posición separada de la posición 'física' real del objeto.
Steven Stadnicki
Hola @StevenStadnicki, como se describe en mi pregunta (el párrafo que comienza "También he intentado hacer una copia"), en realidad ya he intentado usar una posición de 'dibujar solo' :-) ¿Puede proponer un método de interpolación donde yo ¿No necesitas hacer ajustes en la posición del sprite dentro de la rutina de renderizado? ¡Gracias!
BungleBonce
Al mirar su código, parece que está haciendo una extrapolación en lugar de una interpolación.
Durza007

Respuestas:

1

Todavía no puedo publicar un comentario, así que lo publicaré como respuesta.

Si entiendo el problema correctamente, es algo así:

  1. primero tienes una colisión
  2. entonces se corrige la posición del objeto (presumiblemente por la rutina de detección de colisión)
  3. la posición actualizada del objeto se envía a la función de representación
  4. la función de renderizado actualiza la ubicación del objeto mediante extrapolación
  5. la posición del objeto extrapolado ahora rompe la rutina de detección de colisión

Puedo pensar en 3 posibles soluciones. Los enumeraré en el orden que sea más deseable para menos en mi humilde opinión.

  1. Mueva la extrapolación fuera de la función de renderizado. Extrapola la posición del objeto y luego prueba una colisión.
  2. o si desea mantener la extrapolación en la función de representación, establezca una bandera para indicar que se ha producido una colisión. Haga que la detección de colisión corrija la posición del objeto como ya lo hace, pero antes de calcular el valor de extrapolación, compruebe primero la bandera de colisión. Como el objeto ya está donde debe estar, no hay necesidad de moverlo.
  3. La última posibilidad, que para mí es más una solución que una solución, sería compensar en exceso la detección de colisión. Después de una colisión, aleje el objeto de la pared, de modo que después de la extrapolación, el objeto vuelva a la pared.

Código de ejemplo para el n. ° 2.

if (collisionHasOccured)
    extrpolation = 0.0f;
else
    extrapolation = (float)(System.currentTimeMillis() + skipTicks - nextGameTick) / (float)skipTicks;

Creo que el n. ° 2 probablemente sería el más rápido y fácil de poner en marcha, aunque el n. ° 1 parece ser una solución más precisa desde el punto de vista lógico. Dependiendo de cómo maneje su solución de tiempo delta # 1, un gran delta podría dividirlo de la misma manera, en cuyo caso es posible que tenga que usar tanto el # 1 como el # 2 juntos.

EDITAR: no entendí su código anteriormente. Los bucles están diseñados para renderizarse lo más rápido posible y actualizarse en un intervalo establecido. Es por eso que estaría interpolando la posición del sprite, para manejar el caso en el que está dibujando más que actualizando. Sin embargo, si el bucle se queda atrás, sondea la actualización hasta que te atrape o saltes el número máximo de dibujos de cuadros.

Dicho esto, el único problema es que el objeto se mueve después de una colisión. Si hay una colisión, el objeto debe dejar de moverse en esa dirección. Por lo tanto, si hay una colisión, establezca su velocidad en 0. Esto debería evitar que la función de procesamiento mueva más el objeto.

Aholio
fuente
Hola @Aholio, he probado la opción 2 y funciona, pero causa algunos problemas técnicos, tal vez lo hice mal, lo revisaré. Sin embargo, estoy muy interesado en la opción 1, aunque no puedo encontrar información sobre cómo hacerlo. ¿Cómo puedo extrapolar decir en mi rutina lógica? Por cierto, mi delta es fijo, por lo que es 1/60 o 0.01667 (más bien esta es la cifra que uso para integrar, aunque la cantidad de tiempo que toma cada iteración de mi juego obviamente puede variar dependiendo de lo que está sucediendo, pero nunca debería tomar más de 0.01667 ) así que cualquier ayuda sería genial, gracias.
BungleBonce
Quizás no entiendo completamente lo que estás haciendo. Algo que me parece un poco extraño es que no solo estás haciendo movimientos de posición en tu función de dibujar; También estás haciendo cálculos de tiempo. ¿Se hace esto para cada objeto? El orden correcto debe ser: cálculo del tiempo realizado en el código del bucle del juego. El bucle del juego pasa el delta a una función de actualización (dt). Actualizar (dt) actualizará todos los objetos del juego. Debe manejar cualquier movimiento, extrapolación y detección de colisión. Después de que update () regrese al bucle del juego, llama a una función render () que dibuja todos los objetos del juego recién actualizados.
Aholio
Hmmmmm en este momento mi función de actualización hace todo. El movimiento (me refiero a movimiento calcula la nueva posición de los sprites; esto se calcula a partir de mi tiempo delta). Lo único que estoy haciendo en mi función de renderizado (aparte de dibujar) es extrapolar la posición 'nueva', pero, por supuesto, esto usa tiempo, ya que tiene que tener en cuenta el tiempo desde la última actualización lógica hasta la llamada de renderizado. Esa es mi comprensión de todos modos :-) ¿Cómo harías esto? No entiendo cómo usar la extrapolación solo dentro de la actualización lógica. ¡Gracias!
BungleBonce
Actualicé mi respuesta. Espero que ayude
Aholio
Gracias, vea en mi Q original "cuando se detiene, se tambalea ligeramente hacia la izquierda / derecha", así que incluso si detengo mi sprite, la extrapolación aún mantiene al sprite 'moviéndose' (tambaleándose), esto sucede porque no estoy usando el las posiciones antiguas y actuales del sprite para resolver mi extrapolación, por lo tanto, incluso si el sprite es estático, todavía tiene este efecto de bamboleo basado en el valor de 'extrapolación' que se resuelve en cada iteración de cuadro. Incluso he intentado decir 'si las posiciones antiguas y actuales son las mismas, entonces no extrapoles' pero esto todavía no parece funcionar, ¡volveré y echaré otro vistazo!
BungleBonce
0

Parece que necesita separar por completo la representación y la actualización de la física. Por lo general, la simulación subyacente se ejecutará en pasos de tiempo discretos, y la frecuencia nunca cambiará. Por ejemplo, podría simular el movimiento de su bola cada 1/60 de segundo, y eso es todo.

Para permitir una velocidad de fotogramas variable, el código de representación debe operar en una frecuencia variable, pero cualquier simulación debe realizarse en un intervalo de tiempo fijo. Esto permite que los gráficos lean la memoria de la simulación como de solo lectura y le permite configurar la interpolación en lugar de la extrapolación.

Dado que la extrapolación está tratando de predecir dónde estarán los valores futuros, los cambios repentinos en el movimiento pueden generar enormes errores de extrapolación. En su lugar, es mejor renderizar su escena sobre un cuadro detrás de la simulación e interpolar entre posiciones discretas conocidas.

Si desea ver algunos detalles de la implementación, ya he escrito una breve sección sobre este tema en un artículo aquí . Consulte la sección llamada "Timestepping".

Aquí está el código psuedo importante del artículo:

const float fps = 100
const float dt = 1 / fps
float accumulator = 0

// In units seconds
float frameStart = GetCurrentTime( )

// main loop
while(true)
  const float currentTime = GetCurrentTime( )

  // Store the time elapsed since the last frame began
  accumulator += currentTime - frameStart( )

  // Record the starting of this frame
  frameStart = currentTime

  // Avoid spiral of death and clamp dt, thus clamping
  // how many times the UpdatePhysics can be called in
  // a single game loop.
  if(accumulator > 0.2f)
    accumulator = 0.2f

  while(accumulator > dt)
    UpdatePhysics( dt )
    accumulator -= dt

  const float alpha = accumulator / dt;

  RenderGame( alpha )

void RenderGame( float alpha )
  for shape in game do
    // calculate an interpolated transform for rendering
    Transform i = shape.previous * alpha + shape.current * (1.0f - alpha)
    shape.previous = shape.current
    shape.Render( i )

La RenderGamefunción es de mayor interés. La idea es utilizar la interpolación entre posiciones de simulación discretas. El código de representación puede hacer sus propias copias de los datos de solo lectura de la simulación y utilizar un valor interpolado temporal para representar. ¡Esto le dará un movimiento muy suave sin problemas tontos como lo que parece estar teniendo!

RandyGaul
fuente