Read this manual in English

Скрипты редактора

Вы можете создавать пользовательские пункты меню и хуки жизненного цикла редактора, используя файлы 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).

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. Например:

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.