Work in progress
The content of this page was not yet updated for Godot4.4
and may beoutdated. If you know how to improve this page or you can confirm that it's up to date, feel free toopen a pull request.
3D gizmo plugins
Introduction
3D gizmo plugins are used by the editor and custom plugins to define thegizmos attached to any kind of Node3D node.
This tutorial shows the two main approaches to defining your own customgizmos. The first option works well for simple gizmos and creates less clutter inyour plugin structure, and the second one will let you store some per-gizmo data.
Note
This tutorial assumes you already know how to make generic plugins. Ifin doubt, refer to theMaking plugins page.
The EditorNode3DGizmoPlugin
Regardless of the approach we choose, we will need to create a newEditorNode3DGizmoPlugin. This will allowus to set a name for the new gizmo type and define other behaviors such as whetherthe gizmo can be hidden or not.
This would be a basic setup:
# my_custom_gizmo_plugin.gdextendsEditorNode3DGizmoPluginfunc_get_gizmo_name():return"CustomNode"
# MyCustomEditorPlugin.gd@toolextendsEditorPluginconstMyCustomGizmoPlugin=preload("res://addons/my-addon/my_custom_gizmo_plugin.gd")vargizmo_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, inheritingEditorNode3DGizmoPluginis enough. If you want to store some per-gizmo data or you are porting a Godot 3.0 gizmoto 3.1+, you should go with the second approach.
Simple approach
The first step is to, in our custom gizmo plugin, override the_has_gizmo()method so that it returnstrue
when the node parameter is of our target type.
# ...func_has_gizmo(node):returnnodeisMyCustomNode3D# ...
Then we can override methods like_redraw()or all the handle related ones.
# ...func_init():create_material("main",Color(1,0,0))create_handle_material("handles")func_redraw(gizmo):gizmo.clear()varnode3d=gizmo.get_node_3d()varlines=PackedVector3Array()lines.push_back(Vector3(0,1,0))lines.push_back(Vector3(0,node3d.my_custom_value,0))varhandles=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_redrawmethod usingget_material(). Thismethod retrieves one of the material's variants depending on the state of the gizmo(selected and/or editable).
So the final plugin would look somewhat like this:
extendsEditorNode3DGizmoPluginconstMyCustomNode3D=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):returnnodeisMyCustomNode3Dfunc_redraw(gizmo):gizmo.clear()varnode3d=gizmo.get_node_3d()varlines=PackedVector3Array()lines.push_back(Vector3(0,1,0))lines.push_back(Vector3(0,node3d.my_custom_value,0))varhandles=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 implementthe rest of handle-related callbacks inEditorNode3DGizmoPluginto get properly working handles.
Alternative approach
In some cases we want to provide our own implementation ofEditorNode3DGizmo,maybe because we want to have some state stored in each gizmo or because we are portingan old gizmo plugin and we don't want to go through the rewriting process.
In these cases all we need to do is, in our new gizmo plugin, override_create_gizmo(), so it returns our custom gizmo implementationfor the Node3D nodes we want to target.
# my_custom_gizmo_plugin.gdextendsEditorNode3DGizmoPluginconstMyCustomNode3D=preload("res://addons/my-addon/my_custom_node_3d.gd")constMyCustomGizmo=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):ifnodeisMyCustomNode3D:returnMyCustomGizmo.new()else:returnnull
This way all the gizmo logic and drawing methods can be implemented in a new class extendingEditorNode3DGizmo, like so:
# my_custom_gizmo.gdextendsEditorNode3DGizmo# You can store data in the gizmo itself (more useful when working with handles).vargizmo_size=3.0func_redraw():clear()varnode3d=get_node_3d()varlines=PackedVector3Array()lines.push_back(Vector3(0,1,0))lines.push_back(Vector3(gizmo_size,node3d.my_custom_value,0))varhandles=PackedVector3Array()handles.push_back(Vector3(0,1,0))handles.push_back(Vector3(gizmo_size,node3d.my_custom_value,0))varmaterial=get_plugin().get_material("main",self)add_lines(lines,material,false)varhandles_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 implementthe rest of handle-related callbacks inEditorNode3DGizmoto get properly working handles.