--- title: Скрипты редактора brief: Это руководство объясняет, как расширить редактор с помощью Lua --- # Скрипты редактора Вы можете создавать пользовательские пункты меню и хуки жизненного цикла редактора, используя файлы Lua со специальным расширением: `.editor_script`. Используя эту систему, вы можете настраивать редактор для улучшения рабочего процесса разработки. ## Время выполнения скрипта редактора Сценарии редактора выполняются внутри редактора, в Lua VM, эмулированной Java VM. Все скрипты работают в одном едином окружении, а это означает, что они могут взаимодействовать друг с другом. Вы можете подключать модули Lua, также как и файлы `.script`. Помните версии Lua, которые работают внутри редактора, отличаются, поэтому убедитесь, что ваш общий код совместим. Редактор использует Lua версии 5.2.x, а точнее [luaj](https://github.com/luaj/luaj), которая в настоящее время является единственным жизнеспособным решением для запуска Lua на JVM. Кроме этого, есть некоторые ограничения: - нет пакетов `debug` и `coroutine`; - нет `os.execute` - мы предоставляем более удобный и безопасный способ выполнения shell-скриптов в разделе [actions](#actions); - нет пакетов `os.tmpname` и `io.tmpfile` - в настоящее время скрипты редактора могут обращаться к файлам только внутри каталога проекта; - в настоящее время нет `os.rename`, хотя мы хотим его добавить; - нет `os.exit` и `os.setlocale`. Все расширения редактора, определенные в сценариях редактора, загружаются при открытии проекта. При извлечении библиотек, расширения перезагружаются, поскольку в библиотеках, от которых вы зависите, могут быть новые скрипты редактора. Во время этой перезагрузки изменения в ваших собственных скриптах редактора не учитываются, поскольку вы можете находиться в процессе их изменения. Чтобы перезагрузить и их, нужно выполнить команду Project → Reload Editor Scripts. ## Анатомия `.editor_script` Каждый скрипт редактора должен возвращать модуль, подобный этому: ```lua local M = {} function M.get_commands() -- TODO end return M ``` Затем редактор собирает все скрипты редактора, определенные в проекте и библиотеках, загружает их в единую Lua VM и вызывает их при необходимости (подробнее об этом в разделах [commands](#commands) и [lifecycle hooks](#lifecycle-hooks)). ## API редактора Вы можете взаимодействовать с редактором, используя пакет `editor`, который определяет этот API: - `editor.platform` - строка, либо `"x86_64-win32"` для Windows, `"x86_64-macos"` для macOS, либо `"x86_64-linux"` для Linux. - `editor.get(node_id, property)` - получить значение определённого узла внутри редактора. Узлы в редакторе - это различные сущности, такие как файлы скриптов или коллекций, игровые объекты внутри коллекций, json-файлы, загруженные в качестве ресурсов, и т.д. `node_id` - это пользовательские данные, которые передаются в скрипт редактора самим редактором. Также вместо id узла можно передать путь к ресурсу, например `"/main/game.script"`. `property` - это строка. В настоящее время поддерживаются следующие свойства: - `"path"` - путь к файлу из папки проекта для *resources* - сущностей, существующих в виде файлов. Пример возвращаемого значения: `"/main/game.script"`. - `"text"` - текстовое содержимое ресурса, редактируемое как текст (например, файлы скриптов или json). Пример возвращаемого значения: `"function init(self)\nend"`. Обратите внимание, что это не то же самое, что читать файл лучше с помощью `io.open()`, потому что вы можете редактировать файл, не сохраняя его, и эти правки доступны только при обращении к свойству `"text"`. - некоторые свойства отображаются в Properties, которые были выделены в Outline. Поддерживаются такие типы свойств контура: - strings - booleans - numbers - vec2/vec3/vec4 - resources Обратите внимание, что некоторые из этих свойств могут быть доступны только для чтения, а некоторые могут быть недоступны в различных контекстах, поэтому вы должны использовать `editor.can_get`, прежде чем читать их, и `editor.can_set`, прежде чем заставить редактор установить их. Наведите курсор на имя свойства в Properties, чтобы увидеть всплывающую подсказку с информацией о том, как это свойство именуется в скриптах редактора. Вы можете установить свойства ресурса на nil, установив значение `""`. - `editor.can_get(node_id, property)` — проверьте, можете ли вы получить это свойство, чтобы `editor.get()` не выдал ошибку - `editor.can_set(node_id, property)` — проверьте, не приведет ли действие `"set"` с этим свойством к ошибке ## Команды Если модуль сценария редактора определяет функцию `get_commands`, она будет вызываться при перезагрузке расширения, и возвращенные команды будут доступны для использования внутри редактора в строке меню или в контекстных меню на панелях Assets и Outline. Например: ```lua local M = {} function M.get_commands() return { { label = "Remove Comments", locations = {"Edit", "Assets"}, query = { selection = {type = "resource", cardinality = "one"} }, active = function(opts) local path = editor.get(opts.selection, "path") return ends_with(path, ".lua") or ends_with(path, ".script") end, run = function(opts) local text = editor.get(opts.selection, "text") return { { action = "set", node_id = opts.selection, property = "text", value = strip_comments(text) } } end }, { label = "Minify JSON" locations = {"Assets"}, query = { selection = {type = "resource", cardinality = "one"} }, active = function(opts) return ends_with(editor.get(opts.selection, "path"), ".json") end, run = function(opts) local path = editor.get(opts.selection, "path") return { { action = "shell", command = {"./scripts/minify-json.sh", path:sub(2)} } } end } } end return M ``` Редактор ожидает, что `get_commands()` вернет массив таблиц, каждая из которых описывает отдельную команду. Описание команды состоит из: - `label` (обязательно) - текст пункта меню, который будет отображаться пользователю - `locations` (обязательно) - массив из `"Edit"`, `"View"`, `"Assets"` или `"Outline"`, описывает место, где эта команда должна быть доступна. `"Edit"` и `"View"` означают строку меню сверху, `"Assets"` означает контекстное меню в панели Assets, а `"Outline"` означает контекстное меню в панели Outline. - `query` - способ для команды запросить у редактора необходимую информацию и определить, над какими данными она работает. Для каждого ключа в таблице `query` будет соответствующий ключ в таблице `opts`, который обратные вызовы `active` и `run` получают в качестве аргумента. Поддерживаемые ключи: - `selection` означает, что эта команда действительна, когда есть что-то выбранное, и она действует на это выбранное. - `type` - это тип выбранных узлов, которые интересуют команду, в настоящее время допустимы такие типы: - `"resource"` - в Assets и Outline, ресурс - это выделенный элемент, который имеет соответствующий файл. В строке меню (Edit или View), ресурс - это открытый в данный момент файл; - `"outline"` - то, что может быть показано в контуре. В Outline это выделенный элемент, в строке меню - открытый файл; - `cardinality` определяет, сколько выделенных элементов должно быть. Если `"one"`, выбор, переданный в обратный вызов команды, будет единственным идентификатором узла. Если `"many"`, то выборка, передаваемая в обратный вызов команды, будет массивом из одного или нескольких идентификаторов узлов. - `active` - обратный вызов, который выполняется для проверки того, что команда активна, ожидается, что он вернет булево значение. Если `locations` включают `"Assets"` или `"Outline"`, `active` будет вызван при показе контекстного меню. Если местоположения включают `"Edit"` или `"View"`, active будет вызываться при каждом взаимодействии пользователя, например, при наборе текста на клавиатуре или щелчке мышью, поэтому убедитесь, что `active` работает относительно быстро. - `run` - обратный вызов, который выполняется, когда пользователь выбирает пункт меню, ожидается, что он вернет массив [actions](#actions). ## Действия Действие - это таблица, описывающая, что должен сделать редактор. Каждое действие имеет ключ `action`. Действия бывают двух видов: отменяемые и не отменяемые. ### Отменяемые действия Существующие отменяемые действия: - `"set"` — установка свойства узла в редакторе на некоторое значение. Пример: ```lua { action = "set", node_id = opts.selection, property = "text", value = "current time is " .. os.date() } ``` `"set"` действие требует наличия этих ключей: - `node_id` — идентификатора узла данных пользователя. Также вы можете использовать путь к ресурсу, вместо идентификатора узла, полученного от редактора, например `"/main/game.script"`; - `property` — свойство узла для установки, в настоящее время поддерживается только `"text"`; - `value` — новое значение для свойства. Для свойства `"text"` это должна быть строка. ### Неотменяемые действия Неотменяемые действие, очищает историю отмены, поэтому, если вы хотите отменить такое действие, вам придется использовать другие средства, например, контроль версий. Существующие неотменяемые действия: - `"shell"` — выполняет сценарий оболочки. Пример: ```lua { action = "shell", command = { "./scripts/minify-json.sh", editor.get(opts.selection, "path"):sub(2) -- trim leading "/" } } ``` Действие `"shell"` требует ключ `command`, который представляет собой массив команд и их аргументов. Основное отличие от `os.execute` заключается в том, что поскольку это потенциально опасная операция, редактор покажет диалог подтверждения, спрашивающий пользователя, хочет ли он выполнить эту команду. Он будет помнить каждую команду, которую пользователь уже разрешил. ### Действие при смешивании и побочные эффекты Вы можете смешивать отменяемые и неотменяемые действия. Действия выполняются последовательно, поэтому в зависимости от порядка действий вы потеряете возможность отменить часть команды. Вместо того чтобы возвращать действия из функций, которые их ожидают, вы можете просто читать и записывать в файлы напрямую, используя `io.open()`. Это вызовет перезагрузку ресурсов, которая очистит историю отмены. ## Хуки жизненного цикла Существует специально обработанный файл скрипта редактора: `hooks.editor_script`, расположенный в корне вашего проекта, в том же каталоге, что и *game.project*. Этот и только этот скрипт редактора будет получать события жизненного цикла от редактора. Пример такого файла: ```lua local M = {} function M.on_build_started(opts) local file = io.open("assets/build.json", "w") file:write("{\"build_time\": \"".. os.date() .."\"}") file:close() end return M ``` Каждый хук жизненного цикла может возвращать действия или записывать в файлы в директории проекта. Существующие хуки жизненного цикла, которые могут определять `/hooks.editor_script`: - `on_build_started(opts)` — выполняется, когда игра собирается для запуска локально или на удаленной цели. Ваши изменения, будь то возвращенные действия или обновленное содержимое файлов, появятся в собранной игре. Вызов ошибки из этого хука прервет сборку. `opts` - это таблица, содержащая следующие ключи: - `platform` — строка в формате `%arch%-%os%`, описывающая, для какой платформы он создан, в настоящее время всегда то же значение, что и в `editor.platform`. - `on_build_finished(opts)` — выполняется, когда сборка закончена успешно или нет `opts` представляет собой таблицу со следующими ключами: - `platform` — также как и в `on_build_started` - `success` — успешна ли сборка `true` или `false` - `on_bundle_started(opts)` — выполняется, при создании пакета или сборки HTML5-версии игры. Как и в случае с `on_build_started`, изменения, вызванные этим хуком, появятся в пакете, а ошибки прервут упаковывание. `opts` будет иметь такие ключи: - `output_directory` — путь к файлу, указывающий на каталог с выводом пакета, например `"/path/to/project/build/default/__htmlLaunchDir"` - `platform` — платформа, на которой игра упаковывается. Список возможных вариантов платформы см. в [Bob manual](/manuals/bob). - `variant` — вариант упаковывания `"debug"`, `"release"` или `"headless"` - `on_bundle_finished(opts)` — выполняется, когда упаковывание завершается, независимо от того, успешно или нет. `opts` - это таблица с теми же данными, что и `opts` в `on_bundle_started`, плюс ключ `success`, указывающий на успешность сборки. - `on_target_launched(opts)` — выполняется, когда пользователь запускает игру и она успешно запускается. `opts` содержит ключ `url`, указывающий на запущенный сервис движка, например, `"http://127.0.0.1:35405"` - `on_target_terminated(opts)` — выполняется при закрытии запущенной игры, имеет те же опции, что и `on_target_launched` Обратите внимание, что хуки жизненного цикла в настоящее время являются функцией только для редактора, и они не выполняются Бобом при упаковывании из командной строки. ## Скрипты редактора в библиотеках Вы можете опубликовать библиотеки для использования другими людьми, содержащие команды, и они будут автоматически подхвачены редактором. Хуки, с другой стороны, не могут быть подхвачены автоматически, так как они должны быть определены в файле, который находится в корневой папке проекта, а библиотеки раскрывают только вложенные папки. Это сделано для большего контроля над процессом сборки: вы по-прежнему можете создавать хуки жизненного цикла как простые функции в файлах `.lua`, чтобы пользователи вашей библиотеки могли включать и использовать их в своих проектах `/hooks.editor_script`. Также обратите внимание, что хотя зависимости отображаются в Assets, они не существуют как файлы (это записи в zip-архиве), поэтому в настоящее время нет простого способа выполнить сценарий оболочки, который вы предоставляете в зависимости. Если вам это необходимо, вам придется извлечь предоставленные сценарии, получив их текст с помощью `editor.get()` и записав их куда-нибудь с помощью `file:write()`, например, в папку `build/editor-scripts/your-extension-name`.