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.

簡易方法

第一步,在我們的自訂小工具外掛中,覆寫 _has_gizmo() 方法,讓當傳入節點參數為目標型別時返回 true

# ...


func _has_gizmo(node):
    return node is MyCustomNode3D


# ...

接著我們可以覆寫像是 _redraw() 或所有與控制點相關的方法。

# ...


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), [])


# ...

Note that we created a material in the _init method, and retrieved it in the _redraw method using get_material(). This method retrieves one of the material's variants depending on the state of the gizmo (selected and/or editable).

因此,完整的外掛大致如下:

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(), ...).

Note that we just added some handles in the _redraw method, but we still need to implement the rest of handle-related callbacks in EditorNode3DGizmoPlugin to get properly working handles.

進階方法

有時我們會希望自訂 EditorNode3DGizmo 的實作,例如需要在每個小工具中儲存狀態,或是移植舊版小工具外掛且不想全部重寫。

這種情況下,只需在新的小工具外掛中覆寫 _create_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(), ...).

Note that we just added some handles in the _redraw method, but we still need to implement the rest of handle-related callbacks in EditorNode3DGizmo to get properly working handles.