uptodown puzzledom para loop juegos juego guerra gratis descargar android opengl-es thread-sleep game-loop

puzzledom - Bucle de juego android vs actualización en el hilo de representación



infinity tv apk gratis (2)

Estoy haciendo un juego para Android y actualmente no estoy obteniendo el rendimiento que me gustaría. Tengo un bucle de juego en su propio hilo que actualiza la posición de un objeto. El hilo de renderizado atravesará estos objetos y los dibujará. El comportamiento actual es lo que parece un movimiento entrecortado / irregular. Lo que no puedo explicar es que antes de poner la lógica de actualización en su propio hilo, lo tenía en el método onDrawFrame, justo antes de las llamadas gl. En ese caso, la animación fue perfectamente fluida, solo se vuelve entrecortada / irregular específicamente cuando trato de acelerar mi ciclo de actualización a través de Thread.sleep. Incluso cuando permito que el hilo de actualización se vuelva loco (sin reposo), la animación es fluida, solo cuando está involucrado Thread.sleep afecta la calidad de la animación.

Creé un proyecto de esqueleto para ver si podía volver a crear el problema, a continuación se muestran el ciclo de actualización y el método onDrawFrame en el renderizador: Actualizar Loop

@Override public void run() { while(gameOn) { long currentRun = SystemClock.uptimeMillis(); if(lastRun == 0) { lastRun = currentRun - 16; } long delta = currentRun - lastRun; lastRun = currentRun; posY += moveY*delta/20.0; GlobalObjects.ypos = posY; long rightNow = SystemClock.uptimeMillis(); if(rightNow - currentRun < 16) { try { Thread.sleep(16 - (rightNow - currentRun)); } catch (InterruptedException e) { e.printStackTrace(); } } } }

Y aquí está mi método onDrawFrame :

@Override public void onDrawFrame(GL10 gl) { gl.glClearColor(1f, 1f, 0, 0); gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); gl.glLoadIdentity(); gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]); gl.glTranslatef(transX, GlobalObjects.ypos, transZ); //gl.glRotatef(45, 0, 0, 1); //gl.glColor4f(0, 1, 0, 0); gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer); gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, uvBuffer); gl.glDrawElements(GL10.GL_TRIANGLES, drawOrder.length, GL10.GL_UNSIGNED_SHORT, indiceBuffer); gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY); }

Miré a través de la fuente de réplica de la isla y él está haciendo su lógica de actualización en un hilo separado, y también lo estrangula con Thread.sleep, pero su juego se ve muy bien. ¿Alguien tiene alguna idea o alguien ha experimentado lo que estoy describiendo?

--- EDITAR: 1/25/13 ---
He tenido algo de tiempo para pensar y he suavizado considerablemente el motor de este juego. Cómo logré esto podría ser una blasfemia o un insulto para los programadores de juegos reales, así que no dude en corregir cualquiera de estas ideas.

La idea básica es mantener un patrón de actualización, dibujar ... actualizar, dibujar ... mientras se mantiene el delta de tiempo relativamente igual (a menudo fuera de tu control). Mi primer curso de acción fue sincronizar mi procesador de tal manera que solo se dibujara luego de recibir la notificación de que podía hacerlo. Esto se ve así:

public void onDrawFrame(GL10 gl10) { synchronized(drawLock) { while(!GlobalGameObjects.getInstance().isUpdateHappened()) { try { Log.d("test1", "draw locking"); drawLock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } }

Cuando termino mi lógica de actualización, llamo a drawLock.notify (), liberando el hilo de representación para dibujar lo que acabo de actualizar. El propósito de esto es ayudar a establecer el patrón de actualización, dibujar ... actualizar, dibujar ... etc.

Una vez que implementé eso, fue considerablemente más suave, aunque todavía estaba experimentando saltos ocasionales en movimiento. Después de algunas pruebas, vi que tenía múltiples actualizaciones ocurriendo entre las llamadas de ondrawFrame. Esto provocaba que un marco mostrara el resultado de dos (o más) actualizaciones, un salto más grande que el normal.

Lo que hice para resolver esto fue limitar el delta del tiempo a algún valor, digamos 18 ms, entre dos llamadas a DrawFrame y almacenar el tiempo extra en un resto. Este resto se distribuiría a los deltas de tiempo subsiguientes en las próximas actualizaciones si pudieran manejarlo. Esta idea previene todos los saltos largos repentinos, esencialmente suavizando un pico de tiempo sobre múltiples cuadros. Hacer esto me dio excelentes resultados.

La desventaja de este enfoque es que, durante un tiempo, la posición de los objetos no será precisa con el tiempo y, de hecho, se acelerará para compensar esa diferencia. Pero es más suave y el cambio de velocidad no es muy notable.

Finalmente, decidí reescribir mi motor con las dos ideas anteriores en mente, en lugar de reparar el motor que había fabricado originalmente. Hice algunas optimizaciones para la sincronización del hilo que tal vez alguien podría comentar.

Mis hilos actuales interactúan así:
- El hilo de actualización actualiza el búfer actual (sistema de búfer doble para actualizar y dibujar simultáneamente) y luego le dará a este búfer al representador si se dibujó el cuadro anterior.
-Si el cuadro anterior aún no ha dibujado, o está dibujando, el hilo de actualización esperará hasta que el hilo de renderizado le notifique que ha dibujado.
- El hilo de procesamiento espera hasta que el hilo de actualización le notifique que se ha producido una actualización.
-Cuando dibuja el hilo de renderizado , establece una "última variable dibujada" que indica cuál de los dos búferes dibujó por última vez y también notifica al hilo de actualización si estaba esperando que se dibujara el búfer anterior.

Eso puede ser un poco intrincado, pero lo que está haciendo es tener en cuenta las ventajas del multihilo, en el sentido de que puede realizar la actualización para el cuadro n mientras que el cuadro n-1 está dibujando y también evita múltiples iteraciones de actualización por cuadro si el procesador toma una largo tiempo. Para explicar mejor, este escenario de actualización múltiple se maneja mediante el bloqueo del hilo de actualización si detecta que el último búfer Drawn es igual al que se acaba de actualizar. Si son iguales, esto indica al hilo de actualización que el marco anterior aún no se ha dibujado.

Hasta ahora estoy obteniendo buenos resultados. Avíseme si alguien tiene algún comentario, estaría feliz de escuchar sus pensamientos sobre todo lo que estoy haciendo, correcto o incorrecto.

Gracias


Una parte del problema puede deberse al hecho de que Thread.sleep () no es exacto. Intenta investigar cuál es el momento real del sueño.

Lo más importante que debe hacer que sus animaciones sean fluidas es que debe calcular algún factor de interpolación, llamarlo alfa, que interpola linealmente sus animaciones en llamadas de subprocesos de representación consecutivas entre dos llamadas de subprocesos de actualización de animación consecutivas. En otras palabras, si el intervalo de actualización es alto en comparación con la velocidad de fotogramas, no interpolar los pasos de actualización de la animación es como si estuvieras renderizando a una tasa de fotogramas de intervalo de actualización.

EDITAR: como ejemplo, así es como PlayN lo hace:

@Override public void run() { // The thread can be stopped between runs. if (!running.get()) return; int now = time(); float delta = now - lastTime; if (delta > MAX_DELTA) delta = MAX_DELTA; lastTime = now; if (updateRate == 0) { platform.update(delta); accum = 0; } else { accum += delta; while (accum >= updateRate) { platform.update(updateRate); accum -= updateRate; } } platform.graphics().paint(platform.game, (updateRate == 0) ? 0 : accum / updateRate); if (LOG_FPS) { totalTime += delta / 1000; framesPainted++; if (totalTime > 1) { log().info("FPS: " + framesPainted / totalTime); totalTime = framesPainted = 0; } } }


(La respuesta de Blackhex planteó algunos puntos interesantes, pero no puedo incluir todo esto en un comentario).

Tener dos hilos operando de manera asíncrona está destinado a generar problemas como este. Mírelo de esta manera: el evento que impulsa la animación es la señal de hardware "vsync", es decir, el punto en el que el compositor de superficie de Android proporciona una nueva pantalla llena de datos para el hardware de la pantalla. Desea tener un nuevo marco de datos cada vez que llegue vsync. Si no tienes nuevos datos, el juego se ve entrecortado. Si generó 3 cuadros de datos en ese período, se ignorarán dos y perderá la duración de la batería.

(Hacer funcionar una CPU por completo también puede provocar que el dispositivo se caliente, lo que puede provocar un estrangulamiento térmico, lo que ralentiza todo el sistema ... y puede hacer que la animación entrecortada).

La forma más fácil de mantenerse sincronizado con la pantalla es realizar todas las actualizaciones de estado en onDrawFrame() . Si a veces lleva más de un fotograma realizar las actualizaciones de estado y renderizar el fotograma, entonces se verá mal y tendrá que modificar su enfoque. Simplemente cambiando todas las actualizaciones de estado del juego a un segundo núcleo no va a ayudar tanto como a usted le gustaría: si el núcleo # 1 es el subproceso y el núcleo # 2 es el hilo de actualización del estado del juego, entonces el núcleo # 1 va a ir para permanecer inactivo mientras que el núcleo # 2 actualiza el estado, luego de lo cual el núcleo # 1 se reanudará para hacer el renderizado real mientras que el núcleo # 2 permanece inactivo, y tomará el mismo tiempo. Para aumentar realmente la cantidad de cálculos que puede hacer por cuadro, necesitará tener dos (o más) núcleos trabajando simultáneamente, lo que plantea algunos problemas de sincronización interesantes según cómo defina su división del trabajo (vea http: // developer). .android.com / training / articles / smp.html si quieres ir por ese camino).

Intentar utilizar Thread.sleep() para administrar la velocidad de cuadros generalmente termina mal. No puede saber cuánto dura el período entre vsync, o cuánto tiempo tardará en llegar el próximo. Es diferente para cada dispositivo, y en algunos dispositivos puede ser variable. Básicamente terminas con dos relojes - vsync y sleep - pegando uno contra el otro, y el resultado es una animación entrecortada. Además de eso, Thread.sleep() no ofrece ninguna garantía específica sobre la precisión o la duración mínima del sueño.

Realmente no he GameRenderer.onDrawFrame() fuentes de Replica Island, pero en GameRenderer.onDrawFrame() se puede ver la interacción entre el hilo de estado de su juego (que crea una lista de objetos para dibujar) y el hilo de renderizador GL (que simplemente dibuja la lista) ) En su modelo, el estado del juego solo se actualiza según sea necesario, y si nada ha cambiado, simplemente vuelve a dibujar la lista de sorteo anterior. Este modelo funciona bien para un juego impulsado por eventos, es decir, donde los contenidos en la pantalla se actualizan cuando ocurre algo (presionas una tecla, un temporizador dispara, etc.). Cuando ocurre un evento, pueden hacer una actualización de estado mínima y ajustar la lista de sorteo según corresponda.

Visto de otra manera, el hilo de renderizado y el estado del juego funcionan en paralelo porque no están rígidamente unidos. El estado del juego simplemente se ejecuta actualizando las cosas según sea necesario, y el subproceso de representación lo bloquea cada vsync y dibuja lo que encuentra. Mientras ninguno de los lados mantenga algo encerrado por mucho tiempo, no interfieren visiblemente. El único estado compartido interesante es la lista de sorteos, protegida con un mutex, por lo que sus problemas multinúcleo se minimizan.

Para Android Breakout ( http://code.google.com/p/android-breakout/ ), el juego tiene una pelota rebotando, en movimiento continuo. Ahí queremos actualizar nuestro estado con la frecuencia que la pantalla nos permite, por lo que controlamos el cambio de estado de vsync, usando un delta de tiempo del fotograma anterior para determinar qué tan avanzado ha avanzado. El cálculo por cuadro es pequeño, y la representación es bastante trivial para un dispositivo GL moderno, por lo que todo se ajusta fácilmente en 1/60 de segundo. Si la pantalla se actualizara mucho más rápido (240 Hz), ocasionalmente podríamos perder fotogramas (una vez más, es poco probable que se note) y estaríamos consumiendo 4 veces más CPU en las actualizaciones de fotogramas (lo cual es desafortunado).

Si por alguna razón uno de estos juegos se perdió una vsync, el jugador puede o no darse cuenta. El estado avanza por el tiempo transcurrido, no una noción preestablecida de un "cuadro" de duración fija, así que, por ejemplo, la pelota moverá 1 unidad en cada uno de los dos cuadros consecutivos o 2 unidades en un cuadro. Dependiendo de la velocidad de cuadros y la capacidad de respuesta de la pantalla, esto puede no ser visible. (Este es un problema de diseño clave, y uno que puede meterse con tu cabeza si visualizas el estado de tu juego en términos de "tics").

Ambos son enfoques válidos. La clave es dibujar el estado actual siempre que se onDrawFrame y actualizar el estado con la onDrawFrame frecuencia posible.

Nota para cualquier otra persona que lea esto: no use System.currentTimeMillis() . El ejemplo en la pregunta usa SystemClock.uptimeMillis() , que se basa en el reloj monotónico en lugar del reloj de pared. Eso, o System.nanoTime() , son mejores opciones. (Estoy en una pequeña cruzada contra currentTimeMillis , que en un dispositivo móvil podría repentinamente saltar hacia adelante o hacia atrás).

Actualización: escribí una respuesta aún más larga a una pregunta similar.

Actualización 2: escribí una respuesta aún más larga sobre el problema general (consulte el Apéndice A).