Przez kilka dni walczyłem z tym samym problemem. Pętla wygląda gładko bez żadnego odniesienia do czasu Android, ale jak tylko zawiera ona dowolny rodzaj "synchronizacji czasu", czynniki zewnętrzne poza kontrolą rozwoju Androida wprowadzają poważne nieciągłości do końcowego wyniku.
Zasadniczo, te czynniki są:
- eglSwapInterval nie jest realizowany w Androidzie, więc trudno jest znać moment gdy sprzęt narazić końcowy remis na ekranie (sprzęt ekranem Sync)
- wątek. sen nie jest precyzyjny. Wątek może spać mniej lub więcej niż wymagany.
- SystemClock.uptimeMillis() System.nanoTime(), System.currentTimeMillis() i inne pomiary związane z czasem nie są dokładne (dokładne).
Problem jest niezależny od technologii rysowania (rysunek, OpenGL 1.0/1.1 i 2.0) oraz metody pętli gry (ustalony krok czasowy, interpolacja, etap zmiennej fazy). Tak jak ty, próbowałem Thread.sleep, szalonych interpolacji, timerów itd. Nie ma znaczenia, co zrobisz, nie mamy kontroli nad tymi czynnikami.
Zgodnie z wielu Q & A na tej stronie, to podstawowe zasady, aby produkować płynne animacje są:
- Zmniejszyć przy minimum GC usuwając wszystkie żądania pamięci dynamicznej.
- Renderuj klatki tak szybko, jak sprzęt może je przetworzyć (od 40 do 60 fps jest w porządku na większości urządzeń z Androidem).
- Używaj stałych stopni czasu z interpolacją lub zmiennymi krokami czasowymi.
- Optymalizuj fizykę aktualizacji i procedury rysowania, które mają być wykonywane w względnym stałym czasie bez wariancji wysokich pików.
Z pewnością wykonałeś wiele wcześniejszych prac przed opublikowaniem tego pytania, optymalizując metodę updateGame() i drawGame() (bez znaczącej wartości GC i względnego stałego czasu wykonania), aby uzyskać płynną animację w głównym pętla jak wspominasz: "prosta i absolutnie gładka".
Twój szczególny przypadek ze zmiennym czasem kroku i bez specjalnych wymagań, aby być w doskonałej synchronizacji z wydarzeniami w czasie rzeczywistym (jak muzyka), rozwiązanie jest proste: "wygładź zmienną kroku czasu".
Roztwór współpracuje z innymi systemami pętli gry (stałe taktu o zmiennej renderowania) i jest łatwe do przebicia Pojęcie (gładkich wielkość przemieszczenia wytwarzanego przez updateGame i zegar czasu rzeczywistego na kilku ramkach.)
// avoid GC in your threads. declare nonprimitive variables out of onDraw
float smoothedDeltaRealTime_ms=17.5f; // initial value, Optionally you can save the new computed value (will change with each hardware) in Preferences to optimize the first drawing frames
float movAverageDeltaTime_ms=smoothedDeltaRealTime_ms; // mov Average start with default value
long lastRealTimeMeasurement_ms; // temporal storage for last time measurement
// smooth constant elements to play with
static final float movAveragePeriod=40; // #frames involved in average calc (suggested values 5-100)
static final float smoothFactor=0.1f; // adjusting ratio (suggested values 0.01-0.5)
// sample with opengl. Works with canvas drawing: public void OnDraw(Canvas c)
public void onDrawFrame(GL10 gl){
updateGame(gl, smoothedDeltaRealTime_ms); // divide 1000 if your UpdateGame routine is waiting seconds instead mili-seconds.
drawGame(gl);
// Moving average calc
long currTimePick_ms=SystemClock.uptimeMillis();
float realTimeElapsed_ms;
if (lastRealTimeMeasurement_ms>0){
realTimeElapsed_ms=(currTimePick_ms - lastRealTimeMeasurement_ms);
} else {
realTimeElapsed_ms=smoothedDeltaRealTime_ms; // just the first time
}
movAverageDeltaTime_ms=(realTimeElapsed_ms + movAverageDeltaTime_ms*(movAveragePeriod-1))/movAveragePeriod;
// Calc a better aproximation for smooth stepTime
smoothedDeltaRealTime_ms=smoothedDeltaRealTime_ms +(movAverageDeltaTime_ms - smoothedDeltaRealTime_ms)* smoothFactor;
lastRealTimeMeasurement_ms=currTimePick_ms;
}
// Optional: check if the smoothedDeltaRealTIme_ms is too different from original and save it in Permanent preferences for further use.
na schemacie kroku stałej czasowej intermetiate updateGame mogą być realizowane w celu poprawy wyników:
float totalVirtualRealTime_ms=0;
float speedAdjustments_ms=0; // to introduce a virtual Time for the animation (reduce or increase animation speed)
float totalAnimationTime_ms=0;
float fixedStepAnimation_ms=20; // 20ms for a 50FPS descriptive animation
int currVirtualAnimationFrame=0; // useful if the updateGameFixedStep routine ask for a frame number
private void updateGame(){
totalVirtualRealTime_ms+=smoothedDeltaRealTime_ms + speedAdjustments_ms;
while (totalVirtualRealTime_ms> totalAnimationTime_ms){
totalAnimationTime_ms+=fixedStepAnimation_ms;
currVirtualAnimationFrame++;
// original updateGame with fixed step
updateGameFixedStep(currVirtualAnimationFrame);
}
float interpolationRatio=(totalAnimationTime_ms-totalVirtualRealTime_ms)/fixedStepAnimation_ms;
Interpolation(interpolationRatio);
}
Testowane z płótna i openGlES10 rysunek z następującymi urządzeniami: SII SG (57 fps), SG Note (57 FPS), Kartę SG (60 FPS), niemarkowy runni z Androidem 2.3 (43 FPS) ng na Windows XP (8 FPS). Platforma testowa rysuje około 45 obiektów + 1 ogromne tło (tekstura z obrazu źródłowego 70MP) poruszającego się po ścieżce określonej w rzeczywistych parametrach fizycznych (km/h i G), bez skoków lub drgań między kilkoma urządzeniami (cóż, 8 FPS na emulatorze nie wygląda dobrze, ale jego przepływ odbywa się ze stałą prędkością zgodnie z oczekiwaniami)
Sprawdź wykresy pokazujące, jak android raportuje czas. Czasami Android zgłasza duży czas delta, a kolejna pętla jest mała od średniej, co oznacza przesunięcie odczytu wartości czasu rzeczywistego.
bardziej szczegółowo:
How to limit framerate when using Android's GLSurfaceView.RENDERMODE_CONTINUOUSLY?
System.currentTimeMillis vs System.nanoTime
Does the method System.currentTimeMillis() really return the current time?
'ruchu przez akcelerator' akcelerometry daje dane z powrotem Jumpy. Czy to może być twój problem? Spróbuj wygładzić ruch, wykonując coś w stylu "someLoc + = (newLoc - someLoc) * .1f;" – zezba9000
witam, jeśli spróbuję wyłączyć akcelerometr i tylko proste obrazy ruchome o stałej wartości to sporadycznie pstrykam też (próbuję to z RENDERMODE_WHEN_DIRTY teraz) – Commanche