With plugins, we can do more than react. We can change the inspector to inject new functionality. In this tutorial, we look at how to add a button whose job is to find mesh instances and connect the light and specular viewports to their shaders.
Create a new script next to plugin.gd
called ToonInspector.gd
. This is a tool
script that extends EditorInspectorPlugin
. Its only variable is a single button.
tool class_name ToonInspector extends EditorInspectorPlugin var connect_button: Button
The button only appears when the ToonSceneBuilder
is selected, so we need a can_handle
function to return true
when that is the selected object’s inspector. Otherwise, we return false.
func can_handle(object: Object) -> bool: return object is ToonSceneBuilder
Adding the button happens in parse_category
. Godot calls this when populating the inspector. We don’t care about the category, but we do care about the object type.
func parse_category(object: Object, category: String) -> void: if object is ToonSceneBuilder:
In that if block, we create the button if it’s missing and connect to its signal. Then we call add_custom_control
to add the button to the list. Note how we pass object
along into the signal connection. It’s the ToonSceneBuilder
and we need it to access the viewports.
if not connect_button: connect_button = Button.new() connect_button.text = "Connect ViewportTextures" connect_button.connect( "button_down", self, "_on_Connect_Button_down", [object] ) add_custom_control(connect_button)
In _on_Connect_Button_down
, we grab the light and specular data, create ViewportTexture
s with them, then find every mesh instance that has a ShaderMaterial that has a light_data
and specular_data
parameter and apply that texture to it.
func _on_Connect_Button_down(builder: ToonSceneBuilder) -> void: var light_data: Viewport = builder.light_data var specular_data: Viewport = builder.specular_data var light_texture := light_data.get_texture() var specular_texture := specular_data.get_texture()
Much like nodes created in the editor but not having the owner
property set to save properly, viewport textures need a viewport_path
to save. This path is relative to the root of the scene, so we grab it from the editor.
var top_parent: Node = Engine.get_main_loop().edited_scene_root light_texture.viewport_path = top_parent.get_path_to(light_data) specular_texture.viewport_path = top_parent.get_path_to(specular_data)
Finally, we call a function _set_materials
that recursively sets all MeshInstance
materials appropriately.
_set_materials(top_parent, light_texture, specular_texture) func _set_materials( parent: Node, light_data: ViewportTexture, specular_data: ViewportTexture ) -> void: if parent is MeshInstance:
We loop over each mesh instance’s materials count. If it’s a ShaderMaterial
, we call set_shader_param
to set it to the new viewport textures. If the shader doesn’t have that parameter, then no harm. Otherwise, it’s set accordingly.
for mat in parent.get_surface_material_count(): var material: Material = parent.get_surface_material(mat) if material and material is ShaderMaterial: material.set_shader_param("light_data", light_data) material.set_shader_param( "specular_data", specular_data )
Regardless of the outcome, we recursively step through each of the node’s children to find any further mesh instance.
for child in parent.get_children(): _set_materials(child, light_data, specular_data)
This, above all, highlights that the Godot editor really is a video game cleverly disguised as an editor running on its own game engine. You can apply textures, buttons, and other GUI controls to the inspector, if you know how to look for it.
tool class_name ToonInspector extends EditorInspectorPlugin var connect_button: Button func can_handle(object: Object) -> bool: return object is ToonSceneBuilder func parse_category(object: Object, category: String) -> void: if object is ToonSceneBuilder: if not connect_button: connect_button = Button.new() connect_button.text = "Connect ViewportTextures" connect_button.connect( "button_down", self, "_on_Connect_Button_down", [object] ) add_custom_control(connect_button) func _on_Connect_Button_down(builder: ToonSceneBuilder) -> void: var light_data: Viewport = builder.light_data var specular_data: Viewport = builder.specular_data var light_texture := light_data.get_texture() var specular_texture := specular_data.get_texture() var top_parent: Node = Engine.get_main_loop().edited_scene_root light_texture.viewport_path = top_parent.get_path_to(light_data) specular_texture.viewport_path = top_parent.get_path_to(specular_data) _set_materials(top_parent, light_texture, specular_texture) func _set_materials( parent: Node, light_data: ViewportTexture, specular_data: ViewportTexture ) -> void: if parent is MeshInstance: for mat in parent.get_surface_material_count(): var material: Material = parent.get_surface_material(mat) if material and material is ShaderMaterial: material.set_shader_param("light_data", light_data) material.set_shader_param( "specular_data", specular_data ) for child in parent.get_children(): _set_materials(child, light_data, specular_data)