检查器插件

检查器面板支持以插件的形式来创建自定义小工具编辑属性。尽管可以用它来修改内置类型的检查器小工具,但它在需要处理自定义数据类型和资源时尤其有用。你不但可以为特定的属性或者整个对象设计自定义控件,还可以为特定数据类型设计单独的控件。

这份指南会介绍如何使用 EditorInspectorPluginEditorProperty 类来为整数类型创建自定义的界面,将默认的行为替换为一个按了以后就会生成 0 到 99 之间随机数的按钮。

../../../_images/inspector_plugin_example.png

左图为默认行为,右图为最终结果。

创建你的插件

从创建新的空插件开始。

参见

如何创建新插件请参阅 制作插件

让我们假设你的插件文件夹叫做 my_inspector_plugin。那么此时你新建的 addons/my_inspector_plugin 文件夹中就有两个文件:plugin.cfgplugin.gd

和之前一样,plugin.gd 是一个扩展了 EditorPlugin 的脚本,你需要在 _enter_tree_exit_tree 方法中加入新的代码。要创建自己的检查器插件,你必须加载对应的脚本,然后创建并调用 add_inspector_plugin() 来添加实例。禁用插件时,你应该调用 remove_inspector_plugin() 将该实例移除。

备注

因为你在这里读取的是脚本而不是场景包,所以应该使用 new() 而不是 instance()

GDScriptC#

  1. # plugin.gd
  2. tool
  3. extends EditorPlugin
  4. var plugin
  5. func _enter_tree():
  6. plugin = preload("res://addons/my_inspector_plugin/MyInspectorPlugin.gd").new()
  7. add_inspector_plugin(plugin)
  8. func _exit_tree():
  9. remove_inspector_plugin(plugin)
  1. // Plugin.cs
  2. #if TOOLS
  3. using Godot;
  4. [Tool]
  5. public class Plugin : EditorPlugin
  6. {
  7. private MyInspectorPlugin _plugin;
  8. public override void _EnterTree()
  9. {
  10. _plugin = new MyInspectorPlugin();
  11. AddInspectorPlugin(_plugin);
  12. }
  13. public override void _ExitTree()
  14. {
  15. RemoveInspectorPlugin(_plugin);
  16. }
  17. }
  18. #endif

与检查器交互

要和检查器面板交互,你的 MyInspectorPlugin.gd 脚本必须继承自 EditorInspectorPlugin 类。这个类提供了不少虚方法,可以用来控制检查器对属性的处理。

脚本必须实现 can_handle() 方法才能生效。编辑任何 Object 对象时都会调用这个函数,插件想要处理该对象或其属性的话,就必须返回 true

备注

要处理附加在该对象上的 Resource 也同样如此。

另外还有四个方法可以实现,用来往检查器的特定位置添加空间。parse_begin()parse_end() 方法顾名思义,只会在每个对象开始解析和结束解析的时候调用一次。在其中调用 add_custom_control() 就可以在检查器布局的顶部或底部添加控件。

编辑器解析对象时,会调用 parse_category()parse_property() 方法。在这两个函数中,除了 add_custom_control() 以外,你还可以调用 add_property_editor()add_property_editor_for_multiple_properties(),这两个函数是专门用来添加基于 EditorProperty 的控件的。

GDScriptC#

  1. # MyInspectorPlugin.gd
  2. extends EditorInspectorPlugin
  3. var RandomIntEditor = preload("res://addons/my_inspector_plugin/RandomIntEditor.gd")
  4. func can_handle(object):
  5. # We support all objects in this example.
  6. return true
  7. func parse_property(object, type, path, hint, hint_text, usage):
  8. # We handle properties of type integer.
  9. if type == TYPE_INT:
  10. # Create an instance of the custom property editor and register
  11. # it to a specific property path.
  12. add_property_editor(path, RandomIntEditor.new())
  13. # Inform the editor to remove the default property editor for
  14. # this property type.
  15. return true
  16. else:
  17. return false
  1. // MyInspectorPlugin.cs
  2. #if TOOLS
  3. using Godot;
  4. public class MyInspectorPlugin : EditorInspectorPlugin
  5. {
  6. public override bool CanHandle(Object @object)
  7. {
  8. // We support all objects in this example.
  9. return true;
  10. }
  11. public override bool ParseProperty(Object @object, int type, string path, int hint, string hintText, int usage)
  12. {
  13. // We handle properties of type integer.
  14. if (type == (int)Variant.Type.Int)
  15. {
  16. // Create an instance of the custom property editor and register
  17. // it to a specific property path.
  18. AddPropertyEditor(path, new RandomIntEditor());
  19. // Inform the editor to remove the default property editor for
  20. // this property type.
  21. return true;
  22. }
  23. return false;
  24. }
  25. }
  26. #endif

添加编辑属性的界面

EditorProperty 是一种特殊的 Control,可以与检查器面板所编辑的对象进行交互。它本身不显示任何内容,但可以放入其他控件节点,甚至是复杂的场景。

扩展 EditorProperty 的脚本有三个必不可少的部分:

  1. 必须定义 _init() 方法,设置控件节点的结构。

  2. 应该实现 update_property(),处理外部对数据的更改。

  3. 必须在某处使用 emit_changed 触发信号,告知检查器本控件对属性进行了修改。

显示自定义小工具的方法有两种。可以只用默认的 add_child() 方法可以把它显示到属性名称的右边,在 add_child() 之后再调用 set_bottom_editor() 就可以把它显示到名称的下边。

GDScriptC#

  1. # RandomIntEditor.gd
  2. extends EditorProperty
  3. # The main control for editing the property.
  4. var property_control = Button.new()
  5. # An internal value of the property.
  6. var current_value = 0
  7. # A guard against internal changes when the property is updated.
  8. var updating = false
  9. func _init():
  10. # Add the control as a direct child of EditorProperty node.
  11. add_child(property_control)
  12. # Make sure the control is able to retain the focus.
  13. add_focusable(property_control)
  14. # Setup the initial state and connect to the signal to track changes.
  15. refresh_control_text()
  16. property_control.connect("pressed", self, "_on_button_pressed")
  17. func _on_button_pressed():
  18. # Ignore the signal if the property is currently being updated.
  19. if (updating):
  20. return
  21. # Generate a new random integer between 0 and 99.
  22. current_value = randi() % 100
  23. refresh_control_text()
  24. emit_changed(get_edited_property(), current_value)
  25. func update_property():
  26. # Read the current value from the property.
  27. var new_value = get_edited_object()[get_edited_property()]
  28. if (new_value == current_value):
  29. return
  30. # Update the control with the new value.
  31. updating = true
  32. current_value = new_value
  33. refresh_control_text()
  34. updating = false
  35. func refresh_control_text():
  36. property_control.text = "Value: " + str(current_value)
  1. // RandomIntEditor.cs
  2. #if TOOLS
  3. using Godot;
  4. public class RandomIntEditor : EditorProperty
  5. {
  6. // The main control for editing the property.
  7. private Button _propertyControl = new Button();
  8. // An internal value of the property.
  9. private int _currentValue = 0;
  10. // A guard against internal changes when the property is updated.
  11. private bool _updating = false;
  12. public RandomIntEditor()
  13. {
  14. // Add the control as a direct child of EditorProperty node.
  15. AddChild(_propertyControl);
  16. // Make sure the control is able to retain the focus.
  17. AddFocusable(_propertyControl);
  18. // Setup the initial state and connect to the signal to track changes.
  19. RefreshControlText();
  20. _propertyControl.Connect("pressed", this, nameof(OnButtonPressed));
  21. }
  22. private void OnButtonPressed()
  23. {
  24. // Ignore the signal if the property is currently being updated.
  25. if (_updating)
  26. {
  27. return;
  28. }
  29. // Generate a new random integer between 0 and 99.
  30. _currentValue = (int)GD.Randi() % 100;
  31. RefreshControlText();
  32. EmitChanged(GetEditedProperty(), _currentValue);
  33. }
  34. public override void UpdateProperty()
  35. {
  36. // Read the current value from the property.
  37. var newValue = (int)GetEditedObject().Get(GetEditedProperty());
  38. if (newValue == _currentValue)
  39. {
  40. return;
  41. }
  42. // Update the control with the new value.
  43. _updating = true;
  44. _currentValue = newValue;
  45. RefreshControlText();
  46. _updating = false;
  47. }
  48. private void RefreshControlText()
  49. {
  50. _propertyControl.Text = $"Value: {_currentValue}";
  51. }
  52. }
  53. #endif

使用上面的示例代码,可以实现用自定义的小工具替代整数默认的 SpinBox 控件,点击 Button 后生成随机值。