Я делаю андроид-игру, и в настоящее время я не получаю производительность, которую я бы хотел. У меня есть игровой цикл в своем потоке, который обновляет позицию объекта. Поток рендеринга будет перемещать эти объекты и рисовать их. Текущее поведение - это то, что кажется прерывистым/неравномерным движением. Я не могу объяснить, что до того, как я поместил логику обновления в свой собственный поток, я использовал его в методе onDrawFrame, прямо перед вызовом gl. В этом случае анимация была совершенно гладкой, она только становилась изменчивой/неравномерной, когда я пытаюсь затормозить цикл обновления через Thread.sleep. Даже когда я разрешаю потоку обновления перейти на "берсерк" (без сна), анимация плавная, только если задействован Thread.sleep, это влияет на качество анимации.
Я создал проект скелета, чтобы увидеть, могу ли я воссоздать проблему, ниже - цикл обновления и метод onDrawFrame в рендерере: Обновить петлю
@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();
}
}
}
}
И вот мой метод 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);
}
Я просмотрел источник острова реплики, и он делает свою логику обновления в отдельном потоке, а также дросселирует его с помощью Thread.sleep, но его игра выглядит очень гладко. Кто-нибудь есть какие-то идеи или кто-нибудь испытал то, что я описываю?
--- EDIT: 1/25/13 ---
У меня было время подумать и значительно сгладить этот игровой движок. Как мне удалось, это может быть кощунственным или оскорбительным для настоящих программистов, поэтому, пожалуйста, не стесняйтесь исправить любую из этих идей.
Основная идея состоит в том, чтобы сохранить шаблон обновления, рисовать... обновлять, рисовать... сохраняя при этом дельта времени относительно одного и того же (часто вне вашего контроля). Мой первый курс действий состоял в том, чтобы синхронизировать мой рендерер таким образом, чтобы он только рисовался после того, как его уведомили, что ему это разрешено. Это выглядит примерно так:
public void onDrawFrame(GL10 gl10) {
synchronized(drawLock)
{
while(!GlobalGameObjects.getInstance().isUpdateHappened())
{
try
{
Log.d("test1", "draw locking");
drawLock.wait();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
Когда я закончу свою логику обновления, я вызываю drawLock.notify(), освобождая поток визуализации, чтобы нарисовать то, что я только что обновил. Цель этого заключается в том, чтобы помочь установить шаблон обновления, рисовать... обновлять, рисовать... и т.д.
Как только я это осуществил, он был значительно более гладким, хотя я все еще испытывал случайные прыжки в движении. После некоторого тестирования я увидел, что у меня было несколько обновлений, возникающих между вызовами ondrawFrame. Это привело к тому, что один кадр отобразил результат двух (или более) обновлений, более крупный скачок, чем обычно.
То, что я сделал, чтобы решить эту проблему, заключалось в том, чтобы ограничить время дельта до некоторого значения, скажем, 18 мс, между двумя вызовами onDrawFrame и сохранить дополнительное время в остатке. Этот остаток будет распространен на последующие временные дельта в течение следующих нескольких обновлений, если они смогут его обработать. Эта идея предотвращает все внезапные длинные прыжки, по сути сглаживая всплеск времени по нескольким кадрам. Это дало мне большие результаты.
Недостатком этого подхода является то, что в течение небольшого времени позиция объектов будет неточной с течением времени и фактически ускорится, чтобы компенсировать эту разницу. Но это плавное и изменение скорости не очень заметно.
Наконец, я решил переписать свой движок с учетом двух вышеупомянутых идей, вместо того, чтобы исправлять двигатель, который я изначально сделал. Я сделал некоторые оптимизации для синхронизации потоков, которые, возможно, кто-то может прокомментировать.
Мои текущие потоки взаимодействуют следующим образом:
- Обновить поток обновляет текущий буфер (двойную буферную систему, чтобы обновлять и рисовать одновременно), и затем предоставит этот буфер визуализатору, если предыдущий кадр был нарисован.
-Если предыдущий кадр еще не рисовал или рисует, поток обновлений будет ждать, пока поток render не уведомит его, что он нарисовал.
- Показывать поток ожидает уведомления обновить поток, что произошло обновление.
-Когда червь render thread рисует, он устанавливает "последнюю обратную переменную", указывающую, какой из двух последних буферов он нарисовал, а также уведомляет поток обновлений, если он ожидал отвлечения предыдущего буфера.
Это может быть немного запутанным, но то, что это делает, позволяет использовать преимущества многопоточности, поскольку оно может выполнять обновление для фрейма n во время рисования кадра n-1, одновременно предотвращая несколько итераций обновления на каждый кадр, если средство визуализации занимает много времени. Для дальнейшего объяснения этот сценарий с несколькими обновлениями обрабатывается блокировкой потока обновлений, если он обнаруживает, что буфер lastDrawn равен тому, который был только что обновлен. Если они равны, это указывает на поток обновления, который ранее не был нарисован.
До сих пор я получаю хорошие результаты. Дайте мне знать, если у кого есть какие-либо комментарии, будем рады услышать ваши мысли о чем-либо, что я делаю, правильно или неправильно.
Спасибо