Lua, состояние игры и игровой цикл

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

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

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

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

9 ответов

Решение

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

Я поместил бы игровое состояние в 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 или более опкодов в зависимости от выражения).

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

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

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

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

Я не люблю C++. Но я люблю игры.

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

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

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

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

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

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

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

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

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

Я хотел бы добавить свои два цента, так как я твердо верю, что здесь дается неправильный совет. Для контекста я использую 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++ и будет на вашем пути. Если вы пишете большую игру с тысячами сложных объектов со сложной логикой, продвинутым рендерингом и т. Д., Вы выиграете от более предварительного проектирования.

Удачи:)

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

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

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

Удачи в вашей игре!

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

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

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

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

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

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

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

Другие вопросы по тегам