Я делаю игру для Android и в настоящее время не получаю желаемой производительности. У меня есть игровой цикл в собственном потоке, который обновляет позицию объекта. Поток рендеринга будет проходить по этим объектам и рисовать их. Текущее поведение похоже на прерывистое/неравномерное движение. Чего я не могу объяснить, так это того, что до того, как я поместил логику обновления в отдельный поток, она была у меня в методе 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: 25.01.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 и сохранить дополнительное время в остатке. Этот остаток будет распределен на последующие дельты времени в течение следующих нескольких обновлений, если они смогут с этим справиться. Эта идея предотвращает все внезапные длинные скачки, по существу сглаживая скачки времени в нескольких кадрах. Это дало мне отличные результаты.
Недостатком этого подхода является то, что в течение короткого времени положение объектов не будет точным со временем и фактически ускорится, чтобы компенсировать эту разницу. Но он более плавный и изменение скорости не очень заметно.
Наконец, я решил переписать свой движок с учетом двух вышеупомянутых идей, а не исправлять движок, который я изначально сделал. Я сделал некоторые оптимизации для синхронизации потоков, которые, возможно, кто-то может прокомментировать.
Мои текущие потоки взаимодействуют следующим образом:
-Поток обновления обновляет текущий буфер (двойная буферная система для одновременного обновления и отрисовки) и затем передает этот буфер средству визуализации, если предыдущий кадр был отрисован.
-Если предыдущий кадр еще не отрисовывался или отрисовывается, поток обновления будет ждать, пока поток рендеринга не уведомит его о том, что он отрисовался.
- Поток рендеринга ожидает, пока поток обновления не уведомит его о том, что обновление произошло.
— Когда поток рендеринга рисует, он устанавливает «последнюю нарисованную переменную». указывая, какой из двух буферов он рисовал последним, а также уведомляет поток обновления, если он ожидал отрисовки предыдущего буфера.
Это может быть немного запутанно, но то, что он делает, позволяет использовать преимущества многопоточности, поскольку он может выполнять обновление для кадра n, пока кадр n-1 рисуется, а также предотвращает несколько итераций обновления для каждого кадра, если средство визуализации выполняет много времени. Для дальнейшего пояснения, этот сценарий многократного обновления обрабатывается блокировкой потока обновления, если он обнаруживает, что буфер lastDrawn равен тому, который был только что обновлен. Если они равны, это указывает потоку обновления, что предыдущий кадр еще не отрисовывался.
Пока я получаю хорошие результаты. Дайте мне знать, если у кого-то есть какие-либо комментарии, буду рад услышать ваши мысли о том, что я делаю, правильно или неправильно.
Спасибо