Work in progress

The content of this page was not yet updated for Godot 4.4 and may be outdated. If you know how to improve this page or you can confirm that it's up to date, feel free to open a pull request.

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)

對於簡單的小工具,只要繼承 EditorNode3DGizmoPlugin 即可。如果你需要為每個小工具儲存資料,或是從 Godot 3.0 移植小工具到 3.1 以上,建議使用第二種方法。

簡易方法

第一步,在我們的自訂小工具外掛中,覆寫 _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), [])


# ...

請注意,我們在 _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 的實作,例如需要在每個小工具中儲存狀態,或是移植舊版小工具外掛且不想全部重寫。

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

請注意,我們僅在 _redraw 方法內新增了一些控制點,但仍需要在 EditorNode3DGizmo 中補齊其他與控制點相關的回呼,才能讓控制點運作正常。