2009-05-28 2 views
4

Я программирую игру с использованием Visual C++ 2008 Express и Ogre3D sdk.Обновление асинхронного экрана до логики геймплея, C++

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

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

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

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

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

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

ответ

6

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

Как вы правильно указываете, время рендеринга может сильно повлиять на «скорость» вашей игры. Я бы предположил, что вы не делаете свою логику игры зависящей от заданного среза времени (т. Е. 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 является calculcated frametime, что текущий кадр взял. Когда вы обрабатываете игровой цикл, вы используете время цикла из предыдущего кадра (поэтому установите начальное значение на что-то разумное, например 1/30 или 1/15 секунды). Запуск его в предыдущее время цикла достаточно близко, чтобы получить нужные вам результаты. Запустите игровой цикл, используя этот временной интервал, а затем отрисуйте свои вещи. Возможно, вам придется изменить логику в вашем игровом цикле, чтобы не предполагать фиксированный интервал времени, но в целом эти виды исправлений довольно просты.

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

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

+1

Игры переключились с фиксированных предположений рендеринга на отслеживание кадров-времени некоторое время назад. Раньше они полагались на то, что процессор работает на частоте 8 Гц. Когда они вышли с 16-граммовыми машинами, они включили «турбо-кнопку» для переключения процессора с 16 Гц до 8 Гц, чтобы игры все равно работали с правильной скоростью. Интересный факт. – Kieveli

+0

Действительно! Были даже небольшие приложения, которые были написаны для «замедления» вашего процессора, чтобы старые игры работали на приемлемых кадрах на более новых машинах. Один из них назывался «MOslo», я думаю. Я помню, как все мои старые игры Ultima работали слишком быстро на новых компьютерах, которые у меня были. – Mark

+1

Это стало еще лучше с временем выполнения turbo pascal, которое создавало бы цельное переполнение при работе на быстрой машине и тем самым разбивало бы все. – tstenner

0

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

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

1

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

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

Смежные вопросы