Создание игры на ваших глазах — часть 3: Прикручиваем скриптовый язык к Unity (UniLua)


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

    На этот раз поговорим о языке для написания внутриигровых скриптов.

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

    Сразу скажу, что к последнему информации в интернете почти что ноль, и половина этого нуля — на китайском. Так что, можно сказать, — держите эксклюзив.

    Зачем нам скрипты?


    В нашей игре у нас есть необходимость показывать разнообразные скриптованные сценки.

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

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

    Очевидно, что хардкодить такие штуки — вообще не тру.

    Почему Lua?


    Собственно, изначально был выбор между собственным велосипедом и Lua.

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

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

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

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

    Интеграция в Unity


    Здесь начинается первое веселье. Первая же библиотека, реализовывающаяя Lua в Unity, которую вы найдете — будет выглядеть хорошо. Но если копнуть глубже, то окажется, что она юзает какие-то специфичные методы .Net, которые, например, недоступны на мобилах (а, возможно, и каких-то других платформах).

    А нам бы хотелось библиотеку, которая бы поддерживалась везде (на всякий случай) и желательно еще полностью с исходниками, а не в закрытой DLL'ке.

    Покопавшись в инете, мы нашли бесплатное творение китайских программистов — UniLua. Полные сорцы и работает везде.

    Оно всем хорошо кроме того, что доки невероятно скудны и частично написаны на китайском.

    Ну да ладно, у нас же есть исходники! И мозг… =) Качаем, закидываем папку UniLua в плагины (чтобы не перекомпилировалось каждый раз) и вперед.

    Вызываем Lua-скрипт из C#


    Тут все сравнительно просто:
    using UniLua;
    
    private ILuaState _lua; // через этот объект будет производится работа с Lua
    private ThreadStatus _status; // объект для работы с конкретным скриптом
    ...
    _lua = LuaAPI.NewState();	 // создаем 
    
    string lua_script = ""; // сюда можно писать код на Lua
    
    _status = _lua.L_LoadString(lua_script); // загружаем скрипт
    
    if (_status != ThreadStatus.LUA_OK)
    {
    	Debug.LogError("Error parsing lua code");
    }
    
    _status.Call(0, 0); // запускаем Lua-скрипт
    

    Можно попробовать запустить. Если никто не ругнулся — значит все хорошо. Пустой скрипт успешно выполнился.

    Вызов функций C# из Lua


    Теперь надо научиться рулить хоть чем-то из этого скрипта. Очевидно, нам нужно научиться вызывать код на C# из Lua.

    Напишем метод, который просто пишет параметр в лог:
    private int L_Trace(ILuaState s)
    {
    	Debug.Log("Lua trace: " + s.L_CheckString(1)); // читаем первый параметр
    	return 1; // так надо
    }
    

    Как видите, мы использовали класс ILuaState. Именно там хранятся все входные параметры (которые мы захотим передать из Lua и именно туда нужно возвращать результат. Обратите внимание! Результат в Lua возвращается не через return, а через s.PushInteger(), s.PushString() и т.п.

    Функция написана. Теперь ее надо подключить к Lua.

    	private int OpenLib(ILuaState lua)
    	{
    		var define = new NameFuncPair[] // структура, описывающая все доступные методы (интерфейс Lua -> C#)
            {
                new NameFuncPair("trace", L_Trace),
            };
    
    		lua.L_NewLib(define);
    		return 1;
    	}
    

    Далее, после создания объекта _lua, нам нужно добавить подключение этого описания библиотеки:
    _lua.L_OpenLibs();
    _lua.L_RequireF("mylib", OpenLib, true);
    

    Готово! Теперь можно сделать так:
    string lua_script = @"
        local lib = require ""mylib""
        lib.trace(""Test output"")
    ";
    

    Казалось бы, все? Но нет. Теперь самое сложное.

    Yield


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

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

    Первое, что нам нужно будет — это запускать скрипт не Call'ом, а через отдельный поток:
    //_status.Call(0, 0); это нам больше не нужно. вместо этого пишем:
    _thread = _lua.NewThread();
    _status = _thread.L_LoadString(lua_script);
    _thread.Resume(null, 0);
    

    Теперь представим себе, что мы на C# написали функцию «подождать окончания анимации» (L_WaitForAnimationStop), которую вызываем из Lua. Реализация тут может быть разная, то я опишу общий принцип.

    В этой функции нам нужно повесить на окончание этой анимации какой-то callback, и самое главное — ввместо return 1 мы должны сделать так:
    	private int L_WaitForAnimationStop(ILuaState s)
    	{
    		// здесь добавляем нужные callback'и и т.п.
    
    		_temp_state = s; // сохраняем ILuaState в приватный член класса
    		return s.YieldK(s.GetTop(), 0, null); // указываем Lua, что оно должно отдать управление шарпам
    	}
    

    А непосредственно в callback'е — нам нужно будет продолжить выполнение скрипта с места, где он остановился
    if (_temp_state.GetTop() > 0) _thread.Resume(null, 0);
    

    Вот и все. Теперь скрипт типа:
    lib.trace("starting")
    lib.wait_for_animation_stop()
    lib.trace("stopped")
    

    после lib.wait_for_animation_stop() приостановится и продолжится только когда вы этого захотите (т.е. в вышеописанном случае — вызовите callback, который и сделает Resume()).

    Чего удалось добиться


    С помощью вышеописанного метода, а также шаманства для имитации ООП, удалось добиться такого синтаксиса:

    local ch1 = CharacterGfx()
    ch1.create("char_0")
    
    local ch2 = CharacterGfx()
    ch2.create("char_1")
    
    ch1.moveto("workout")
    ch2.moveto("fridge")	
    
    ch2.wait_move_finish()
    ch1.wait_move_finish()
    
    vh.trace("finished ok")
    

    Скрипт создает два спрайта персонажей, двигает первого к точке «workout», второго — к точке «fridge», потом ждет, когда оба закончат свое движение, и только потом пишет «finished ok».

    Из документации могу посоветовать только Lua 5.2 Reference Manual, где все эти шаманства описаны, хоть и немного для другой реализации.

    Все статьи серии:
    1. Идея, вижен, выбор сеттинга, платформы, модели распространения и т.п
    2. Шейдеры для стилизации картинки под ЭЛТ/LCD
    3. Прикручиваем скриптовый язык к Unity (UniLua)
    4. Шейдер для fade in по палитре (а-ля NES)
    5. Промежуточный итог (прототип)
    6. Поговорим о пиаре инди игр
    7. 2D-анимации в Unity («как во флэше»)
    8. Визуальное скриптование кат-сцен в Unity (uScript)
    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 35
    • +2
      Хм, скрипты на скриптах? :)

      И почему творение от китайских друзей? На мой взгляд, LuaInterface проще
      • 0
        Я прогуглил разные библиотеки. Выбирал по параметрам:
        1. полный source code, никаких dll
        2. 100% совместимость даже с обрезанными Mono .NET на всех платформах

        Выбрал UniLua, попробовал, заработало =)
        • +1
          Мы тоже хотели прикрутить Lua или Python, но пока останавливает как раз то, что они не на всех платформах работают. В отзывах Unity Lua Interface Library написано что на маке не работает и ответ разработчика что мол да, не работает.

          Вы проверяли работоспособность на всех платформах?

          Такую же бы штуку, только для C#
          • 0
            Мы не проверяли, но везде написано, что именно эта UniLua будет работать, т.к. написана полностью в исходниках и не ссылается ни на какие хитрые части дотнета. И кучи отзывов, что да, помогло и на iOS заработало.
    • +6
      А почему вас не устроил C# или JS?
      Ведь C#,UnityScript и Javascript в Unity и есть скрипты.
      Да и велосипедные надстройки теряют тестируемость.
      Что вы выиграли от внедрения еще одного уровня скриптов?
      • +1
        Потому что написание квестов, скриптовых сценок и т.п. — не должно требовать перекомпиляции кода. Это геймдизайнерская задача. А никакая геймдизайнерская задача не должна требовать лазанья в код. Это — аксиома грамотного построения кода игры.
        • 0
          Простите, написание на LUA разве не лизание в коде, да и еще в сторонней IDE?
          Описание квестов, уровней и другого динамического контента должно происходить деклоративно, например, в XML или JSON формате и скармливается движку, написанного программистом на «родных» языках для Unity и протестированному.
          На мой взгляд — это грамотное построение проекта.
          • +2
            Так именно так и есть. Есть отдельные ресурсы гейм-баланса. Скрипты в текстовых файликах, баланс в табличке Google Docs. Все это дело скармливается в игру.

            То есть дизайнер придумал квест, перед которым по его задумке нужно проиграть анимацию. Он забил квест в табличку и прописал ему скриптик в отдельном текстовом файлике. Все изолированно.
            • 0
              Тогда зачем Lua? Это не декларативное описание, а язык скриптов.
              Использование Lua может нарушать порядок выполнения, а также приводить к появлению ошибок.
              Этих недостатков лишен декларативный подход.
          • –1
            Можно сделать написание и компиляцию C# скриптов без перекомпиляции кода! Можно генерить код по декларативным описаниям дизайнеров. У нас например дизайнеры писали функцию расчета урона как обычную формулу а мы генерили и компилировали код, и меняли без перекомпиляции хоть в рантайме.
            Повторюсь, C# может все что любой другой скриптовой язык и любая прикрутка луа и других скриптов к шарпу всегда будет бессмысленной и вызывать недоумение.
            • 0
              Вы сейчас про Unity или про C# в целом?
              • –1
                И про Unity и про C# в целом. mono поддерживает генерацию и компиляцию кода.
                • 0
                  Еще раз — геймдиз вносит правку в блокнотике в текстовый файл скрипта и перезапускает готовый .exe — ваш подход позволяет так сделать?
                    • 0
                      Я не знаю, как с поддержкой всего этого в Моно, но на iOS работать не должно, т.к. там выпилены все подобные вещи в угоду секурности.
                      • +1
                        не получится, надо версию .net 3.5 минимум
                    • –2
                      Правильно ли я понимаю, что если правки не в начал игры, а в нескольких произвольных местах, то надо всю игру проходить заново до момента изменений?
                      Или гейм дизайнер еще и читерит в сейвах?
                      • +1
                        Естественно, для тестирования предусмотрены все необходимые инструменты
          • +2
            Друзья! Скажите, пожалуйста, как обстоят дела в этом юнити с тестированием? Тестируют ли вообще? Какие библиотеки используют?
            И увидим ли мы тесты в этой замечательной серии?
            Спасибо.
          • 0
            Рекомендую рассматривать данную статью в ракурсе «Как не надо делать», если только вы не хотите скинуть работу программиста на game-дизайнера.
            • –1
              То есть по-вашему скриптовать сценки в игре должен программист???
              • 0
                А по вашему программировать должен дизайнер?
                Есть специальная должность — программист скриптов. Вот пусть он и пишет. Не лишайте людей работы:)
                • 0
                  О чем мы вообще говорим, если у нас в компании всего 3 человека? =)
                  • 0
                    Тогда зачем вам вообще так все усложнять если вы делаете простенькую инди игру, а не WoW?:)
                    • –1
                      Поверьте — это наоборот упрощение.
                      • 0
                        Не смешите мои тапки! Покажите любой известный движок или просто проект, где применили такую методику и не разочаровались.
                        Сам на Lua сделал больше пяти проектов, но таких извращений еще ни в одном вообще не встречал.
                        Думаю есть только один вариант, когда уже есть на Lua написанная логика проверенная может даже из готовой игры.
                        и вот что бы только связать и запустить логику через два десятка Lua функций под Unity, на такую жертву можно пойти.
                • 0
                  дело в том что Unity уже содержит скриптовый Engine на базе mono, а также содержит IDE для работы как программиста(MonoDevelop или VS), так и level-дизайнера(сама оболочка Unity и MonoDevelop если уже приходится делать кастовую логику на уровне)
                  Используя Unity, вы по сути, пишите скрипты под универсальный 3D Engine. И это все выгодно отличает Unity от все остальных игровых фремворков.
                  Описанный вами подход отлично подошел бы для реализации на Marmalade, где есть C++ и все. А в Unity как раз подход такой, что бы можно было легко и просто делать уровни и их логику, прост отлаживать и релизить игровые приложения.
                  Простите, но вы забиваете микроскопом гвозди.

              • –1
                Вообще зачем плодить сущности? Вам и так дали широкие возможности скриптования на C#, JavaScript или Boo на ваш выбор. Зачем еще Lua нужна? Тем более с такими шаманствами?
                Сделайте редактор квестов и ведите описание квестов в JSON или csv, как уже было описано ранее. Надо чтобы перед квестом проигрался ролик — меняем соответствующий параметр в блокноте и не мучаемся с скриптованием и программированием. Пожалейте дизайнеров, им и так много чего считать и балансить приходится.

                Единственное что приходит в голову для чего может понадобится Lua — клиент серверная игра-сервис с толстым клиентом. Например: у нас есть два числа x и y и мы знаем что в любой момент взаимодействие между ними может измениться. Ок, посылаем запрос серверу, он нам выдает скриптик на Lua и уже на клиенте считаем. Все довольны и выпускать патч ради такой мелочи не требуется.
                • 0
                  У меня такое чувство, что вы не читали статью и комменты выше.
                • +1
                  [missclick]
                  • –1
                    Но если это исключительно для геймдизайнера, то ведь есть же вроде визуальное скриптование в Юнити?
                    • 0
                      У них просто одна лицензия на всех 3 человек. И Юнити стоит только у програмиста, а у остальных только блокнот..))… Хардкорщики.
                      • 0
                        Такие вещи больше подходят для 3D игр, где прямо в играх собираются сцены и т.п.

                        В нашем же 2D подходе всю картинку удобнее и проще рисовать кодом и собирать тоже кодом, без работы с редактором сцены.

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