Пользователь
0,0
рейтинг
6 февраля 2014 в 12:17

Разработка → Создание игры на ваших глазах — часть 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)
Святослав @soulburner
карма
234,5
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (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 — ваш подход позволяет так сделать?
              • –1
                • 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 подходе всю картинку удобнее и проще рисовать кодом и собирать тоже кодом, без работы с редактором сцены.

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