Lua, состояние игры и игровой цикл - PullRequest
21 голосов
/ 21 апреля 2010
  1. На каждой итерации игрового цикла вызывайте скрипт main.lua - это хороший или плохой дизайн? Как это влияет на производительность (относительно)?

  2. Поддерживать игровое состояние с a . Хост-программа на C ++ или b . из сценариев Lua или c . из обоих и синхронизировать их?

(Предыдущий вопрос по теме: Lua и C ++: разделение обязанностей )

(Я голосую за каждый ответ. Лучший ответ будет принят.)

Ответы [ 9 ]

25 голосов
/ 21 апреля 2010

Моё основное правило для lua - или любой сценарий языка в игре -

  • Все, что происходит в каждом кадре: c ++
  • асинхронные события - пользовательский ввод - lua
  • синхронный игровой движок событий - lua

По сути, любой код, который вызывается на частоте> 33-100 Гц (в зависимости от частоты кадров), является C ++. Я пытаюсь вызвать скрипт-движок <10 Гц. </p>

На основании какого-либо фактического показателя? на самом деле, нет. но это действительно создает разделительную черту в проекте, когда задачи c ++ и lua четко разграничены - без предварительного разграничения задачи lua для каждого кадра будут расти до тех пор, пока они не начнут обрабатывать каждый кадр, а затем нет четких указаний относительно того, что следует сокращать.

9 голосов
/ 24 апреля 2010

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

Я бы поместил игровое состояние в C ++, добавил бы функции в lua, которые могут достигать, и изменил бы состояние. Подход, основанный на событиях, почти лучше, когда регистрация событий должна выполняться в lua (предпочтительно только в начале игры или на определенных игровых событиях, но не чаще, чем несколько раз в минуту), но фактические события должны запускаться C ++ код. Пользовательские вводы также являются событиями, и они обычно не происходят каждый кадр (за исключением, возможно, MouseMove, но который следует использовать осторожно из-за этого). То, как вы обрабатываете события пользовательского ввода (обрабатываете ли вы все (например, какая клавиша была нажата и т. Д.) В lua, или, например, существуют ли отдельные события для каждой клавиши на клавиатуре (в крайнем случае), зависит от игры, которую вы '' пытаемся сделать (в пошаговой игре может быть только один обработчик событий для всех событий, в RTS должно быть больше событий, и с FPS следует обращаться осторожно (в основном потому, что перемещение мыши будет происходить в каждом кадре)). Обычно, чем больше отдельные виды событий, которые вы имеете, тем меньше вам нужно кодировать в lua (что повысит производительность), но тем сложнее становится, если «реальное событие», которое вам нужно обработать, фактически инициируется более отдельными «событиями уровня программирования» ( что может фактически снизить производительность, потому что код lua должен быть более сложным).

В качестве альтернативы, если производительность действительно важна, вы на самом деле можете улучшить виртуальную машину lua, добавив в нее новые коды операций (я видел, что некоторые компании делают это, но в основном, чтобы сделать декомпиляцию скомпилированных фрагментов lua более сложной), что это на самом деле не сложно сделать. Если у вас есть то, что код lua должен выполнять много раз (например, регистрация событий, запуск событий или изменение состояния игры), вы можете захотеть реализовать их в виртуальной машине lua, поэтому вместо нескольких getglobal и setglobal кодам операций они будут принимать только один или два (например, вы можете сделать код операции SETSTATE с параметром 0-255 и 0-65535, где первый параметр описывает, какое состояние нужно изменить, а второй описывает новое значение состояние. Конечно, это работает, только если у вас есть максимум 255 событий, максимум 2 ^ 16 значений, но в некоторых случаях этого может быть достаточно. И тот факт, что для этого требуется только один код операции, означает, что код будет выполняться Быстрее). Это также усложнит декомпиляцию, если вы намереваетесь скрыть свой код lua (хотя и не слишком сильно для того, кто знает внутреннюю работу lua). Выполнение нескольких опкодов на кадр (около 30-40 вершин) не сильно ухудшит вашу производительность. Но 30-40 опкодов в виртуальной машине lua не уйдут далеко, если вам нужно делать действительно сложные вещи (простой if-then-else может занять до 10-20 или более опкодов в зависимости от выражения).

6 голосов
/ 27 апреля 2010

Я впервые использую Lua в игре, над которой я работаю.C ++ сторона моего приложения на самом деле содержит указатели на экземпляры каждого игрового состояния.Некоторые игровые состояния реализованы на C ++, а некоторые реализованы на Lua (например, состояние «игры»).

Обновление и основной цикл приложения живут на стороне C ++.Я представил функции, которые позволяют виртуальной машине Lua добавлять новые игровые состояния в приложение во время выполнения.

У меня еще не было проблем с медлительностью, даже при работе на оборудовании с ограниченными ресурсами (процессор Atom со встроенным видео), Функции Lua вызываются каждый кадр .Самая дорогая (с точки зрения времени) операция в моем приложении - это рендеринг .

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

Редактировать: я использую Luabind , который, как я прочитал, в целом работает медленнее, чем другие связующие средыкурс API Lua C.

5 голосов
/ 14 сентября 2011
  1. Вы, вероятно, не хотите выполнять весь скрипт Lua на каждой итерации кадра, потому что любая достаточно сложная игра будет иметь несколько игровых объектов со своим поведением.Другими словами, преимущества Lua теряются, если у вас нет нескольких крошечных скриптов, которые обрабатывают определенную часть поведения большой игры.Вы можете использовать функцию lua_call для вызова любой подходящей подпрограммы lua в вашем скрипте, а не только всего файла.

  2. Здесь нет идеального ответа, но подавляющее большинство состояния вашей игры традиционнохранится в игровом движке (т.е. C ++).Вы открываете Луа только для того, чтобы Луа принял решение, которое вы назначили Луа.

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

5 голосов
/ 25 апреля 2010

Мне не нравится C ++. Но я люблю игры.

Мой подход может быть немного нетипичным: я делаю все, что могу в Lua, и только абсолютный минимум в C ++. Игровой цикл, сущности и т. Д. Выполнены в Lua. У меня даже есть реализация QuadTree в Lua. C ++ обрабатывает графические и файловые системы, а также взаимодействует с внешними библиотеками.

Это не машинное решение, а решение программиста; Я выводил код намного быстрее в Lua, чем в C ++. Поэтому я трачу свои циклы программиста на новые функции, а не на сохранение компьютерных циклов. Мои целевые машины (любой ноутбук за последние 3 года) могут легко справиться с таким количеством Lua.

Lua на удивление не занимает много места (см. luaJIT , если вы этого не знаете).

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

5 голосов
/ 24 апреля 2010

ИМХО, сценарии Lua предназначены для определенного поведения, это определенно ухудшит производительность, если вы вызываете сценарий Lua 60 раз в секунду.

Сценарии Lua часто предназначены для отделения таких вещей, как поведение и конкретные события, от логики игрового движка (GUI, элементы, диалоги, события игрового движка и т. Д.). Например, Lua может быть полезен при запуске взрыва (частицы FX), если игровой персонаж ходит куда-то, жестко запрограммировав результат этого события в вашем движке, будет очень уродливым выбором. Тем не менее, было бы лучше сделать правильный запуск сценария для движка, развязав это специфическое поведение с вашим движком.

Я бы порекомендовал попытаться сохранить ваше игровое состояние в одной части, вместо того, чтобы повышать уровень сложности синхронизации состояний в двух местах (Lua и Engine), добавьте к нему многопоточность, и в итоге вы получите очень безобразный беспорядок Будь проще. (В своих проектах я в основном сохраняю Game State на C ++)

Удачи в игре!

4 голосов
/ 27 апреля 2010

О производительности 1: если main.lua не изменяется, загрузите его один раз с lua_loadfile или loadfile, сохраните ссылку на возвращенную функцию, а затем вызовите ее при необходимости.

2 голосов
/ 21 апреля 2017

Я хотел бы добавить свои два цента, так как я твердо верю, что здесь даются некоторые неверные советы. Для контекста я использую Lua в большой игре, которая включает в себя как интенсивный 3D-рендеринг, так и интенсивное игровое логическое моделирование. Я стал более знаком, чем я хотел бы с Lua и выступлением ...

Обратите внимание, что я собираюсь поговорить конкретно о LuaJIT, потому что вы захотите использовать LuaJIT. Это действительно подключи и играй, так что если вы можете встроить Lua, вы можете встроить LuaJIT. Вы захотите, если не для дополнительной скорости, то для модуля интерфейса с автоматическими посторонними функциями (требует 'ffi'), который позволит вам вызывать ваш нативный код непосредственно из Lua, даже не касаясь API Lua C (95 % + случаев).

  1. Это совершенно нормально звонить Луа на частоте 60 Гц (я называю это на частоте 90 Гц в ВР ..). Уловка в том, что вы должны быть осторожны, чтобы сделать это правильно. Как уже упоминалось, очень важно, чтобы вы загружали скрипт только один раз . Затем вы можете использовать C API, чтобы получить доступ к функциям, которые вы определили в этом скрипте, или запустить сам скрипт как функцию. Я рекомендую первое: для относительно простой игры вы можете обойтись с определением таких функций, как onUpdate (dt), onRender (), onKeyPressed (key), onMouseMoved (dx, dy) и т. Д. Вы можете вызывать их в соответствующее время. из вашего основного цикла в C ++. В качестве альтернативы, вы можете сделать так, чтобы весь ваш главный цикл был в Lua, и вместо этого вызывать код C ++ для подпрограмм, критичных к производительности (я так и делаю). Это особенно легко сделать с помощью LuaJIT FFI.

  2. Это действительно сложный вопрос. Это будет зависеть от ваших потребностей. Можете ли вы довольно легко забить состояние игры? Отлично, поставьте это на C ++ и получите доступ от LuaJIT FFI. Не уверен, что все будет в игровом состоянии / как можно будет быстро создавать прототипы? Ничего плохого в том, чтобы держать его в Lua. То есть, пока вы не начнете говорить о сложной игре с тысячами объектов, каждый из которых содержит нетривиальное состояние. В этом случае гибрид - это путь, но выяснить, как именно разделить состояние между C ++ и Lua, и как воевать указанное состояние между этими двумя (особенно в перфоментных подпрограммах), является делом искусства. Дайте мне знать, если вы придумали пуленепробиваемую технику :) Как и все остальное, общее правило: данные, проходящие через критичные к производительности пути, должны быть на родной стороне. Например, если у ваших сущностей есть позиции и скорости, которые вы обновляете в каждом кадре, и у вас есть тысячи упомянутых сущностей, вам нужно сделать это в C ++. Тем не менее, вы можете избежать размещения «инвентаря» поверх этих объектов, используя Lua (инвентарь не нуждается в обновлении за кадр).

Теперь, еще пару замечаний, которые я хотел бы выкинуть как в качестве общих советов, так и в ответ на некоторые другие ответы.

  1. Основанные на событиях подходы, как правило, имеют решающее значение для производительности любой игры, но это вдвойне относится к системам, написанным на Lua.Я сказал, что это прекрасно, называть Lua @ 60hz.Но это не - это прекрасно, когда вы выполняете крутые петли над множеством игровых объектов в каждом кадре в Lua.Возможно, вам не понравится расточительный вызов update () для всего во вселенной в C ++ (хотя вы не должны этого делать), но в Lua это приведет к слишком быстрому поглощению этих драгоценных миллисекунд.Вместо этого, как уже упоминали другие, вы должны думать о логике Lua как о «реактивной» - обычно это означает обработку события.Например, не проверяйте, что один объект находится в диапазоне другого в каждом кадре в Lua (я имею в виду, это хорошо для одного объекта, но в целом, когда вы расширяете свою игру, вам не следует так думать).Вместо этого попросите ваш движок C ++ уведомлять вас, когда эти два объекта находятся на определенном расстоянии друг от друга.Таким образом, Lua становится высокоуровневым контроллером игровой логики, распределяя высокоуровневые команды и реагируя на события высокого уровня, не выполняя низкоуровневый математический анализ тривиальной игровой логики.

  2. Остерегайтесь совета, что «смешанный код» медленный.Lua C API легок и быстр.Обертывание функций для использования с Lua в худшем случае довольно просто (и если вы потратите некоторое время, чтобы понять, как Lua взаимодействует с C относительно виртуального стека и т. Д., Вы заметите, что оно было разработано специально для минимизации накладных расходов на вызовы), ив лучшем случае, тривиально (не требуется перенос) и на 100% так же эффективно, как и нативные вызовы (спасибо, LuaJIT FFI!). В большинстве случаев я считаю, что тщательная смесь Lua и C (через LJ FFI) превосходит чистую Lua.Даже векторные операции, которые, как я полагаю, в руководствах LuaJIT упоминаются где-то в качестве примера, где код должен храниться в Lua, я обнаружил, что быстрее, когда я выполняю через вызовы Lua-> C.В конечном счете, никому и ничему не доверяйте, кроме ваших собственных (осторожных) измерений производительности, когда дело касается фрагментов кода, чувствительных к производительности.

  3. Большинство маленьких игр могут остаться безнаказаннымипросто отлично с состоянием игры, циклом ядра, обработкой ввода и т. д., выполненными полностью на Lua и работающими под LuaJIT.Если вы создаете небольшую игру, подумайте о подходе «переход на C ++ по мере необходимости» (ленивая компиляция!). Пишите на Lua, когда вы обнаружите явное узкое место ( и измерите его, чтобы убедиться, что это виновник).), перенесите этот бит в C ++ и отправляйтесь в путь.Если вы пишете большую игру с тысячами сложных объектов со сложной логикой, продвинутым рендерингом и т. Д., То вы выиграете от более предварительного проектирования.

Удачи:)

2 голосов
/ 27 апреля 2010

Большая часть производительности будет потеряна из-за связывания между Lua и C ++. Вызов функции на самом деле нужно будет обернуть, а затем обернуть, и, как правило, пару раз. Чистый код Lua или чистый C ++ обычно быстрее, чем смешанный код (для небольших операций).

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

Обычно сценарии хороши на высоком уровне . Lua использовался в известных играх для ботов ( Quake 3 ) и для пользовательского интерфейса ( World of Warcraft ). Используемые на высоком уровне микропотоки Lua пригодятся: сопрограммы могут значительно сэкономить (по сравнению с реальными нитями). Например, запускать код Lua только время от времени.

...