За последние 8 месяцев я задавал несколько похожих вопросов без особой радости, поэтому я собираюсь сделать вопрос более общим.
У меня есть игра для Android, которая является OpenGL ES 2.0. внутри него у меня есть следующий цикл игры:
Мой цикл работает по принципу фиксированного временного шага (dt = 1 / ticksPerSecond )
loops=0;
while(System.currentTimeMillis() > nextGameTick && loops < maxFrameskip){
updateLogic(dt);
nextGameTick+=skipTicks;
timeCorrection += (1000d/ticksPerSecond) % 1;
nextGameTick+=timeCorrection;
timeCorrection %=1;
loops++;
}
render();
Моя интеграция работает так:
sprite.posX+=sprite.xVel*dt;
sprite.posXDrawAt=sprite.posX*width;
Теперь все работает так, как мне бы хотелось. Я могу указать, что я хотел бы, чтобы объект переместился на определенное расстояние (скажем, ширину экрана) за 2,5 секунды, и это будет сделано именно так. Также из-за пропуска кадров, который я допускаю в своем игровом цикле, я могу сделать это практически на любом устройстве, и это всегда будет занимать 2,5 секунды.
проблема
Однако проблема заключается в том, что когда рамка рендеринга пропускается, графика заикается. Это очень раздражает. Если я уберу возможность пропустить кадры, то все будет гладко, как вам нравится, но будет работать на разных скоростях на разных устройствах. Так что это не вариант.
Я все еще не уверен, почему кадр пропускает, но я хотел бы отметить, что это не имеет ничего общего с низкой производительностью , я вернул код обратно к 1 крошечному спрайту и никакой логике (кроме логики, необходимой для переместить спрайт) и я все равно получаю пропущенные кадры. И это на планшете Google Nexus 10 (и, как уже упоминалось выше, мне нужно пропустить кадр, чтобы скорость в любом случае оставалась неизменной).
Итак, единственный другой вариант, который у меня есть, - это использовать интерполяцию (или экстраполяцию), я прочитал каждую статью, но ни одна из них не помогла мне понять, как она работает, и все мои попытки реализации потерпели неудачу.
Используя один метод, я смог заставить вещи двигаться гладко, но это было неработоспособно, потому что это испортило мое столкновение. Я могу предвидеть ту же проблему с любым подобным методом, потому что интерполяция передается (и действует внутри) в метод рендеринга - во время рендеринга. Таким образом, если Collision исправляет положение (персонаж теперь стоит прямо возле стены), то средство визуализации может изменить свое положение и нарисовать его в стене.
Так что я действительно запутался. Люди говорят, что вы никогда не должны изменять положение объекта в методе рендеринга, но все примеры в Интернете показывают это.
Поэтому я прошу толчок в правильном направлении, пожалуйста, не делайте ссылки на популярные статьи игрового цикла (deWitters, Fix your timetep и т. Д.), Поскольку я читал их несколько раз . Я не прошу никого писать мой код для меня. Просто объясните, пожалуйста, простыми словами, как на самом деле работает интерполяция с некоторыми примерами. Затем я пойду и попытаюсь интегрировать любые идеи в мой код и задам более конкретные вопросы, если это будет необходимо. (Я уверен, что это проблема, с которой многие люди борются).
редактировать
Некоторая дополнительная информация - переменные, используемые в игровом цикле.
private long nextGameTick = System.currentTimeMillis();
//loop counter
private int loops;
//Amount of frames that we will allow app to skip before logic is affected
private final int maxFrameskip = 5;
//Game updates per second
final int ticksPerSecond = 60;
//Amount of time each update should take
private final int skipTicks = (1000 / ticksPerSecond);
float dt = 1f/ticksPerSecond;
private double timeCorrection;
источник
Ответы:
Есть две вещи, которые очень важны для придания движению гладкости, во-первых, очевидно, что то, что вы отображаете, должно соответствовать ожидаемому состоянию во время представления кадра пользователю, во-вторых, вам нужно представить кадры пользователю. с относительно фиксированным интервалом. Представление кадра в момент времени T + 10 мс, затем другого в момент времени T + 30 мс, а затем другого в момент времени T + 40 мс, покажется пользователю, что он судит, даже если то, что фактически показано для тех времен, является правильным в соответствии с моделированием.
В вашем основном цикле, похоже, отсутствует какой-либо механизм стробирования, который бы гарантировал, что вы выполняете рендеринг только через регулярные интервалы. Так что иногда вы можете делать 3 обновления между рендерами, иногда вы можете делать 4. В основном ваш цикл будет отображаться как можно чаще, как только вы смоделируете достаточно времени, чтобы перенести состояние симуляции перед текущим временем, вы будете затем сделать это состояние. Но любая изменчивость в том, сколько времени потребуется для обновления или рендеринга, а также интервал между кадрами также будет меняться. У вас есть фиксированный временной шаг для вашего моделирования, но переменный временной шаг для вашего рендеринга.
То, что вам, вероятно, нужно, это ожидание непосредственно перед рендерингом, которое гарантирует, что вы начнете рендеринг только в начале интервала рендеринга. В идеале это должно быть адаптивно: если вам потребовалось слишком много времени для обновления / рендеринга, а начало интервала уже прошло, вы должны выполнить рендеринг немедленно, но также увеличьте длину интервала, пока вы не сможете последовательно рендерить и обновлять, и при этом добираться до следующий рендер до окончания интервала. Если у вас есть много свободного времени, то вы можете медленно уменьшить интервал (то есть увеличить частоту кадров), чтобы снова ускорить рендеринг.
Но, и вот что важно, если вы не визуализируете кадр сразу после обнаружения того, что состояние симуляции было обновлено до «сейчас», то вы вводите временное алиасинг. Кадр, представляемый пользователю, представляется в немного неподходящее время, и это само по себе будет ощущаться как заикание.
Это причина "частичного временного шага", который вы увидите в статьях, которые вы прочитали. Это есть по уважительной причине, и это потому, что если вы не исправите свой физический временной шаг к некоторому фиксированному целому кратному своему фиксированному временному шагу рендеринга, вы просто не сможете представить кадры в нужное время. Вы заканчиваете тем, что либо представляете их слишком рано, либо слишком поздно. Единственный способ получить фиксированную скорость рендеринга и по-прежнему представить что-то физически правильное - это признать, что во время интервала рендеринга вы, скорее всего, окажетесь на полпути между двумя вашими фиксированными временными шагами физики. Но это не значит, что объекты изменяются во время рендеринга, только то, что рендеринг должен временно установить, где находятся объекты, чтобы он мог рендерить их где-то между тем, где они были до и где они находятся после обновления. Это важно - никогда не меняйте состояние мира для рендеринга, только обновления должны изменять состояние мира.
Итак, чтобы поместить его в цикл псевдокода, я думаю, вам нужно что-то вроде:
Для того, чтобы это работало, все обновляемые объекты должны сохранять знания о том, где они были раньше и где они сейчас, чтобы рендеринг мог использовать свои знания о том, где находится объект.
И давайте выложим временную шкалу в миллисекундах, говоря, что рендеринг занимает 3 мсек, обновление занимает 1 мс, ваш временной шаг обновления фиксируется на 5 мс, а ваш временной интервал рендеринга начинается (и остается) с 16 мс [60 Гц].
Здесь есть еще один нюанс, связанный с симуляцией слишком заблаговременно, то есть пользовательские входы могут быть проигнорированы, даже если они произошли до того, как кадр был фактически визуализирован, но не беспокойтесь об этом, пока не убедитесь, что цикл симулируется плавно.
источник
То, что все говорили тебе, правильно. Никогда не обновляйте позицию симуляции вашего спрайта в логике рендеринга.
Думайте об этом так, ваш спрайт имеет 2 позиции; где симуляция говорит, что он на момент последнего обновления симуляции, и где отображается спрайт. Это две совершенно разные координаты.
Спрайт отображается в его экстраполированном положении. Экстраполированное положение вычисляется для каждого кадра рендеринга, используется для рендеринга спрайта, а затем выбрасывается. Это все, что нужно сделать.
Помимо этого, у вас, кажется, есть хорошее понимание. Надеюсь это поможет.
источник