|
@@ -0,0 +1,201 @@
|
|
|
|
+---
|
|
|
|
+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-darwin"` для 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`.
|