Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

Плагины 3D-гизмо

Введение

Плагины 3D-гизмо используются редактором и пользовательскими плагинами для определения гизмо, прикрепленных к любому типу узла Node3D.

В этом руководстве показаны два основных подхода к созданию собственных гизмо. Первый вариант хорошо подходит для простых гизмо и не загромождает структуру плагина, а второй позволяет хранить некоторые данные для каждого гизмо.

Примечание

В этом руководстве предполагается, что вы уже умеете создавать универсальные плагины. Если у вас возникли сомнения, обратитесь к странице Создание плагинов.

EditorNode3DGizmoPlugin

Независимо от выбранного подхода, нам потребуется создать новый EditorNode3DGizmoPlugin. Это позволит нам задать имя для нового типа гизмо и определить другие параметры, например, можно ли скрыть гизмо.

Вот базовая настройка:

# my_custom_gizmo_plugin.gd
extends EditorNode3DGizmoPlugin


func _get_gizmo_name():
    return "CustomNode"
# MyCustomEditorPlugin.gd
@tool
extends EditorPlugin


const MyCustomGizmoPlugin = preload("res://addons/my-addon/my_custom_gizmo_plugin.gd")

var gizmo_plugin = MyCustomGizmoPlugin.new()


func _enter_tree():
    add_node_3d_gizmo_plugin(gizmo_plugin)


func _exit_tree():
    remove_node_3d_gizmo_plugin(gizmo_plugin)

For simple gizmos, inheriting EditorNode3DGizmoPlugin is enough. If you want to store some per-gizmo data, you should go with the second approach.

Простой подход

Первым шагом в нашем пользовательском плагине gizmo является переопределение метода _has_gizmo() так, чтобы он возвращал true, когда параметр узла имеет наш целевой тип.

# ...


func _has_gizmo(node):
    return node is MyCustomNode3D


# ...

Затем мы можем переопределить такие методы, как _redraw() или все методы, связанные с дескриптором (handle).

# ...


func _init():
    create_material("main", Color(1, 0, 0))
    create_handle_material("handles")


func _redraw(gizmo):
    gizmo.clear()

    var node3d = gizmo.get_node_3d()

    var lines = PackedVector3Array()

    lines.push_back(Vector3(0, 1, 0))
    lines.push_back(Vector3(0, node3d.my_custom_value, 0))

    var handles = PackedVector3Array()

    handles.push_back(Vector3(0, 1, 0))
    handles.push_back(Vector3(0, node3d.my_custom_value, 0))

    gizmo.add_lines(lines, get_material("main", gizmo), false)
    gizmo.add_handles(handles, get_material("handles", gizmo), [])


# ...

Обратите внимание, что мы создали материал в методе _init и извлекли его в методе _redraw с помощью метода get_material(). Этот метод извлекает один из вариантов материала в зависимости от состояния гизмо (выбран и/или доступен для редактирования).

Таким образом, финальный плагин будет выглядеть примерно так:

extends EditorNode3DGizmoPlugin


const MyCustomNode3D = preload("res://addons/my-addon/my_custom_node_3d.gd")


func _init():
    create_material("main", Color(1,0,0))
    create_handle_material("handles")


func _has_gizmo(node):
    return node is MyCustomNode3D


func _redraw(gizmo):
    gizmo.clear()

    var node3d = gizmo.get_node_3d()

    var lines = PackedVector3Array()

    lines.push_back(Vector3(0, 1, 0))
    lines.push_back(Vector3(0, node3d.my_custom_value, 0))

    var handles = PackedVector3Array()

    handles.push_back(Vector3(0, 1, 0))
    handles.push_back(Vector3(0, node3d.my_custom_value, 0))

    gizmo.add_lines(lines, get_material("main", gizmo), false)
    gizmo.add_handles(handles, get_material("handles", gizmo), [])


# You should implement the rest of handle-related callbacks
# (_get_handle_name(), _get_handle_value(), _commit_handle(), ...).

Обратите внимание, что мы только что добавили несколько дескрипторов в метод _redraw, но нам все еще необходимо реализовать остальные обратные вызовы, связанные с дескрипторами, в EditorNode3DGizmoPlugin, чтобы получить правильно работающие дескрипторы.

Альтернативный подход

В некоторых случаях мы хотим предоставить собственную реализацию EditorNode3DGizmo, возможно, потому, что мы хотим хранить некоторое состояние в каждом гизмо или потому, что мы портируем старый плагин гизмо и не хотим проходить через процесс переписывания.

В этих случаях все, что нам нужно сделать, это в нашем новом плагине gizmo переопределить _create_gizmo(), чтобы он возвращал нашу пользовательскую реализацию gizmo для узлов Node3D, на которые мы хотим ориентироваться.

# my_custom_gizmo_plugin.gd
extends EditorNode3DGizmoPlugin


const MyCustomNode3D = preload("res://addons/my-addon/my_custom_node_3d.gd")
const MyCustomGizmo = preload("res://addons/my-addon/my_custom_gizmo.gd")


func _init():
    create_material("main", Color(1, 0, 0))
    create_handle_material("handles")


func _create_gizmo(node):
    if node is MyCustomNode3D:
        return MyCustomGizmo.new()
    else:
        return null

Таким образом, всю логику гизмо и методы рисования можно реализовать в новом классе, расширяющем EditorNode3DGizmo, например так:

# my_custom_gizmo.gd
extends EditorNode3DGizmo


# You can store data in the gizmo itself (more useful when working with handles).
var gizmo_size = 3.0


func _redraw():
    clear()

    var node3d = get_node_3d()

    var lines = PackedVector3Array()

    lines.push_back(Vector3(0, 1, 0))
    lines.push_back(Vector3(gizmo_size, node3d.my_custom_value, 0))

    var handles = PackedVector3Array()

    handles.push_back(Vector3(0, 1, 0))
    handles.push_back(Vector3(gizmo_size, node3d.my_custom_value, 0))

    var material = get_plugin().get_material("main", self)
    add_lines(lines, material, false)

    var handles_material = get_plugin().get_material("handles", self)
    add_handles(handles, handles_material, [])


# You should implement the rest of handle-related callbacks
# (_get_handle_name(), _get_handle_value(), _commit_handle(), ...).

Обратите внимание, что мы только что добавили несколько дескрипторов в метод _redraw, но нам все еще необходимо реализовать остальные обратные вызовы, связанные с дескрипторами, в EditorNode3DGizmo, чтобы получить правильно работающие дескрипторы.