Как создать цикл обновления / рисования соответственно с Android OpenGL? - PullRequest
7 голосов
/ 29 марта 2012

Я хотел написать какую-нибудь простую игру на андроиде с использованием opengl es, но сразу столкнулся с проблемой основного цикла игры. Как я читал здесь: http://developer.android.com/resources/tutorials/opengl/opengl-es10.html приложение вызывает public void onDrawFrame(GL10 gl) каждый раз, когда ему нужно перерисовать поверхность. Или что-то в этом роде. Моя проблема была - как создать цикл обновления, который не зависит от вызовов отрисовки. Я имею в виду - это не может так работать, я не могу обновить игровую логику, только когда устройство (приложение?) Хочет перерисовать поверхность (или я ошибаюсь?).

После некоторого поиска в Google я пришел к выводу, что мне нужно создать еще один поток для цикла обновления. Но потом у меня возникла другая проблема - с одним потоком, заботящимся о рисовании, а другим - с обновлением игровой логики, я не знал, как заставить их взаимодействовать. Во-первых, это были два отдельных класса (по крайней мере, в моей реализации), поэтому они не могли использовать одни и те же игровые переменные и объекты (спрайты, таймеры, разные переменные, счетчики и т. Д.) В значительной степени все, что нужно было сделать этим двум классам. их работы). Теперь я думаю, что смог бы как-то собрать их обоих в один класс. Но, во-вторых, мне нужно было как-то синхронизировать два потока.

Наконец, я выдвинул эту общую идею:

  • 3 класса:
    1. public class MyRenderer implements GLSurfaceView.Renderer по методу onSurfaceCreated(), заботящемуся о рисовании
    2. public class UpdateThread implements Runnable с методами run() и update(). run() вызывал update() метод ровно 60 раз в секунду (я хотел фиксированный пошаговый цикл)
    3. public class SpritesHolder используется в качестве контейнера для всех игровых объектов / переменных / вещей (таких как спрайты, таймеры, переменные состояния и т. Д.) Со всеми открытыми полями.
  • Таким образом, класс SpritesHolder представлял собой ящик, содержащий все необходимые переменные в одном месте, чтобы классы MyRenderer и UpdateThread могли получить к нему доступ и использовать его.
  • Что касается синхронизации - я просто сделал что-то вроде этого:

    public void update(float delta)
    {
        synchronized (spritesHolder)
        {
            // whole method code...
        }
    }
    

    и

    public void onDrawFrame(GL10 gl)
    {
        synchronized (spritesHolder)
        {
            // whole method code...
        }
    }
    

    , чтобы оба потока не использовали spritesHolder одновременно. Таким образом, обновления выполнялись 60 раз в секунду, и рисование происходило всякий раз, когда требовалось приложение (устройство?).

Много разговоров, извините, я почти закончил писать этот пост. ;) Так или иначе - это (описано выше) работает, и я даже написал какую-то игру, основанную на этом «шаблоне», но я думаю, что мои идеи могут быть сумасшедшими, и можно все гораздо лучше придумать. Буду очень признателен за все комментарии и советы.

Ответы [ 2 ]

1 голос
/ 29 марта 2012

Я еще не использовал это с OpenGL, но UpdateThread должен быть TimerTask. В вашей активности запустите его с

new Timer().schedule(new UpdateThread(view), 0, 15); //where view is your view, and 15 is the wait time

в вашей TimerTask вызовите представление с помощью обработчика. например (в вашем классе просмотра)

Handler refreshHandler = new Handler() {
    public void handleMessage(Message msg) { 
               //Handle it; e.g. for canvas invalidate()
            }};

В вашем методе запуска UpdateThread сделайте это:

 view.refreshHandler.sendMessage(new Message());

Теперь он не зависит от вашего игрового цикла. Кроме того, ваш игровой цикл должен быть не в MainActivity, а в потоке (однонаправленный OO).

1 голос
/ 29 марта 2012

Ваше решение, вероятно, будет работать, хотя оно может быть неоптимальным для производительности.Если вы подумаете об этом, то рендер и нить обновления не обязательно должны быть на 100% эксклюзивными.

Я недавно потратил некоторое время на размышления об этом для своей игры, и это то, что я закончил в итогес (не обязательно лучше, но просто о чем подумать):

Для каждого визуализируемого объекта у меня есть объект, принадлежащий потоку обновления, и другой объект, принадлежащий потоку рендеринга, который является простым контейнером, которыйсодержит инструкции по рисованию объекта (матрица модели, унифицированные значения, meshID и т. д.).Я запускаю поток обновления, вычисляя все конечные позиции и значения для моих объектов рендеринга, а затем синхронизируюсь для небольшого окна, где я передаю любые значения, которые изменились в течение этого кадра, объектам в потоке рендеринга.Затем, как только новая информация пройдена, рендеринг кадра начинается, пока следующий кадр обновления выполняется одновременно.Поскольку существует только небольшое окно исключения, оно позволяет обновлять и отрисовывать потоки для одновременной работы большую часть времени.

...