Помогите с разработкой игры. Рендеринг петли? - PullRequest
7 голосов
/ 07 мая 2010

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

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

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

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

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

спасибо!

Ответы [ 7 ]

5 голосов
/ 07 мая 2010

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

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

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

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

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

Редактировать 2: Это похоже на интересное обсуждение: http://www.gamedev.net/community/forums/topic.asp?topic_id=482397

4 голосов
/ 07 мая 2010

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

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

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 для каждого движущегося объекта. Затем сравните линии, чтобы увидеть, пересекаются ли две линии. Обратите внимание, вы должны принять во внимание размер объектов. Точка пересечения - это место, где сталкиваются объекты. С помощью этого метода вы можете точно обнаруживать столкновения движущихся объектов.

3 голосов
/ 11 октября 2012

Возможно иметь отдельную нить обновления и нить рисования , но это не просто! Обычно вам нужно выполнить много проверок mutex , чтобы предотвратить многопоточный доступ к одним и тем же переменным, так что это на самом деле нереально (плюс вы не хотите обрабатывать полуобновленные состояния). Для правильной реализации вам действительно нужно иметь некоторую форму снимка последнего состояния рендеринга. Если вы не возражаете против сложности, есть хорошая реализация, которую можно найти здесь:

http://blog.slapware.eu/game-engine/programming/multithreaded-renderloop-part1/

http://blog.slapware.eu/game-engine/programming/multithreaded-renderloop-part2/

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

2 голосов
/ 07 мая 2010

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

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

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

Например, если мяч перемещается на 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, посмотритев заметное столкновение.

1 голос
/ 07 мая 2010

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

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

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

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

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

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

0 голосов
/ 07 мая 2010

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

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

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

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

0 голосов
/ 07 мая 2010

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

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

Только мои мнения, надеюсь, я не далеко от базы.

...