Create an Editor Tool Extension

This tutorial will help you to create your own tool for the world editor, following some paradigms and editor APIs usage that will allow the tool to seamlessly integrate in the editor workflow.

Step 1 - Create the extension’s Lua file

Lets call our tool just MyCoolTool, then we’ll create a new file in the /lua/ge/extensions/editor folder, as myCoolTool.lua. If you have some low level API for this tool’s operations you can create a Lua file in the /lua/ge/extensions/editor/api folder, where all the low level non-UI APIs for editor operations reside.

Step 2 - Writing the base code

The current code will be written in your new editor extension Lua file:

local M = {}
local logTag = 'editor_myCoolTool' -- this is used for logging as a tag
local imgui = ui_imgui -- shortcut for imgui

-- here you can place your imgui calls
-- this function is called every frame when the world editor is active
local function onEditorGui()
  -- if our tool window is not shown, just return
  if not editor.isWindowVisible("myWindow") then return end
  -- begin our window
  if editor.beginWindow("myWindow", "My Window") then
    -- your widgets here
  end
  editor.endWindow()
end

-- this function is a callback which we add below and called when the `Window` menu item is clicked
-- usually used to just show our tool window
local function onWindowMenuItem()
  -- show our window, this will set the visible bool to true
  editor.showWindow("myWindow")
end

-- this function is called when the editor is first initialized
-- it will not be called everytime after that when we toggle editor visibility
-- so just once, per game session, or when Ctrl+L pressed to reload Lua scripts
local function onEditorInitialized()
  -- this will add our tool s aa menu item in the Window menu
  -- the first parameter is the text of the item
  -- the second parameter is a callback to be called when item is clicked
  local editorExtensionInfo = {
    experimental = false -- this extension is not experimental/wip
  }
  editor.addWindowMenuItem("My Cool Tool", onWindowMenuItem, editorExtensionInfo)
  -- register our window by name and a default size
  editor.registerWindow("myWindow", imgui.ImVec2(500, 600))
end

M.onEditorInitialized = onEditorInitialized
M.onEditorGui = onEditorGui

return M

Step 3 - Add an edit mode

If you want your tool to have a toolbar with actions, then you can use the edit modes system which lets you add an edit mode to the editor. An edit mode is a state in which the editor enters and all the actions are focused on a specific task. For example the object tool has 3 actions for move, rotate and scale which appear on the toolbar when this mode is selected. Likewise you can add your own custom edit mode. Edit modes are usually for viewport actions like scene operations (editing or creating specific objects, like road/rivers, paths, special AI tools, etc.) To add an edit mode, you will do the following:

More info: Edit Modes

-- this callback will be called when your edit mode is activated,
-- be it by toolbar button click or via editor api (editor.selectEditMode)
local function testEditModeActivate()
  -- here you can prepare your tool editing mode by activating various gizmos or
  -- setting various edit state for objects/caching etc.
end

-- this will be called when the edit mode is deactivated, either by choosing another
-- edit mode in the toolbar or via code select a new edit mode
local function testEditModeDeactivate()
  -- here you can disable the edit gizmos or delete any temporary data
end

-- this will be called every frame when your edit mode is active
local function testEditModeUpdate()
  -- here you can check for example mouse ray cast scene object intersection
  -- or any other operations you may want to perform on the scene
end

local function onEditorInitialized()
  -- this basically adds your new edit mode to the editor's system
  editor.editModes.myCoolTool =
  {
    onActivate = testEditModeActivate, -- called when our edit mode is activated
    onDeactivate = testEditModeDeactivate, -- called when our edit mode is deactivated
    onUpdate = testEditModeUpdate, -- called every frame when our edit mode is active
    icon = editor.icons.some_icon_here, -- this icon will appear in the edit modes toolbar
    iconTooltip = "My Tool"
  }
  -- your tool can have many edit modes, but usually there is one per tool
  -- we'll learn below how to add a toolbar to our edit mode, in which we can place more actions
end

Step 4 - Add a toolbar for the edit mode

When the edit mode is active, we can add some UI, respectively toolbar buttons for our editing operations. To do that we set the onToolbar callback member of our edit mode and draw toolbar buttons there:

local function myEditModeToolbar()
  if editor.uiIconImageButton(editor.icons.some_icon_here) then
    -- do something here, set some edit mode variable, do some action
  end
  -- this is for the tooltip of the toolbar button
  if imgui.IsItemHovered() then imgui.BeginTooltip() imgui.Text("My Tool 1") imgui.EndTooltip() end
  imgui.SameLine()

  if editor.uiIconImageButton(editor.icons.some_icon_here) then
    -- do something here, set some edit mode variable, do some action
  end
  -- this is for the tooltip of the toolbar button
  if imgui.IsItemHovered() then imgui.BeginTooltip() imgui.Text("My Tool 2") imgui.EndTooltip() end
  imgui.SameLine()
end

local function onEditorInitialized()
  -- this basically adds your new edit mode to the editor's system
  editor.editModes.myCoolTool =
  {
    --....................
    onToolbar = myEditModeToolbar -- set our toolbar callback, called when the edit mode's toolbar must be rendered
  }
end

Step 5 - Add edit mode undo/redo capabilities

When doing various operations in the editor, you can add undo and redo steps which is a must do, so the user experience is aided by the fact operations can be reverted. This doesnt apply to edit modes only, but also to various editing operations in general, in the following example we’ll explore both scenarios.

-- here lets define some simple variables used as test for the undo steps
local testVariable1 = 0
local testVariable2 = 0

-- the undo history action handlers
local MySampleUndoAction1 = {}
local MySampleUndoAction2 = {}

-- this will be executed when the user presses Ctrl+Z or the Undo toolbar button or the Edit->Undo menu item was clicked
-- it must set the old values which were modified
-- of course, this can get as involved as you can see it fit
-- NOTE: do not make this function local
function MySampleUndoAction1:undo(actionData)
  testVariable1 = actionData.oldValue
end

-- this will be execute when the user presses Ctrl+Y or the Redo toolbar button or the Edit->Redo menu item was clicked
-- NOTE: do not make this function local
function MySampleUndoAction1:redo(actionData)
  testVariable1 = actionData.newValue
end

-- same as in MySampleUndoAction1
-- NOTE: do not make this function local
function MySampleUndoAction2:undo(actionData)
  testVariable2 = actionData.oldValue
end

-- same as in MySampleUndoAction1
-- NOTE: do not make this function local
function MySampleUndoAction2:redo(actionData)
  testVariable2 = actionData.newValue
end

-- this will be called by our tool to just set the value of the test variable and handle undo
local function doAction1(value)
  -- we add the history action, name it, set a table with the action data, with old and new values, and the undo action class type
  local action = editor.history:addAction("MySampleAction1", {oldValue = testVariable1, newValue = value}, MySampleUndoAction1)
  -- this will bascally call the redo function of this history action, efectivelly executing the action of new value variable assignment
  editor.history:executeAction(action)
end

-- this will be called by our tool to just set the value of the test variable and handle undo
local function doAction2(value)
  -- we add the history action, name it, set a table with the action data, with old and new values, and the undo action class type
  local action = editor.history:addAction("MySampleAction2", {oldValue = testVariable2, newValue = value}, MySampleUndoAction2)
  -- this will bascally call the redo function of this history action, efectivelly executing the action of new value variable assignment
  editor.history:executeAction(action)
end

-- so in your edit mode toolbar add some action call when a button pressed
local function myEditModeToolbar()

  if editor.uiIconImageButton(editor.icons.some_icon_here) then
    -- lets just execute the action, which will also add the undo history item
    -- just set the value to something
    doAction1(123)
  end

  -- this is for the tooltip of the toolbar button
  if imgui.IsItemHovered() then imgui.BeginTooltip() imgui.Text("My Tool 1") imgui.EndTooltip() end
  imgui.SameLine()

  --.................................................
end

local function onEditorGui()
  -- if our window is not open, just return
  if not editor.isWindowVisible("myTool") then return end

  --.................................................

  -- lets say we have a button inside our tool window that will call doAction2(...)
  -- remember we declared the windowIsOpen above in the previous steps
  if editor.beginWindow("myTool", "My Tool") then
    -- if button pressed, it will execute the undo enabled function doAction2(...), storing a history item also
    -- if then Ctrl+Z will be pressed, the action will be undone
    if imgui.Button("Action 2") then
      -- just set the value to something
      doAction2(321)
    end
  end
  editor.endWindow()

  --.................................................
end

local function onEditorInitialized()
  --.................................................
end

M.onEditorGui = onEditorGui
M.onEditorInitialized = onEditorInitialized

Step 6 - Add custom clipboard operations

When your edit mode is active the cut/copy/paste etc., are redirected to your edit mode callbacks, if present. This way you can implement custom clipboard operations for your needs. When the shortcuts Ctrl+V, Ctrl+C, etc. are issued, the edit mode callbacks are called. Same for when the menu items in Edit are clicked.

NOTE: this step is not necessary if your edit mode or tool does not require custom clipboard operations.

local function onCut()
  -- cut objects/things to clipboard
  -- you can check if your tool window is focused and cut some selection
  -- for example the object tool is using this to cut objects to clipboard
  -- the asset browser is using it to cut assets to clipboard
end

local function onCopy()
  -- copy selected things to clipboard
end

local function onPaste()
  -- paste selected things from clipboard
end

local function onDuplicate()
  -- duplicate selected things
end

local function onSelectAll()
  -- select all things in the current container, be it scene or some list
end

local function onDeleteSelection()
  -- delete the things in the current selection
end

local function onDeselect()
  -- deselect the things in the current selection
end

local function onEditorInitialized()
  editor.editModes.myTool =
  {
  --...........................................
    onCut = onCut,
    onCopy = onCopy,
    onPaste = onPaste,
    onDeleteSelection = onDeleteSelection,
    onDuplicate = onDuplicate,
    onDeselect = onDeselect
  --...........................................
  }
end

--...........................................
M.onEditorInitialized = onEditorInitialized

Step 7 - Add custom preferences in the Editor Preferences

This step is valid when you want to have some settings for your tool that users can customize. To keep things tidy, we have an API to add a preferences page in the Preferences window of the editor.

More info: Adding custom preferences .

Step 8 - Add custom input action map

If your edit mode has some specific shortcuts which need to override any other editor shortcuts while the edit mode is active, then you may want to set a custom input map. See Input Introduction for more info on how to create them.

local function onEditorInitialized()
--...........................................
  editor.editModes.myTool =
  {
    --...........................................
    -- here we set the action map name for this edit mode
    actionMap = "MyToolActionMap"
    --...........................................
  }
end
Last modified: 7/12/2021 21:20

Any further questions?

Join our discord