@tool и EditorPlugin — расширение редактора
Tool-скрипты, custom inspectors, EditorPlugin, добавление узлов и docks к редактору Godot.
Godot полностью написан на собственном языке для UI — это значит, что сам редактор может быть расширен GDScript-кодом. Никаких C++-плагинов, никаких пересборок.
Два уровня расширения:
@tool-скрипты — обычные узлы с поведением в редакторе. Например, скрипт, который автоматически расставляет деревья по сплайну.- EditorPlugin — настоящий плагин: добавляет узлы, кастомные docks, инспекторы, gizmos.
@tool — скрипт работает в редакторе
Обычный GDScript исполняется только в Play Mode. Добавьте @tool в первой строке — и скрипт будет
работать прямо в Scene View.
@tool
extends Node3D
@export var radius: float = 5.0:
set(value):
radius = value
_rebuild_circle() # перерисовываем при изменении
@export var count: int = 8:
set(value):
count = value
_rebuild_circle()
@export var rebuild: bool = false:
set(value):
if value: _rebuild_circle()
rebuild = false
func _rebuild_circle() -> void:
# Чистим старых детей
for child in get_children():
child.queue_free()
# Расставляем сферы по кругу
for i in count:
var angle := TAU * i / count
var sphere := MeshInstance3D.new()
sphere.mesh = SphereMesh.new()
sphere.position = Vector3(cos(angle), 0, sin(angle)) * radius
add_child(sphere)
sphere.owner = get_tree().edited_scene_root # чтобы дети сохранились в .tscn
Меняете radius в Inspector — круг сразу перерисовывается в Scene View. Сохраняете сцену —
дочерние сферы остаются.
Storybook + Design Tokens: интерактивные превью компонентов с live-параметрами в редакторе. Tool-скрипты — это “Storybook прямо в финальном продукте”.
Если поставите while true: или тяжёлый цикл в @tool-скрипте без условия выхода — зависнет
редактор. Перед сохранением проверяйте: ничего бесконечного, нет блокирующих операций. Если
всё сломалось — запускайте Godot из терминала с флагом --no-window для удаления скрипта.
owner = edited_scene_root — обязательно
Если вы создаёте детей в @tool-скрипте, обязательно проставляйте им owner:
var instance := preload("res://prefab.tscn").instantiate()
add_child(instance)
instance.owner = get_tree().edited_scene_root
Без owner — дети существуют в Scene Tree во время редактирования, но не сохраняются в .tscn.
Сохранение и открытие сцены сделает их невидимыми.
EditorPlugin — настоящий плагин
Полноценный плагин — это EditorPlugin-узел, который Godot загружает при старте редактора. Может:
- Добавить новый тип узла (
add_custom_type). - Добавить dock в боковую панель (
add_control_to_dock). - Добавить пункт в меню Project/Scene (
add_tool_menu_item). - Зарегистрировать gizmo для узла (
add_node_3d_gizmo_plugin). - Перехватить save/load сцены.
Структура плагина
addons/
└── my_plugin/
├── plugin.cfg ← манифест
├── plugin.gd ← главный EditorPlugin
├── my_node.gd ← новый тип узла
└── icon.svg
plugin.cfg:
[plugin]
name="MyAwesomePlugin"
description="Adds X to the editor"
author="You"
version="1.0"
script="plugin.gd"
plugin.gd:
@tool
extends EditorPlugin
func _enter_tree() -> void:
# Регистрируем новый тип узла "TerrainPainter"
add_custom_type(
"TerrainPainter",
"Node3D",
preload("res://addons/my_plugin/my_node.gd"),
preload("res://addons/my_plugin/icon.svg")
)
func _exit_tree() -> void:
remove_custom_type("TerrainPainter")
После сохранения: Project → Project Settings → Plugins → активируйте свой. В меню Add Node появится “TerrainPainter”.
Dock-панель
@tool
extends EditorPlugin
var dock: Control
func _enter_tree() -> void:
dock = preload("res://addons/my_plugin/dock.tscn").instantiate()
add_control_to_dock(EditorPlugin.DOCK_SLOT_LEFT_UR, dock)
func _exit_tree() -> void:
remove_control_from_docks(dock)
dock.queue_free()
dock.tscn — обычная сцена с Control-узлом. Появится в редакторе как настоящая dock-панель,
перетаскиваемая.
Inspector plugin — custom property edits
Для нестандартных property-эдиторов (например, color-palette picker вместо обычного Color):
extends EditorInspectorPlugin
func _can_handle(object: Object) -> bool:
return object is MyCustomResource
func _parse_property(object, type, name, hint, hint_string, usage, wide):
if name == "palette_color":
add_property_editor(name, MyCustomPaletteEditor.new())
return true
return false
Gizmos — 3D-визуализация в редакторе
Хотите видеть в 3D viewport круг радиуса агрессии вашего врага? Зарегистрируйте Node3DGizmoPlugin:
class_name EnemyGizmo extends EditorNode3DGizmoPlugin
func _has_gizmo(node):
return node is Enemy # ваш custom class
func _redraw(gizmo):
var node = gizmo.get_node_3d() as Enemy
gizmo.clear()
# Рисуем sphere wireframe радиуса agro_radius
var lines := PackedVector3Array()
var segments := 32
for i in segments:
var a1 := TAU * i / segments
var a2 := TAU * (i + 1) / segments
lines.append(Vector3(cos(a1), 0, sin(a1)) * node.agro_radius)
lines.append(Vector3(cos(a2), 0, sin(a2)) * node.agro_radius)
gizmo.add_lines(lines, get_material("main", gizmo))
В редакторе вокруг каждого Enemy будет нарисован круг агро — мгновенная визуальная подсказка.
Когда писать плагин vs использовать @tool
- @tool — для одной сцены: процедурная расстановка, генератор, превью.
- EditorPlugin — для переиспользуемой фичи: кастомные узлы, нестандартные инспекторы, dock’и.
Большинство Asset Library-плагинов (Phantom Camera, Dialogic, Terrain3D) — это EditorPlugin. Они активируются через Project Settings → Plugins.
Сравнение с Unity
Unity-аналог:
[ExecuteAlways]атрибут MonoBehaviour ↔ Godot @tool.OnDrawGizmos()/OnDrawGizmosSelected()↔ Godot EditorNode3DGizmoPlugin.- Custom Editor (
[CustomEditor(typeof(...))]) ↔ Godot EditorInspectorPlugin. EditorWindow↔ Godot dock сControl.MenuItem↔ Godotadd_tool_menu_item.
Концепции эквивалентны, имена разные.