Ответ 1
Я боролся с одной и той же проблемой в течение нескольких дней. Цикл выглядит гладко, без привязки к времени в Android, но как только он включает в себя любой тип "синхронизации времени", внешние факторы из управления разработкой android вводят серьезные разрывы в конечный результат.
В основном, эти факторы:
- eglSwapInterval не реализован в Android, поэтому трудно узнать момент, когда аппаратное обеспечение отображает финальную ничью на экране (синхронизация аппаратного экрана).
- Thread.sleep не является точным. Поток может спать больше или меньше запрошенного.
- SystemClock.uptimeMillis() System.nanoTime(), System.currentTimeMillis() и другое измерение, связанное с измерением, неточно (точное).
Проблема не зависит от технологии рисования (рисунок, openGL 1.0/1.1 и 2.0) и метода игрового цикла (шаг фиксированного времени, интерполяция, шаг изменения времени). Как и вы, я пытался использовать Thread.sleep, сумасшедшие интерполяции, таймеры и т.д. Не имеет значения, что вы будете делать, мы не контролируем эти факторы.
В соответствии с множеством Q & A на этом сайте основными правилами для создания плавных непрерывных анимаций являются:
- Уменьшите как минимум GC, удалив все запросы динамической памяти.
- Рендеринг кадров так быстро, что аппаратное обеспечение может обрабатывать их (от 40 до 60 кадров в секунду в большинстве устройств Android).
- Используйте фиксированные шаги времени с шагами интерполяции или изменения времени.
- Оптимизировать физику обновления и рисовать процедуры, которые будут выполняться в относительном постоянном времени без изменения высокого пика.
Конечно, вы сделали много предыдущей работы, прежде чем опубликовать этот вопрос, оптимизируя ваши updateGame() и drawGame() (без заметного GC и относительного постоянного времени выполнения), чтобы получить гладкую анимацию в вашем основном цикле, поскольку вы упомянуть: "простой и абсолютный гладкость".
В вашем конкретном случае с переменной stepTime и без особых требований, чтобы быть в идеальной синхронизации с событиями realTime (например, музыкой), решение прост: "сгладьте шаг" Временная переменная ".
Решение работает с другими схемами игровых циклов (фиксированный временной шаг с переменным рендерингом) и легко переносит концепцию (сглаживает объем смещения, создаваемый updateGame и часы реального времени на нескольких кадрах.)
// 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.
Для схемы с фиксированным временным шагом может быть реализовано промежуточное updateGame для улучшения результатов:
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);
}
Протестировано с рисунком canvas и openGlES10 со следующими устройствами: SG SII (57 FPS), SG Примечание (57 FPS), вкладка SG (60 FPS), ненавязчивый медленный эмулятор Android 2.3 (43 FPS), работающий в Windows XP (8 FPS). Испытательная платформа рисует около 45 объектов + 1 огромный фон (текстура из исходного изображения 70MP), перемещаясь по пути, указанному в реальных физических параметрах (км/ч и G), без всплесков или щелчков между несколькими устройствами (ну, 8 FPS на эмуляторе не выглядит хорошо, но его поток с постоянной скоростью, как ожидалось)
Проверить Графики того, как андроид сообщает время. В некоторых случаях Android сообщает о большом дельта-времени и только в следующем цикле он меньше среднего, что означает смещение при чтении значения realTime.
более подробно:
Как ограничить частоту кадров при использовании Android GLSurfaceView.RENDERMODE_CONTINUUUSLY?
System.currentTimeMillis vs System.nanoTime
Действительно ли метод System.currentTimeMillis() действительно возвращает текущее время?