Как Lua работает как язык сценариев в играх?

67

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

Например, когда вы используете программу, написанную на C ++, которая использует скрипты Lua: код на Lua просто вызывает функции в основной программе, написанной на C ++, и действует как некомпилированный класс, ожидающий компиляции и добавления в кучу памяти C ++ программа?

Или он работает как bash-скрипт в Linux, где он просто выполняет программы, полностью отделенные от основной программы?

XSoloDolo
источник

Ответы:

90

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

Концептуально, общие шаги для включения сценариев следующие (я буду использовать псевдо-c для хоста и псевдо-lua для сценария. Это не точные шаги, а скорее как общий поток, в котором вы включаете сценарии)

  1. Создайте виртуальную машину в основной программе:

    VM m_vm = createVM();
  2. Создайте функцию и выставьте ее на виртуальную машину:

    void scriptPrintMessage(VM vm)
    {
        const char* message = getParameter(vm, 0); // first parameter
        printf(message);
    }
    
    //...
    
    createSymbol(m_vm, "print", scriptPrintMessage);
    

    Обратите внимание, что имя, под которым мы выставили функцию ( print), не обязательно должно совпадать с внутренним именем самой функции ( scriptPrintMessage).

  3. Запустите некоторый код скрипта, который использует функцию:

    const char* scriptCode = "print(\"Hello world!\")"; // Could also be loaded from a file though
    doText(m_vm, scriptCode);
    

Это все, что нужно сделать. Затем программа работает следующим образом:

  1. Вы звоните doText(). Затем управление передается виртуальной машине, которая будет выполнять текст внутри scriptCode.

  2. Код скрипта находит ранее экспортированный символ print. Затем он передаст управление функции scriptPrintMessage().

  3. По scriptPrintMessage()окончании управление вернется к виртуальной машине.

  4. Когда весь текст scriptCodeбудет выполнен, doText()он закончится, и управление будет перенесено обратно в вашу программу после строки doText().

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

  • Разделение проблем: это общая схема написания игрового движка на C / C ++, а затем и реальной игры на языке сценариев, таком как lua. Если все сделано правильно, игровой код может быть разработан полностью независимо от самого движка.

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

  • Стабильность и безопасность: поскольку скрипт выполняется внутри виртуальной машины, если все сделано правильно, глючный скрипт не приведет к сбою программы хоста. Это особенно важно, когда вы разрешаете своим пользователям писать свои собственные сценарии для вашей игры. Помните, что вы можете создать столько независимых виртуальных машин, сколько захотите! (Однажды я создал сервер MMO, в котором каждое совпадение выполнялось на отдельной виртуальной машине lua)

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

Скрипты не идеальны, хотя. Есть несколько распространенных недостатков использования скриптов:

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

    Некоторые языки сценариев, такие как lua, имеют средства отладки, которые можно использовать в некоторых средах разработки, таких как Eclipse. Сделать это очень сложно, и я, честно говоря, никогда не видел, чтобы отладка скриптов работала так же хорошо, как и отладка.

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

  • Интеграция с IDE. Маловероятно, что ваша IDE будет знать, какие функции экспортируются из вашей программы, и поэтому такие функции, как IntelliSense и т. П., Вряд ли будут работать с вашими сценариями.

  • Производительность. Будучи обычно интерпретируемыми программами и предназначенными для абстрактной виртуальной машины, архитектура которой может отличаться от реального оборудования, сценарии обычно выполняются медленнее, чем собственный код. Некоторые виртуальные машины, такие как luaJIT и V8, делают действительно хорошую работу. Это может быть заметно, если вы очень интенсивно используете сценарии.

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

Как вы используете ваши сценарии, зависит от вас. Я видел сценарии, используемые для таких простых вещей, как загрузка настроек, и для создания сложных игр на языке сценариев с очень тонким игровым движком. Однажды я даже увидел очень экзотический игровой движок, в котором смешаны скрипты lua и JavaScript (через V8).

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

Панда Пижама
источник
Я действительно новичок в этом, но я использую Unity. Вы упомянули что-то о Unity, не могли бы вы подробнее остановиться на этом? Должен ли я использовать что-то еще?
Токамоча
1
Я бы не стал использовать printfв вашем примере (или, по крайней мере, использовать printf("%s", message);)
чокнутый урод
1
@ratchetfreak: точка с запятой в конце вашего сообщения, сопровождаемая круглыми скобками, подмигивает мне ...
Panda Pajama
1
Один из лучших ответов, которые я видел на этом сайте за долгое время; это заслуживает каждого возражения, которое это получает. Очень красиво сделано.
Стивен Стадницки,
1
Дитя Lua в играх - это World of Warcraft, в котором большая часть пользовательского интерфейса написана на Lua, и большая часть которого может быть заменена игроком. Я удивлен, что ты не упомянул об этом.
Майкл Хэмптон
7

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

Сам код Lua интерпретируется средой выполнения Lua, то есть кодом C, который вы связываете как библиотеку (обычно) в своей собственной программе. Вы можете прочитать больше о том, как Lua работает на домашней странице Lua . В частности, Lua не является «некомпилированным классом», как вы думаете, особенно если вы думаете о классе C ++, потому что C ++ практически никогда не динамически компилируется на практике. Однако среда выполнения Lua и объекты, созданные сценариями Lua, которые запускает ваша игра, занимают место в пуле системной памяти вашей игры.

Джош
источник
5

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

Команды Lua вызываются только тогда, когда программа C ++ хочет их выполнить. Таким образом, код будет интерпретироваться только при его вызове. Я думаю, вы можете рассматривать Lua как bash-скрипт, который выполняется отдельно от основной программы.

Как правило, вы хотите использовать языки сценариев для вещей, которые вы можете захотеть обновить или повторить в дальнейшем. Я видел, что многие компании используют его для графического интерфейса, поэтому он предоставляет много возможностей для настройки интерфейса. Если вы знаете, как они создали свой Lua-интерфейс, вы также можете изменить графический интерфейс самостоятельно. Но существует множество других способов использования Lua, таких как логика ИИ, информация об оружии, диалог персонажей и т. Д.

М Дэвис
источник
3

Большинство языков сценариев, включая Lua, работают на виртуальной машине ( ВМ ), которая в основном представляет собой систему для сопоставления инструкции сценария с «реальной» инструкцией ЦП или вызовом функции. Виртуальная машина Lua обычно работает в том же процессе, что и основное приложение. Это особенно верно для игр, которые его используют. Lua API предоставляет вам несколько функций, которые вы вызываете в собственном приложении для загрузки и компиляции файлов скриптов. Например, luaL_dofile()компилирует данный скрипт в байт-код Lua, а затем запускает его. Затем этот байт-код будет отображаться виртуальной машиной, работающей внутри API, в собственные машинные инструкции и вызовы функций.

Процесс соединения родного языка, такого как C ++, с языком сценариев называется связыванием . В случае Lua его API предоставляет функции, которые помогают вам представить собственные функции в коде сценария. Таким образом, вы можете, например, определить функцию C ++ say_hello()и сделать ее вызываемой из сценария Lua. Lua API также предоставляет методы для создания переменных и таблиц с помощью кода C ++, которые будут видны для сценариев при их запуске. Комбинируя эти функции, вы можете предоставить Lua целые классы C ++. Возможно и обратное: API Lua позволяет пользователю изменять переменные Lua и вызывать функции Lua из собственного кода C ++.

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

Надеюсь, это поможет прояснить некоторые ваши вопросы.

glampert
источник
3

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

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

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

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

free3dom
источник
2

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

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

Теперь вы можете подумать: «Ну, я понимаю, что это немного проще, но с какой стати кто-то отказался бы от всей этой производительности ради немного большей простоты использования?»

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

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

Редактировать: Причина, по которой lua имеет тенденцию быть довольно популярной в разработке игр, заключается в том, что, возможно, это один из самых быстрых (если не самых быстрых) общедоступных языков сценариев в мире. Однако, с этой дополнительной скоростью, он пожертвовал некоторыми из своих удобств. Тем не менее, это все еще возможно более удобно, чем работать с прямым C или C ++.

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

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

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

  • Они набираются динамически
  • У них мало или вообще нет условий для сложных структур данных
  • Программы в них (скрипты) интерпретируются

Кроме того, часто считается, что язык является языком сценариев, если он предназначен для взаимодействия и функционирования наряду с другим языком программирования (обычно языком, который не считается языком сценариев).

Gurgadurgen
источник
1
Я бы не согласился с вами в первой строке, где вы написали "языки сценариев не компилируются". Lua фактически компилируется в промежуточный байт-код перед выполнением JIT-компилятором. Некоторые из них, конечно, интерпретируются построчно, но не все.
Гламперт
Я бы не согласился с вашим определением «язык сценариев». Существуют языки с двойным использованием (такие как Lua или C #), которые относительно часто используются в их скомпилированной форме, а не в форме сценария (или наоборот, как в случае с C #). Когда язык используется в качестве языка сценариев, он строго определяется как язык, который интерпретируется, а не компилируется.
Гургадурген
Справедливости ради, ваше определение более соответствует Википедии: «Язык сценариев или язык сценариев - это язык программирования, который поддерживает сценарии, программы, написанные для специальной среды выполнения, которая может интерпретировать (а не компилировать) ...», не говоря уже о том, мой комментарий тогда
гламперт
1
Даже обычный Lua полностью скомпилирован в байт-код, JIT-компилятор может даже создавать собственный двоичный файл. Так что нет, Луа не интерпретируется.
Волков Олег Викторович
Первая часть сомнительна, я испытываю желание понизить голос. Он в некотором роде прав в том, что это в конечном итоге интерпретируется, и JIT-компиляция @ OlegV.Volkov не приводит к компиляции чего-либо. Компиляция определяется временем компиляции, которое есть у Lua, а у байт-кода Lua (JIT или без JIT). Давайте не будем путать наш термин.
Алек Тил