Асинхронное обновление экрана для логики игрового процесса, C ++ - PullRequest
4 голосов
/ 28 мая 2009

Я программирую игру с использованием Visual C ++ 2008 Express и SDK Ogre3D.

Моя основная логика геймплея рассчитана на 100 раз в секунду. Для простоты я скажу, что это метод с именем 'gamelogic ()'. Это не основано на времени, что означает, что если я хочу «продвинуть» игровое время на 1 секунду, я должен вызвать 'gamelogic ()' 100 раз. 'gamelogic ()' облегчён по сравнению с рендерингом экрана игры.

Ogre имеет логику "слушателя", которая сообщает вашему коду, когда он собирается нарисовать рамку и когда она закончила рисовать рамку. Если я просто вызову 'gamelogic ()' непосредственно перед рендерингом кадров, то на игровой процесс будет сильно влиять скорость рендеринга экрана, которая может варьироваться от 5 до 120 кадров в секунду.

Простое решение, которое приходит на ум: вычислить время, прошедшее с последнего отрисованного кадра, и вызвать 'gamelogic ()' много раз до следующего кадра: 100 * timeElapsedInSeconds

Однако я полагаю, что «правильный» способ сделать это - многопоточность; есть отдельный поток, который запускает 'gamelogic ()' 100 раз в секунду.

Вопрос в том, как мне этого добиться и что можно сделать, если между двумя отдельными потоками существует конфликт: изменение содержимого экрана gamelogic (координаты 3D-объекта), когда Ogre рендерит экран одновременно.

Большое спасибо заранее.

Ответы [ 3 ]

6 голосов
/ 28 мая 2009

Если это ваше первое игровое приложение, использование многопоточности для достижения ваших результатов может оказаться более трудоемким, чем вы должны реально заниматься в своей первой игре. Синхронизация игрового цикла и цикла рендеринга в разных потоках - задача не из легких.

Как вы правильно заметили, время рендеринга может сильно повлиять на "скорость" вашей игры. Я бы посоветовал вам не делать свою игровую логику зависимой от установленного временного интервала (то есть 1/100 секунды). Сделайте его зависимым от текущего времени кадра (ну, последнее время кадра, так как вы не знаете, сколько времени займет рендеринг вашего текущего кадра).

Обычно я писал бы что-то вроде ниже (то, что я написал, это значительно упрощенно):

float Frametime = 1.0f / 30.0f;
while(1) {
    game_loop(Frametime);      // maniuplate objects, etc.
    render_loop();             // render the frame
    calculate_new_frametime();
}

Где Frametime - это рассчитанное время кадра, которое занял текущий кадр. Когда вы обрабатываете свой игровой цикл, вы используете время кадра из предыдущего кадра (поэтому установите начальное значение на что-то разумное, например, 1/30 или 1/15 секунды). Запуск его в предыдущий кадр достаточно близок, чтобы получить нужные вам результаты. Запустите ваш игровой цикл, используя эти временные рамки, а затем визуализируйте ваши вещи. Возможно, вам придется изменить логику в игровом цикле, чтобы не предполагать фиксированный интервал времени, но, как правило, исправления такого рода довольно просты.

Асинхронные циклы игры / рендеринга могут быть чем-то, что вам в конечном итоге понадобится, но это сложная проблема, которую нужно решить. Он включает в себя создание снимков объектов и соответствующих данных, помещение этих снимков в буфер, а затем передачу буфера в механизм рендеринга. Этот буфер памяти должен быть правильно распределен вокруг критических секций, чтобы избежать записи в него игрового цикла во время чтения из него цикла рендеринга. Вы должны быть уверены, что скопировали все соответствующие данные в буфер перед передачей в цикл рендеринга. Кроме того, вам придется написать логику, чтобы остановить либо игру, либо рендеринг циклов, ожидая завершения одного или другого.

Эта сложность - причина, по которой я предлагаю сначала написать ее более последовательно (если у вас нет опыта, который вы могли бы). Причина заключается в том, что сначала это «легкий» способ заставит вас узнать о том, как работает ваш код, как работает механизм рендеринга, какие данные нужны механизму рендеринга и т. Д. Знание многопоточности определенно требуется при разработке сложных игр в наши дни, но знание того, как это сделать хорошо, требует глубоких знаний о том, как игровые системы взаимодействуют друг с другом.

1 голос
/ 05 сентября 2009

Ваша основная логика игры не дает больших преимуществ, чем игрок может ответить. Практически единственный раз, когда это действительно полезно, это для физических симуляций, когда бег с быстрым фиксированным временным шагом может заставить сима вести себя более последовательно.

Кроме того, просто обновляйте свой игровой цикл один раз за кадр и передавайте переменную временную дельту вместо того, чтобы полагаться на фиксированную. Преимущества использования многопоточности минимальны по сравнению со стоимостью, особенно если это ваша первая игра.

0 голосов
/ 05 сентября 2009

Двойная буферизация ваших визуализируемых объектов - это подход, который вы могли бы изучить. Это означает, что компонент рендеринга использует 1 буфер, который обновляется, когда все игровые действия обновили соответствующий объект во 2-м буфере.

Но лично мне это не нравится, я бы (и часто использовал) подход Марка.

...