|
@@ -9,14 +9,16 @@ brief: Это руководство объясняет, как расширит
|
|
|
|
|
|
## Время выполнения скрипта редактора
|
|
|
|
|
|
-Сценарии редактора выполняются внутри редактора, в 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.
|
|
|
+Сценарии редактора выполняются внутри редактора, в Lua VM, эмулированной Java VM. Все скрипты работают в одном едином окружении, а это означает, что они могут взаимодействовать друг с другом. Вы можете подключать модули Lua, также как и файлы `.script`, но версия Lua, работающая внутри редактора, отличается, поэтому убедитесь, что ваш общий код совместим. Редактор использует Lua версии 5.2.x, а точнее [luaj](https://github.com/luaj/luaj) — в настоящее время это единственное жизнеспособное решение для запуска Lua на JVM. Кроме того, есть некоторые ограничения:
|
|
|
+- отсутствует пакет `debug`;
|
|
|
+- отсутствует `os.execute`, однако предоставляется аналог — `editor.execute()`;
|
|
|
+- отсутствуют `os.tmpname` и `io.tmpfile` — в настоящее время скрипты редактора могут обращаться к файлам только внутри каталога проекта;
|
|
|
+- отсутствует `os.rename`, хотя мы планируем его добавить;
|
|
|
+- отсутствуют `os.exit` и `os.setlocale`;
|
|
|
+- нельзя использовать некоторые функции, выполнение которых занимает длительное время, в контекстах, где редактор ожидает немедленного ответа от скрипта. См. раздел [Режимы выполнения](#режимы-выполнения) для подробностей.
|
|
|
+
|
|
|
+Все расширения редактора, определённые в скриптах редактора, загружаются при открытии проекта. При извлечении библиотек расширения перезагружаются, так как в библиотеках, от которых вы зависите, могут появиться новые скрипты редактора. Во время этой перезагрузки изменения в ваших собственных скриптах редактора не учитываются, поскольку вы можете находиться в процессе их изменения. Чтобы перезагрузить и их, нужно выполнить команду **Project → Reload Editor Scripts**.
|
|
|
+
|
|
|
## Анатомия `.editor_script`
|
|
|
|
|
|
Каждый скрипт редактора должен возвращать модуль, подобный этому:
|
|
@@ -24,7 +26,15 @@ brief: Это руководство объясняет, как расширит
|
|
|
local M = {}
|
|
|
|
|
|
function M.get_commands()
|
|
|
- -- TODO
|
|
|
+ -- TODO - define editor commands
|
|
|
+end
|
|
|
+
|
|
|
+function M.get_language_servers()
|
|
|
+ -- TODO - define language servers
|
|
|
+end
|
|
|
+
|
|
|
+function M.get_prefs_schema()
|
|
|
+ -- TODO - define preferences
|
|
|
end
|
|
|
|
|
|
return M
|
|
@@ -34,20 +44,37 @@ return M
|
|
|
## 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"` с этим свойством к ошибке
|
|
|
+- `editor.platform` — строка: `"x86_64-win32"` для Windows, `"x86_64-macos"` для macOS или `"x86_64-linux"` для Linux.
|
|
|
+- `editor.version` — строка, версия Defold, например `"1.4.8"`.
|
|
|
+- `editor.engine_sha1` — строка, SHA1 движка Defold.
|
|
|
+- `editor.editor_sha1` — строка, SHA1 редактора Defold.
|
|
|
+- `editor.get(node_id, property)` — получить значение узла в редакторе. Узлы в редакторе — это различные сущности, такие как скриптовые или коллекционные файлы, игровые объекты в коллекциях, json-файлы, загруженные как ресурсы и т.д. `node_id` — это userdata, переданная редактором скрипту. Вместо `node_id` можно также передать путь к ресурсу, например `"/main/game.script"`. `property` — строка. Поддерживаются следующие свойства:
|
|
|
+ - `"path"` — путь к файлу из каталога проекта для *ресурсов* — сущностей, существующих как файлы или директории. Пример: `"/main/game.script"`
|
|
|
+ - `"children"` — список путей к дочерним ресурсам (для директорий)
|
|
|
+ - `"text"` — текстовое содержимое ресурса, редактируемое как текст (например, скрипты или json). Пример: `"function init(self)\nend"`. Это не то же самое, что чтение через `io.open()`, так как можно редактировать файл без сохранения, и такие изменения доступны только при обращении к свойству `"text"`.
|
|
|
+ - для атласов: `images` (список узлов изображений в атласе) и `animations` (список узлов анимаций)
|
|
|
+ - для анимаций атласа: `images` (то же, что и в атласе)
|
|
|
+ - для тайлмапов: `layers` (список узлов слоёв в тайлмапе)
|
|
|
+ - для слоёв тайлмапа: `tiles` (неограниченная двумерная сетка тайлов), см. `tilemap.tiles.*` для подробностей
|
|
|
+ - свойства, отображаемые в панели 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)` — проверить, приведёт ли попытка установки свойства с помощью `editor.tx.set()` к ошибке.
|
|
|
+- `editor.create_directory(resource_path)` — создать директорию (и все отсутствующие родительские директории), если она не существует.
|
|
|
+- `editor.delete_directory(resource_path)` — удалить директорию, если она существует, включая все вложенные директории и файлы.
|
|
|
+- `editor.execute(cmd, [...args], [options])` — выполнить shell-команду, при необходимости получив её вывод.
|
|
|
+- `editor.save()` — сохранить все несохранённые изменения на диск.
|
|
|
+- `editor.transact(txs)` — изменить внутреннее состояние редактора с помощью одной или нескольких транзакций, созданных функциями `editor.tx.*`.
|
|
|
+- `editor.ui.*` — различные функции, связанные с пользовательским интерфейсом. См. [UI manual](/manuals/editor-scripts-ui).
|
|
|
+- `editor.prefs.*` — функции для работы с настройками редактора. См. раздел [preferences](#preferences).
|
|
|
+
|
|
|
+Полную документацию по API редактора можно найти [здесь](https://defold.com/ref/alpha/editor/).
|
|
|
|
|
|
## Команды
|
|
|
|
|
@@ -69,14 +96,9 @@ function M.get_commands()
|
|
|
end,
|
|
|
run = function(opts)
|
|
|
local text = editor.get(opts.selection, "text")
|
|
|
- return {
|
|
|
- {
|
|
|
- action = "set",
|
|
|
- node_id = opts.selection,
|
|
|
- property = "text",
|
|
|
- value = strip_comments(text)
|
|
|
- }
|
|
|
- }
|
|
|
+ editor.transact({
|
|
|
+ editor.tx.set(opts.selection, "text", strip_comments(text))
|
|
|
+ })
|
|
|
end
|
|
|
},
|
|
|
{
|
|
@@ -90,12 +112,7 @@ function M.get_commands()
|
|
|
end,
|
|
|
run = function(opts)
|
|
|
local path = editor.get(opts.selection, "path")
|
|
|
- return {
|
|
|
- {
|
|
|
- action = "shell",
|
|
|
- command = {"./scripts/minify-json.sh", path:sub(2)}
|
|
|
- }
|
|
|
- }
|
|
|
+ editor.execute("./scripts/minify-json.sh", path:sub(2))
|
|
|
end
|
|
|
}
|
|
|
}
|
|
@@ -103,25 +120,392 @@ end
|
|
|
|
|
|
return M
|
|
|
```
|
|
|
-Редактор ожидает, что `get_commands()` вернет массив таблиц, каждая из которых описывает отдельную команду. Описание команды состоит из:
|
|
|
+Редактор ожидает, что `get_commands()` вернёт массив таблиц, каждая из которых описывает отдельную команду. Описание команды состоит из следующих полей:
|
|
|
+
|
|
|
+- `label` (обязательный) — текст пункта меню, который будет отображён пользователю.
|
|
|
+- `locations` (обязательный) — массив из следующих значений: `"Edit"`, `"View"`, `"Project"`, `"Debug"`, `"Assets"`, `"Bundle"` или `"Outline"`. Определяет, где команда должна быть доступна. `"Edit"`, `"View"`, `"Project"` и `"Debug"` относятся к верхнему меню, `"Assets"` — к контекстному меню в панели Assets, `"Outline"` — к контекстному меню в панели Outline, а `"Bundle"` — к подменю **Project → Bundle**.
|
|
|
+- `query` — способ для команды запросить у редактора необходимую информацию и определить, с какими данными она работает. Для каждого ключа в таблице `query` будет соответствующий ключ в таблице `opts`, который обратные вызовы `active` и `run` получают в качестве аргумента. Поддерживаемые ключи:
|
|
|
+ - `selection` — команда работает, когда есть выбранные элементы, и действует на них.
|
|
|
+ - `type` — тип интересующих команду узлов. Поддерживаемые значения:
|
|
|
+ - `"resource"` — в Assets и Outline: выбранный ресурс с файлом; в меню — открытый файл;
|
|
|
+ - `"outline"` — элемент в панели Outline; в меню — открытый файл;
|
|
|
+ - `cardinality` — количество выбранных элементов. Значение `"one"` означает, что в `opts.selection` передаётся один id узла. Значение `"many"` — массив из одного или нескольких id узлов.
|
|
|
+ - `argument` — аргумент команды. В настоящее время используется только в командах, расположенных в `"Bundle"`. Значение `true` означает, что пользователь явно выбрал команду упаковки, а `false` — что она вызвана повторно.
|
|
|
+- `id` — строковый идентификатор команды, например, используется для запоминания последней использованной команды упаковки в `prefs`.
|
|
|
+- `active` — функция, вызываемая для определения, активна ли команда. Возвращает `true` или `false`. Для `Assets` и `Outline` вызывается при открытии контекстного меню. Для `Edit`, `View`, `Project` и `Debug` вызывается при каждом взаимодействии пользователя с редактором (клавиатура, мышь и т.д.), поэтому функция должна быть максимально быстрой.
|
|
|
+- `run` — функция, вызываемая при выборе команды пользователем.
|
|
|
+
|
|
|
+### Использование команд для изменения состояния редактора в памяти
|
|
|
+
|
|
|
+Внутри обработчика `run` вы можете запрашивать и изменять состояние редактора в памяти. Запросы выполняются с помощью функции `editor.get()`, с помощью которой можно получить текущее состояние файлов и выделений (если используется `query = {selection = ...}`). Вы можете получить свойство `"text"` у скриптовых файлов, а также некоторые свойства, отображаемые в панели Properties — наведите курсор на имя свойства, чтобы увидеть подсказку с информацией о том, как это свойство называется в скриптах редактора. Изменение состояния редактора выполняется с помощью `editor.transact()`, где вы объединяете одну или несколько модификаций в один шаг, который можно отменить. Например, если вы хотите сбросить трансформацию игрового объекта, можно написать такую команду:
|
|
|
+```lua
|
|
|
+{
|
|
|
+ label = "Reset transform",
|
|
|
+ locations = {"Outline"},
|
|
|
+ query = {selection = {type = "outline", cardinality = "one"}},
|
|
|
+ active = function(opts)
|
|
|
+ local node = opts.selection
|
|
|
+ return editor.can_set(node, "position")
|
|
|
+ and editor.can_set(node, "rotation")
|
|
|
+ and editor.can_set(node, "scale")
|
|
|
+ end,
|
|
|
+ run = function(opts)
|
|
|
+ local node = opts.selection
|
|
|
+ editor.transact({
|
|
|
+ editor.tx.set(node, "position", {0, 0, 0}),
|
|
|
+ editor.tx.set(node, "rotation", {0, 0, 0}),
|
|
|
+ editor.tx.set(node, "scale", {1, 1, 1})
|
|
|
+ })
|
|
|
+ end
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### Редактирование атласов
|
|
|
+
|
|
|
+Помимо чтения и записи свойств атласа, вы можете читать и изменять изображения и анимации в атласе. Атлас определяет свойства-списки узлов `images` и `animations`, а анимации определяют собственное свойство-список узлов `images`. Вы можете использовать шаги транзакций `editor.tx.add`, `editor.tx.remove` и `editor.tx.clear` с этими свойствами.
|
|
|
+
|
|
|
+Например, чтобы добавить изображение в атлас, выполните следующий код в обработчике `run` команды:
|
|
|
+```lua
|
|
|
+editor.transact({
|
|
|
+ editor.tx.add("/main.atlas", "images", {image="/assets/hero.png"})
|
|
|
+})
|
|
|
+```
|
|
|
+To find a set of all images in an atlas, execute the following code:
|
|
|
+```lua
|
|
|
+local all_images = {} ---@type table<string, true>
|
|
|
+-- first, collect all "bare" images
|
|
|
+local image_nodes = editor.get("/main.atlas", "images")
|
|
|
+for i = 1, #image_nodes do
|
|
|
+ all_images[editor.get(image_nodes[i], "image")] = true
|
|
|
+end
|
|
|
+-- second, collect all images used in animations
|
|
|
+local animation_nodes = editor.get("/main.atlas", "animations")
|
|
|
+for i = 1, #animation_nodes do
|
|
|
+ local animation_image_nodes = editor.get(animation_nodes[i], "images")
|
|
|
+ for j = 1, #animation_image_nodes do
|
|
|
+ all_images[editor.get(animation_image_nodes[j], "image")] = true
|
|
|
+ end
|
|
|
+end
|
|
|
+pprint(all_images)
|
|
|
+-- {
|
|
|
+-- ["/assets/hero.png"] = true,
|
|
|
+-- ["/assets/enemy.png"] = true,
|
|
|
+-- }}
|
|
|
+```
|
|
|
+To replace all animations in an atlas:
|
|
|
+```lua
|
|
|
+editor.transact({
|
|
|
+ editor.tx.clear("/main.atlas", "animations"),
|
|
|
+ editor.tx.add("/main.atlas", "animations", {
|
|
|
+ id = "hero_run",
|
|
|
+ images = {
|
|
|
+ {image = "/assets/hero_run_1.png"},
|
|
|
+ {image = "/assets/hero_run_2.png"},
|
|
|
+ {image = "/assets/hero_run_3.png"},
|
|
|
+ {image = "/assets/hero_run_4.png"}
|
|
|
+ }
|
|
|
+ })
|
|
|
+})
|
|
|
+```
|
|
|
+
|
|
|
+#### Редактирование tilesource
|
|
|
+
|
|
|
+Помимо свойств, отображаемых в Outline, tilesource определяет следующие дополнительные свойства:
|
|
|
+- `animations` — список узлов анимаций tilesource
|
|
|
+- `collision_groups` — список узлов групп столкновений tilesource
|
|
|
+- `tile_collision_groups` — таблица назначений групп столкновений для конкретных тайлов в tilesource
|
|
|
+
|
|
|
+Например, вот как можно настроить tilesource:
|
|
|
+```lua
|
|
|
+local tilesource = "/game/world.tilesource"
|
|
|
+editor.transact({
|
|
|
+ editor.tx.add(tilesource, "animations", {id = "idle", start_tile = 1, end_tile = 1}),
|
|
|
+ editor.tx.add(tilesource, "animations", {id = "walk", start_tile = 2, end_tile = 6, fps = 10}),
|
|
|
+ editor.tx.add(tilesource, "collision_groups", {id = "player"}),
|
|
|
+ editor.tx.add(tilesource, "collision_groups", {id = "obstacle"}),
|
|
|
+ editor.tx.set(tilesource, "tile_collision_groups", {
|
|
|
+ [1] = "player",
|
|
|
+ [7] = "obstacle",
|
|
|
+ [8] = "obstacle"
|
|
|
+ })
|
|
|
+})
|
|
|
+```
|
|
|
+
|
|
|
+#### Редактирование tilemap
|
|
|
+
|
|
|
+Tilemap определяет свойство `layers` — список узлов слоёв tilemap. Каждый слой также имеет свойство `tiles`, представляющее собой неограниченную двумерную сетку тайлов на этом слое. Это отличается от движка: тайлы не имеют жёстких границ и могут быть размещены в любых координатах, включая отрицательные. Для редактирования тайлов API скриптов редактора предоставляет модуль `tilemap.tiles` со следующими функциями:
|
|
|
+- `tilemap.tiles.new()` — создаёт новую структуру данных для неограниченной двумерной сетки тайлов (в редакторе, в отличие от движка, tilemap не имеет границ, и координаты могут быть отрицательными);
|
|
|
+- `tilemap.tiles.get_tile(tiles, x, y)` — возвращает индекс тайла в заданной координате;
|
|
|
+- `tilemap.tiles.get_info(tiles, x, y)` — возвращает полную информацию о тайле по координатам (структура данных совпадает с `tilemap.get_tile_info` из движка);
|
|
|
+- `tilemap.tiles.iterator(tiles)` — создаёт итератор по всем тайлам в tilemap;
|
|
|
+- `tilemap.tiles.clear(tiles)` — удаляет все тайлы из tilemap;
|
|
|
+- `tilemap.tiles.set(tiles, x, y, tile_or_info)` — задаёт тайл в определённой координате;
|
|
|
+- `tilemap.tiles.remove(tiles, x, y)` — удаляет тайл из определённой координаты.
|
|
|
+
|
|
|
+Пример: как напечатать содержимое всей tilemap:
|
|
|
+```lua
|
|
|
+local layers = editor.get("/level.tilemap", "layers")
|
|
|
+for i = 1, #layers do
|
|
|
+ local layer = layers[i]
|
|
|
+ local id = editor.get(layer, "id")
|
|
|
+ local tiles = editor.get(layer, "tiles")
|
|
|
+ print("layer " .. id .. ": {")
|
|
|
+ for x, y, tile in tilemap.tiles.iterator(tiles) do
|
|
|
+ print(" [" .. x .. ", " .. y .. "] = " .. tile)
|
|
|
+ end
|
|
|
+ print("}")
|
|
|
+end
|
|
|
+```
|
|
|
+
|
|
|
+Вот пример, который показывает, как добавить слой с тайлами в tilemap:
|
|
|
+```lua
|
|
|
+local tiles = tilemap.tiles.new()
|
|
|
+tilemap.tiles.set(tiles, 1, 1, 2)
|
|
|
+editor.transact({
|
|
|
+ editor.tx.add("/level.tilemap", "layers", {
|
|
|
+ id = "new_layer",
|
|
|
+ tiles = tiles
|
|
|
+ })
|
|
|
+})
|
|
|
+```
|
|
|
+
|
|
|
+### Использование shell-команд
|
|
|
+
|
|
|
+Внутри обработчика `run` вы можете записывать данные в файлы (используя модуль `io`) и выполнять shell-команды (с помощью функции `editor.execute()`). При выполнении shell-команды можно захватить её вывод как строку и использовать далее в коде. Например, если вы хотите создать команду для форматирования JSON, которая вызывает глобально установленный [`jq`](https://jqlang.github.io/jq/), можно написать следующую команду:
|
|
|
+```lua
|
|
|
+{
|
|
|
+ label = "Format JSON",
|
|
|
+ locations = {"Assets"},
|
|
|
+ query = {selection = {type = "resource", cardinality = "one"}},
|
|
|
+ action = function(opts)
|
|
|
+ local path = editor.get(opts.selection, "path")
|
|
|
+ return path:match(".json$") ~= nil
|
|
|
+ end,
|
|
|
+ run = function(opts)
|
|
|
+ local text = editor.get(opts.selection, "text")
|
|
|
+ local new_text = editor.execute("jq", "-n", "--argjson", "data", text, "$data", {
|
|
|
+ reload_resources = false, -- don't reload resources since jq does not touch disk
|
|
|
+ out = "capture" -- return text output instead of nothing
|
|
|
+ })
|
|
|
+ editor.transact({ editor.tx.set(opts.selection, "text", new_text) })
|
|
|
+ end
|
|
|
+}
|
|
|
+```
|
|
|
+Поскольку эта команда вызывает shell-программу в режиме только для чтения (и уведомляет редактор об этом с помощью `reload_resources = false`), вы получаете преимущество: действие можно отменить.
|
|
|
+
|
|
|
+::: sidenote
|
|
|
+Если вы хотите распространять свой скрипт редактора как библиотеку, возможно, стоит включить бинарную программу для платформ редактора в состав зависимости. Подробнее см. в разделе [Скрипты редактора в библиотеках](#editor-scripts-in-libraries).
|
|
|
+:::
|
|
|
+
|
|
|
+## Хуки жизненного цикла
|
|
|
+
|
|
|
+Существует специально обработанный файл скрипта редактора: `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)` — вызывается, когда игра собирается для запуска локально или на удалённой платформе через Project Build или Debug Start. Изменения, произведённые в этом хуке, попадут в собранную игру. Ошибка, выброшенная из этого хука, прервёт сборку. `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](/manuals/bob).
|
|
|
+ - `variant` — тип сборки: `"debug"`, `"release"` или `"headless"`
|
|
|
+- `on_bundle_finished(opts)` — вызывается по завершении сборки, вне зависимости от результата. `opts` содержит те же поля, что и `on_bundle_started`, плюс ключ `success`.
|
|
|
+- `on_target_launched(opts)` — вызывается, когда пользователь запускает игру, и она успешно стартует. `opts` содержит ключ `url`, указывающий на адрес запущенного движка, например `"http://127.0.0.1:35405"`
|
|
|
+- `on_target_terminated(opts)` — вызывается, когда запущенная игра закрывается. Использует те же `opts`, что и `on_target_launched`
|
|
|
+
|
|
|
+Обратите внимание: хуки жизненного цикла работают только в редакторе и не выполняются при сборке через Bob из командной строки.
|
|
|
+
|
|
|
+## Языковые серверы
|
|
|
+
|
|
|
+Редактор поддерживает подмножество [Language Server Protocol](https://microsoft.github.io/language-server-protocol/). Хотя в будущем мы планируем расширить поддержку LSP-функций, на данный момент редактор может только отображать диагностические сообщения (например, предупреждения линтера) и предлагать автодополнение.
|
|
|
+
|
|
|
+Чтобы определить языковой сервер, необходимо отредактировать функцию `get_language_servers` в вашем скрипте редактора следующим образом:
|
|
|
+
|
|
|
+```lua
|
|
|
+function M.get_language_servers()
|
|
|
+ local command = 'build/plugins/my-ext/plugins/bin/' .. editor.platform .. '/lua-lsp'
|
|
|
+ if editor.platform == 'x86_64-win32' then
|
|
|
+ command = command .. '.exe'
|
|
|
+ end
|
|
|
+ return {
|
|
|
+ {
|
|
|
+ languages = {'lua'},
|
|
|
+ watched_files = {
|
|
|
+ { pattern = '**/.luacheckrc' }
|
|
|
+ },
|
|
|
+ command = {command, '--stdio'}
|
|
|
+ }
|
|
|
+ }
|
|
|
+end
|
|
|
+```
|
|
|
+Редактор запустит языковой сервер, используя указанный `command`, через стандартный ввод и вывод процесса сервера для взаимодействия.
|
|
|
+
|
|
|
+Таблица определения языкового сервера может содержать следующие поля:
|
|
|
+- `languages` (обязательно) — список языков, поддерживаемых сервером, как указано [здесь](https://code.visualstudio.com/docs/languages/identifiers#_known-language-identifiers) (также поддерживаются расширения файлов);
|
|
|
+- `command` (обязательно) — массив, содержащий команду и её аргументы;
|
|
|
+- `watched_files` — массив таблиц с ключом `pattern` (глоб-шаблон), которые вызовут уведомление сервера о [изменении отслеживаемых файлов](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_didChangeWatchedFiles).
|
|
|
+
|
|
|
+## HTTP-сервер
|
|
|
+
|
|
|
+Каждый запущенный экземпляр редактора включает в себя HTTP-сервер. Этот сервер можно расширить с помощью скриптов редактора. Чтобы расширить HTTP-сервер редактора, необходимо добавить функцию `get_http_server_routes` в ваш скрипт редактора — она должна возвращать дополнительные маршруты:
|
|
|
+```lua
|
|
|
+print("My route: " .. http.server.url .. "/my-extension")
|
|
|
+
|
|
|
+function M.get_http_server_routes()
|
|
|
+ return {
|
|
|
+ http.server.route("/my-extension", "GET", function(request)
|
|
|
+ return http.server.response(200, "Hello world!")
|
|
|
+ end)
|
|
|
+ }
|
|
|
+end
|
|
|
+```
|
|
|
+После перезагрузки скриптов редактора вы увидите следующий вывод в консоли: `My route: http://0.0.0.0:12345/my-extension`. Если вы откроете эту ссылку в браузере, вы увидите сообщение `"Hello world!"`.
|
|
|
+
|
|
|
+Аргумент `request` на входе — это простая таблица Lua с информацией о запросе. Она содержит следующие ключи: `path` (URL-путь, начинающийся с `/`), `method` (HTTP-метод запроса, например `"GET"`), `headers` (таблица с именами заголовков в нижнем регистре), и, опционально, `query` (строка запроса) и `body` (если маршрут указывает, как интерпретировать тело запроса). Например, если вы хотите создать маршрут, который принимает тело в формате JSON, определите его с параметром-конвертером `"json"`:
|
|
|
+```lua
|
|
|
+http.server.route("/my-extension/echo-request", "POST", "json", function(request)
|
|
|
+ return http.server.json_response(request)
|
|
|
+end)
|
|
|
+```
|
|
|
+Вы можете протестировать этот эндпоинт в командной строке с помощью `curl` и `jq`:
|
|
|
+```sh
|
|
|
+curl 'http://0.0.0.0:12345/my-extension/echo-request?q=1' -X POST --data '{"input": "json"}' | jq
|
|
|
+{
|
|
|
+ "path": "/my-extension/echo-request",
|
|
|
+ "method": "POST",
|
|
|
+ "query": "q=1",
|
|
|
+ "headers": {
|
|
|
+ "host": "0.0.0.0:12345",
|
|
|
+ "content-type": "application/x-www-form-urlencoded",
|
|
|
+ "accept": "*/*",
|
|
|
+ "user-agent": "curl/8.7.1",
|
|
|
+ "content-length": "17"
|
|
|
+ },
|
|
|
+ "body": {
|
|
|
+ "input": "json"
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+Путь маршрута поддерживает шаблоны, которые могут быть извлечены из пути запроса и переданы в функцию-обработчик как часть запроса, например:
|
|
|
+```lua
|
|
|
+http.server.route("/my-extension/setting/{category}.{key}", function(request)
|
|
|
+ return http.server.response(200, tostring(editor.get("/game.project", request.category .. "." .. request.key)))
|
|
|
+end)
|
|
|
+```
|
|
|
+Теперь, если вы откроете, например, `http://0.0.0.0:12345/my-extension/setting/project.title`, вы увидите название вашей игры, взятое из файла `/game.project`.
|
|
|
+
|
|
|
+Помимо шаблона с одним сегментом пути, вы также можете сопоставить оставшуюся часть URL-пути, используя синтаксис `{*name}`. Например, вот простой эндпоинт файлового сервера, который обслуживает файлы из корня проекта:
|
|
|
+```lua
|
|
|
+http.server.route("/my-extension/files/{*file}", function(request)
|
|
|
+ local attrs = editor.external_file_attributes(request.file)
|
|
|
+ if attrs.is_file then
|
|
|
+ return http.server.external_file_response(request.file)
|
|
|
+ else
|
|
|
+ return 404
|
|
|
+ end
|
|
|
+end)
|
|
|
+```
|
|
|
+Теперь, если открыть, например, `http://0.0.0.0:12345/my-extension/files/main/main.collection` в браузере, отобразится содержимое файла `main/main.collection`.
|
|
|
+
|
|
|
+## Скрипты редактора в библиотеках
|
|
|
+
|
|
|
+Вы можете публиковать библиотеки с командами для использования другими пользователями, и редактор автоматически их подхватит. Хуки, с другой стороны, не могут быть подхвачены автоматически, так как они должны быть определены в файле, расположенном в корневой папке проекта, в то время как библиотеки предоставляют только подкаталоги. Это сделано для того, чтобы предоставить больше контроля над процессом сборки: вы всё ещё можете создавать хуки жизненного цикла как обычные функции в `.lua`-файлах, чтобы пользователи вашей библиотеки могли подключать их в своём `/hooks.editor_script`.
|
|
|
+
|
|
|
+Также обратите внимание, что хотя зависимости отображаются в окне Assets, они не существуют как обычные файлы (это записи внутри zip-архива). Однако можно заставить редактор извлекать определённые файлы из зависимостей в папку `build/plugins/`. Чтобы это сделать, необходимо создать файл `ext.manifest` в вашей библиотеке и затем создать папку `plugins/bin/${platform}` в той же директории, где находится `ext.manifest`. Файлы в этой папке будут автоматически извлечены в `/build/plugins/${extension-path}/plugins/bin/${platform}`, чтобы ваши скрипты редактора могли ссылаться на них.
|
|
|
+
|
|
|
+## Предпочтения
|
|
|
+
|
|
|
+Скрипты редактора могут определять и использовать предпочтения — постоянные, не зафиксированные в системе управления версиями данные, хранящиеся на компьютере пользователя. Эти предпочтения обладают тремя основными характеристиками:
|
|
|
+- типизированные: каждое предпочтение имеет определение схемы, которое включает тип данных и дополнительные метаданные, такие как значение по умолчанию;
|
|
|
+- с областью действия: предпочтения могут иметь область действия на уровне проекта или на уровне пользователя;
|
|
|
+- вложенные: каждый ключ предпочтения представляет собой строку с точечной нотацией, где первый сегмент определяет скрипт редактора, а остальные — вложенные свойства.
|
|
|
+
|
|
|
+Все предпочтения должны быть зарегистрированы путём определения их схемы:
|
|
|
+```lua
|
|
|
+function M.get_prefs_schema()
|
|
|
+ return {
|
|
|
+ ["my_json_formatter.jq_path"] = editor.prefs.schema.string(),
|
|
|
+ ["my_json_formatter.indent.size"] = editor.prefs.schema.integer({default = 2, scope = editor.prefs.SCOPE.PROJECT}),
|
|
|
+ ["my_json_formatter.indent.type"] = editor.prefs.schema.enum({values = {"spaces", "tabs"}, scope = editor.prefs.SCOPE.PROJECT}),
|
|
|
+ }
|
|
|
+end
|
|
|
+```
|
|
|
+После перезагрузки такого скрипта редактора редактор зарегистрирует эту схему. Затем скрипт может получать и задавать предпочтения, например:
|
|
|
+```lua
|
|
|
+-- Get a specific preference
|
|
|
+editor.prefs.get("my_json_formatter.indent.type")
|
|
|
+-- Returns: "spaces"
|
|
|
+
|
|
|
+-- Get an entire preference group
|
|
|
+editor.prefs.get("my_json_formatter")
|
|
|
+-- Returns:
|
|
|
+-- {
|
|
|
+-- jq_path = "",
|
|
|
+-- indent = {
|
|
|
+-- size = 2,
|
|
|
+-- type = "spaces"
|
|
|
+-- }
|
|
|
+-- }
|
|
|
+
|
|
|
+-- Set multiple nested preferences at once
|
|
|
+editor.prefs.set("my_json_formatter.indent", {
|
|
|
+ type = "tabs",
|
|
|
+ size = 1
|
|
|
+})
|
|
|
+```
|
|
|
+
|
|
|
+## Режимы выполнения
|
|
|
+
|
|
|
+Среда выполнения скриптов редактора использует 2 режима выполнения, которые в основном прозрачны для скриптов: **немедленный (immediate)** и **долгосрочный (long-running)**.
|
|
|
+
|
|
|
+**Немедленный** режим используется, когда редактору необходимо получить ответ от скрипта как можно быстрее. Например, обратные вызовы `active` у команд меню выполняются в немедленном режиме, поскольку эти проверки выполняются в UI-потоке редактора в ответ на действия пользователя и должны обновить интерфейс в пределах одного кадра.
|
|
|
+
|
|
|
+**Долгосрочный** режим используется, когда редактору не требуется мгновенный ответ от скрипта. Например, обратные вызовы `run` у команд меню выполняются в **долгосрочном** режиме, позволяя скрипту выполнять работу дольше.
|
|
|
|
|
|
-- `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).
|
|
|
+Некоторые функции, используемые в скриптах редактора, могут выполняться достаточно долго. Например, `editor.execute("git", "status", {reload_resources=false, out="capture"})` может занять до секунды на достаточно крупных проектах. Чтобы сохранить отзывчивость редактора и производительность, функции, которые могут выполняться долго, запрещены в контекстах, где от скрипта ожидается немедленный ответ. Попытка использовать такую функцию в немедленном контексте приведёт к ошибке: `Cannot use long-running editor function in immediate context`. Чтобы избежать этой ошибки, избегайте вызова таких функций в немедленных контекстах.
|
|
|
+
|
|
|
+Следующие функции считаются долгосрочными и не могут использоваться в немедленном режиме:
|
|
|
+- `editor.create_directory()`, `editor.delete_directory()`, `editor.save()`, `os.remove()` и `file:write()`: эти функции изменяют файлы на диске, вызывая необходимость синхронизации состояния ресурсов в памяти с данными на диске, что может занять несколько секунд в больших проектах.
|
|
|
+- `editor.execute()`: выполнение команд оболочки может занимать непредсказуемое количество времени.
|
|
|
+- `editor.transact()`: крупные транзакции с широко используемыми узлами могут занимать сотни миллисекунд, что слишком долго для обновления UI.
|
|
|
+
|
|
|
+Следующие контексты выполнения кода используют немедленный режим:
|
|
|
+- Обратные вызовы `active` у команд меню: редактору нужен ответ от скрипта в пределах одного UI-кадра.
|
|
|
+- Верхний уровень скриптов редактора: предполагается, что перезагрузка скриптов не должна иметь побочных эффектов.
|
|
|
|
|
|
## Действия
|
|
|
|
|
|
-Действие - это таблица, описывающая, что должен сделать редактор. Каждое действие имеет ключ `action`. Действия бывают двух видов: отменяемые и не отменяемые.
|
|
|
+::: sidenote
|
|
|
+Ранее редактор взаимодействовал с Lua VM в блокирующем режиме, поэтому была строгая необходимость в том, чтобы скрипты редактора не блокировали выполнение, поскольку некоторые взаимодействия должны выполняться из UI-потока редактора. По этой причине, например, отсутствовали функции `editor.execute()` и `editor.transact()`. Выполнение скриптов и изменение состояния редактора осуществлялось путём возврата массива "действий" из хуков и обработчиков `run` команд.
|
|
|
+
|
|
|
+Теперь редактор взаимодействует с Lua VM в неблокирующем режиме, поэтому в этих действиях больше нет необходимости: использование функций, таких как `editor.execute()`, более удобно, лаконично и мощно. Эти действия теперь считаются **УСТАРЕВШИМИ**, хотя мы не планируем их удаление.
|
|
|
+:::
|
|
|
+
|
|
|
+Скрипты редактора могут возвращать массив действий из функции `run` команды или из функций хуков в файле `/hooks.editor_script`. Эти действия затем будут выполнены редактором.
|
|
|
+
|
|
|
+Действие — это таблица, описывающая, что редактор должен сделать. Каждое действие содержит ключ `action`. Существуют два типа действий: отменяемые и неотменяемые.
|
|
|
|
|
|
### Отменяемые действия
|
|
|
|
|
|
+::: sidenote
|
|
|
+Предпочтительно использовать `editor.transact()`.
|
|
|
+:::
|
|
|
+
|
|
|
+Отменяемое действие может быть отменено после его выполнения. Если команда возвращает несколько отменяемых действий, они выполняются вместе и отменяются также вместе. Следует использовать отменяемые действия, когда это возможно. Их недостаток заключается в том, что они более ограничены по возможностям.
|
|
|
|
|
|
Существующие отменяемые действия:
|
|
|
- `"set"` — установка свойства узла в редакторе на некоторое значение. Пример:
|
|
@@ -140,6 +524,10 @@ return M
|
|
|
|
|
|
### Неотменяемые действия
|
|
|
|
|
|
+::: sidenote
|
|
|
+Предпочтительно использовать `editor.execute()`.
|
|
|
+:::
|
|
|
+
|
|
|
Неотменяемые действие, очищает историю отмены, поэтому, если вы хотите отменить такое действие, вам придется использовать другие средства, например, контроль версий.
|
|
|
|
|
|
Существующие неотменяемые действия:
|
|
@@ -153,49 +541,10 @@ return M
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
- Действие `"shell"` требует ключ `command`, который представляет собой массив команд и их аргументов. Основное отличие от `os.execute` заключается в том, что поскольку это потенциально опасная операция, редактор покажет диалог подтверждения, спрашивающий пользователя, хочет ли он выполнить эту команду. Он будет помнить каждую команду, которую пользователь уже разрешил.
|
|
|
-
|
|
|
-### Действие при смешивании и побочные эффекты
|
|
|
-
|
|
|
-Вы можете смешивать отменяемые и неотменяемые действия. Действия выполняются последовательно, поэтому в зависимости от порядка действий вы потеряете возможность отменить часть команды.
|
|
|
-
|
|
|
-Вместо того чтобы возвращать действия из функций, которые их ожидают, вы можете просто читать и записывать в файлы напрямую, используя `io.open()`. Это вызовет перезагрузку ресурсов, которая очистит историю отмены.
|
|
|
-
|
|
|
-## Хуки жизненного цикла
|
|
|
+ Действие `"shell"` требует ключ `command`, который представляет собой массив с командой и её аргументами.
|
|
|
|
|
|
-Существует специально обработанный файл скрипта редактора: `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`.
|
|
|
+Вместо возврата действий из функций, которые этого ожидают, вы можете напрямую читать и записывать файлы с помощью `io.open()`. Это вызовет перезагрузку ресурса и очистит историю отмен.
|