Вы можете создавать пользовательские пункты меню и хуки жизненного цикла редактора, используя файлы Lua со специальным расширением: .editor_script
. Используя эту систему, вы можете настраивать редактор для улучшения рабочего процесса разработки.
Сценарии редактора выполняются внутри редактора, в Lua VM, эмулированной Java VM. Все скрипты работают в одном едином окружении, а это означает, что они могут взаимодействовать друг с другом. Вы можете подключать модули Lua, также как и файлы .script
. Помните версии Lua, которые работают внутри редактора, отличаются, поэтому убедитесь, что ваш общий код совместим. Редактор использует Lua версии 5.2.x, а точнее luaj, которая в настоящее время является единственным жизнеспособным решением для запуска Lua на JVM. Кроме этого, есть некоторые ограничения:
debug
и coroutine
;os.execute
- мы предоставляем более удобный и безопасный способ выполнения shell-скриптов в разделе actions;os.tmpname
и io.tmpfile
- в настоящее время скрипты редактора могут обращаться к файлам только внутри каталога проекта;os.rename
, хотя мы хотим его добавить;os.exit
и os.setlocale
.Все расширения редактора, определенные в сценариях редактора, загружаются при открытии проекта. При извлечении библиотек, расширения перезагружаются, поскольку в библиотеках, от которых вы зависите, могут быть новые скрипты редактора. Во время этой перезагрузки изменения в ваших собственных скриптах редактора не учитываются, поскольку вы можете находиться в процессе их изменения. Чтобы перезагрузить и их, нужно выполнить команду Project → Reload Editor Scripts.
.editor_script
Каждый скрипт редактора должен возвращать модуль, подобный этому:
local M = {}
function M.get_commands()
-- TODO
end
return M
Затем редактор собирает все скрипты редактора, определенные в проекте и библиотеках, загружает их в единую Lua VM и вызывает их при необходимости (подробнее об этом в разделах commands и lifecycle hooks).
Вы можете взаимодействовать с редактором, используя пакет 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"
.Обратите внимание, что некоторые из этих свойств могут быть доступны только для чтения, а некоторые могут быть недоступны в различных контекстах, поэтому вы должны использовать 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. Например:
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.Действие - это таблица, описывающая, что должен сделать редактор. Каждое действие имеет ключ action
. Действия бывают двух видов: отменяемые и не отменяемые.
Существующие отменяемые действия:
"set"
— установка свойства узла в редакторе на некоторое значение. Пример:
{
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"
— выполняет сценарий оболочки. Пример:
{
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. Этот и только этот скрипт редактора будет получать события жизненного цикла от редактора. Пример такого файла:
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.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
.
Did you spot an error or do you have a suggestion? Please let us know on GitHub!
GITHUB