Справка с разработкой игр. Цикл рендеринга?

Я работаю над простой игрой, это - мой первый игровой проект.

Большинство образцов, которые я нахожу, имеет Цикл Рендеринга, где вся игровая логика сделана также, и мне просто не нравится это. Скажем, у меня есть шар с X=0 и стена в X=10 и в медленной машине, первый цикл помещает шар в X=7 и во второй цикл, это помещает шар в X=14. Это просто разрушило бы игру!

Является этот "цикл рендеринга" правильным способом сделать игры? Я должен написать код для проверки на вещи как это в каждом кадре? Пример, новый кадр X=14, последний кадр имеет X=7, таким образом, я должен проверить, существует ли что-нибудь от X=7 до X=14??

Я думал, что у меня должен быть разделенный поток для игровой логики и в цикле рендеринга, я должен просто "взять снимок" текущей игровой логики и дисплея это, нет?

Как делают Вас парни, опытные разработчики игр работают вокруг этого?

спасибо!

7
задан Dinah 7 May 2010 в 00:10
поделиться

6 ответов

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

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

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

Например. Если мяч перемещается на 10 единиц за одну секунду, и вы можете определить, что с момента последнего обновления прошло 0,1 секунды (используйте высокопроизводительный таймер или что-то еще, что вам доступно), вы просто масштабируете движение на 0,1, и мяч перемещается на 1 единицу.

Например.

private const float BallSpeedInMetresPerSecond = 10;

public void Update(float deltaTimeInSeconds)
{
    float adjustedSpeed = deltaTimeInSeconds * BallSpeedInMetresPerSecond;
    // set ball's speed / move it etc. using adjusted speed
}

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

Если у вас это работает, а затем вы хотите решить более сложную проблему, как сказал dash-tom-bang, обратите внимание на обнаружение столкновений с разверткой.

2
ответ дан 6 December 2019 в 14:01
поделиться

Исходя из моего ограниченного опыта в дизайне игр и искусственном интеллекте, я бы сказал, что у меня есть логический цикл и цикл отображения (во многом как в XNA). Логический цикл (метод обновления в XNA) будет в основном обрабатывать обновления позиций, а что нет, в то время как цикл отображения (метод рисования в XNA) будет отображать все на экране. Что касается обнаружения столкновений, я бы лично локализовал это на вашем шаре. Когда он движется, пусть он ищет столкновения и реагирует соответствующим образом.

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

Просто мое мнение, надеюсь, я не ошибаюсь.

0
ответ дан 6 December 2019 в 14:01
поделиться

Как сказано в другом ответе, проблема, которую вы наблюдаете, называется "туннелированием" Это проблема "пуля сквозь бумагу", пуля движется быстро, бумага тонкая, как вы узнаете, что произошло столкновение?

Это легко, если границы вашего мира просты. Например, в Тетрисе блокам разрешено двигаться влево и вправо только до тех пор, пока они не ударятся о боковые стороны, и легко проверить, ударяется ли самая нижняя координата о "землю". Эти тесты просты, потому что вы можете делать по одной оси за раз, а столкновения с боковыми сторонами означают нечто иное, чем столкновения с дном ямы. Если у вас прямоугольная комната, просто "остановите" движущийся объект, если его движение вывело его за пределы комнаты, зажав его координаты. Например, если ширина комнаты от -3 до +3, а ваш объект имеет X равный 5, просто измените его на 3 и все готово.

Если вы хотите работать с более сложными мирами, это немного сложнее. Вам нужно будет прочитать о столкновении "развернутой" геометрии. В принципе, если у вас есть круг, вам нужно провести тесты столкновений с капсулой вместо него - фигурой, которая получится, если "прочесать" круг от начальной точки до конечной. Она будет похожа на прямоугольник с полукругами на каждом конце. Математика на удивление проста (ИМХО), но может быть сложно добиться правильного результата и понять, что происходит. Но оно того стоит!

Edit: По поводу ниток - не нужно усложнять. Одного потока вполне достаточно. Пропуск кадров обновления тоже может запутать, и это довольно сложно, поскольку вам нужно определить "будущее", а затем сделать интерполяцию всех интересных значений до этого момента. Я сам не называю это циклом "рендеринга", поскольку цикл рендеринга - это только одна часть процесса.

def GameLoop():
   while True:
      ReadInputs()
      FigureOutWhatStuffDoes()
      DrawItAll()

Edit 2: Это кажется интересной дискуссией: http://www.gamedev.net/community/forums/topic.asp?topic_id=482397

5
ответ дан 6 December 2019 в 14:01
поделиться

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

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

while(true) {
   oldTime = currentTime;
   currentTime = systemTime();
   timeStep = currentTime - oldTime;

   // Only do logic x times / second
   if( currentTime > lastLogicTime + logicRefreshTime ){
      doGameLogic( currentTime - lastLogicTime );
      lastLogicTime = currentTime;
   }

   // Extrapolate all movements using timeStep
   renderGraphics( timeStep );

   wait( screenRefreshTime );
}

void doGameLogic( timeStep ) {
   // Update all objects
   for each( gameObject obj )
     obj.move( timeStep );
}

Пусть все твердые подвижные объекты наследуют класс SolidObject. Когда вы вызываете SolidObject.move(timeStep), этот метод проверяет, как далеко объект может быть перемещен в течение заданного timeStep. Если перед этой точкой есть стена, то объект должен остановиться, подпрыгнуть и изменить направление, умереть или что угодно.


Edit:

Если два объекта движутся, вы можете проверить если и где они сталкиваются. Многие игры делают это не очень хорошо, но вот как это делается:

Сначала вычислите линию движения между oldTime и currentTime для каждого объекта, который движется. Затем сравните линии, чтобы увидеть, пересекаются ли две линии. Обратите внимание, что необходимо учитывать размер объектов. Точка пересечения - это место столкновения объектов. Используя этот метод, вы можете точно обнаружить столкновения движущихся объектов.

4
ответ дан 6 December 2019 в 14:01
поделиться

Я думал, что у меня должен быть отдельный поток для игровой логики , а в цикле рендеринга я должен просто "сделайте снимок" логики текущей игры и покажите это, не так ли?

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

Допустим, у меня есть мяч с X = 0 и стена в X = 10, а в медленной машине первый цикл помещает мяч в X = 7 {{1 }} и во втором цикле помещает шар в X = 14. Это просто приведет к сбою игры !

Распределение двух потоков не решит эту проблему, если вы не можете гарантировать, что каждый отдельный компьютер, который вы используете, всегда будет достаточно быстрым, чтобы проверить X = 1, X = 2, X = 3 ... X = 10. Вы не можете дать эту гарантию. И даже если бы вы могли, редко можно использовать целые числа для позиций. Можете ли вы итеративно проверять X = 0,0000001, X = 0,0000002, X = 0,0000003 ... X = 0,9999999, X = 10,00000? Неа.

Как вы, ребята, опытные разработчики игр, решаете эту проблему?

Обычно у нас остается один цикл. ввод, обновление, рендеринг, повтор. Проблемы столкновения, как вы упомянули, решаются с помощью метода обнаружения столкновений, который вычисляет площадь, через которую может пройти объект, например. разрешение для X = [от 0 до 17].На действительно медленной машине это может быть X = [0-50], а на быстрой - X = [0-5], за которым следует X = [5-10], но каждый будет работать должным образом.

1
ответ дан 6 December 2019 в 14:01
поделиться

Если обновления логики обычно дешевы, а рендеринг иногда обходится дорого, проще всего сделать N обновлений логики в секунду. N = 60 является обычным явлением, но вы должны просто выбрать наименьшее значение, которое позволяет игре работать хорошо, выбрать значение и настраивать игру, пока она не будет работать с такой скоростью, или (что более вероятно) некоторая комбинация этих двух.

Во время выполнения отслеживайте фактически истекшее время, отслеживайте, сколько времени прошло логически (с точки зрения выполненных обновлений), и когда имеется расхождение более 1,0 / N секунд (потому что рендеринг занимает слишком много времени) выполнить дополнительные обновления, чтобы наверстать упущенное. Это лучше, чем пытаться выполнить произвольный период времени за один раз, потому что это более предсказуемо. (Если читатель не согласен, он может выяснить это на собственном горьком опыте.)

Недостатком этой системы является то, что если рендеринг становится особенно трудоемким, и из-за этого логике приходится выполнять слишком много обновлений, эти два могут немного рассинхронизироваться, и логика никогда не догонит . Если вы ориентируетесь на фиксированную систему, это просто указывает на то, что вы пытаетесь сделать слишком много, и вам придется как-то делать меньше, или (если такая ситуация, вероятно, будет редкостью) просто выбросьте всю идею и выполните рендеринг 1: 1: обновление. Если вы нацеливаетесь на что-то переменное, например ПК с Windows, вам просто нужно ограничить количество обновлений догоняющей логики и надеяться, что это позволит вернуть все в норму.

(Если логика дороже, этот подход не подходит; я никогда не работал над игрой, где это было проблемой.)

0
ответ дан 6 December 2019 в 14:01
поделиться
Другие вопросы по тегам:

Похожие вопросы: