Browse Source

Add tutorial for custom spatial gizmos plugin.

JFonS 6 years ago
parent
commit
8c2470bf3b
2 changed files with 220 additions and 0 deletions
  1. 1 0
      tutorials/plugins/editor/index.rst
  2. 219 0
      tutorials/plugins/editor/spatial_gizmos.rst

+ 1 - 0
tutorials/plugins/editor/index.rst

@@ -7,3 +7,4 @@ Editor plugins
 
 
    making_plugins
    making_plugins
    import_plugins
    import_plugins
+   spatial_gizmos

+ 219 - 0
tutorials/plugins/editor/spatial_gizmos.rst

@@ -0,0 +1,219 @@
+.. _doc_spatial_gizmo_plugins:
+
+Spatial gizmo plugins
+=====================
+
+Introduction
+------------
+
+Spatial gizmo plugins are used by the editor and custom plugins to define the
+gizmos attached to any kind of Spatial node.
+
+This tutorial will show you the two main approaches to defining your own custom 
+gizmos. The first option works well for simple gizmos and creates less clutter in 
+your plugin structure, while the second one will let you store some per-gizmo data. 
+
+.. note:: This tutorial assumes you already know how to make generic plugins. If
+          in doubt, refer to the :ref:`doc_making_plugins` page.
+
+The EditorSpatialGizmoPlugin
+----------------------------
+
+Regardless of the approach we choose, we will need to create a new 
+:ref:`EditorSpatialGizmoPlugin <class_EditorSpatialGizmoPlugin>`. This will allow
+us to set a name for the new gizmo type and define other behaviors such as whether 
+the gizmo can be hidden or not.
+
+This would be a basic setup:
+
+::
+    
+    # MyCustomGizmoPlugin.gd 
+    
+    extends EditorSpatialGizmoPlugin
+
+    func get_name():
+        return "CustomNode"
+
+
+::
+
+    # MyCustomEditorPlugin.gd
+
+    tool
+    extends EditorPlugin
+
+    const MyCustomGizmoPlugin = preload("res://addons/my-addon/MyCustomGizmoPlugin.gd")
+
+    var gizmo_plugin = MyCustomGizmoPlugin.new()
+
+    func _enter_tree():
+        add_spatial_gizmo_plugin(gizmo_plugin)
+
+    func _exit_tree():
+        remove_spatial_gizmo_plugin(gizmo_plugin)
+
+
+For simple gizmos, just inheriting :ref:`EditorSpatialGizmoPlugin <class_EditorSpatialGizmoPlugin>` 
+is enough. If you want to store some per-gizmo data or you are porting a Godot 3.0 gizmo 
+to 3.1+, you should go with the second approach.
+
+
+Simple approach
+---------------
+
+The first step is to, in our custom gizmo plugin, override the :ref:`has_gizmo()<class_EditorSpatialGizmoPlugin_method_has_gizmo>`
+method so that it returns ``true`` when the spatial parameter is of our target type.
+
+::
+    
+    # ...
+
+    func has_gizmo(spatial):
+        return spatial is MyCustomSpatial
+    # ...
+
+Then we can override methods like :ref:`redraw()<class_EditorSpatialGizmoPlugin_method_redraw>`
+or all the handle related ones.
+
+::
+    
+    # ...
+
+    func _init():
+        create_material("main", Color(1, 0, 0))
+        create_handles_material("handles")
+
+    func redraw(gizmo):
+        gizmo.clear()
+
+        var spatial = gizmo.get_spatial_node()
+        
+        var lines = PoolVector3Array()
+        
+        lines.push_back(Vector3(0, 1, 0))
+        lines.push_back(Vector3(0, spatial.my_custom_value, 0))
+
+        var handles = PoolVector3Array()
+
+        handles.push_back(Vector3(0, 1, 0))
+        handles.push_back(Vector3(0, spatial.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 :ref:`get_material()<class_EditorSpatialGizmoPlugin_method_get_material>`. This 
+method 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:
+
+::
+
+    extends EditorSpatialGizmoPlugin
+
+    const MyCustomSpatial = preload("res://addons/my-addon/MyCustomSpatial.gd")
+
+    func _init():
+        create_material("main", Color(1,0,0))
+        create_handles_material("handles")
+
+    func has_gizmo(spatial):
+        return spatial is MyCustomSpatial
+
+    func redraw(gizmo):
+        gizmo.clear()
+
+        var spatial = gizmo.get_spatial_node()
+        
+        var lines = PoolVector3Array()
+        
+        lines.push_back(Vector3(0, 1, 0))
+        lines.push_back(Vector3(0, spatial.my_custom_value, 0))
+
+        var handles = PoolVector3Array()
+
+        handles.push_back(Vector3(0, 1, 0))
+        handles.push_back(Vector3(0, spatial.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 :ref:`EditorSpatialGizmoPlugin <class_EditorSpatialGizmoPlugin>`
+to get properly working handles.
+
+Alternative approach
+--------------------
+
+In some cases we want to provide our own implementation of :ref:`EditorSpatialGizmo<class_EditorSpatialGizmo>`,
+maybe because we want to have some state stored in each gizmo or because we are porting 
+an 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 
+:ref:`create_gizmo()<class_EditorSpatialGizmoPlugin_method_create_gizmo>`, so it returns our custom gizmo implementation
+for the Spatial nodes we want to target.
+
+::
+
+    # MyCustomGizmoPlugin.gd 
+    extends EditorSpatialGizmoPlugin
+
+    const MyCustomSpatial = preload("res://addons/my-addon/MyCustomSpatial.gd")
+    const MyCustomGizmo = preload("res://addons/my-addon/MyCustomGizmo.gd")
+
+    func _init():
+        create_material("main", Color(1, 0, 0))
+        create_handles_material("handles")
+
+    func create_gizmo(spatial):
+        if spatial is MyCustomSpatial:
+            return MyCustomGizmo.new()
+        else:
+            return null
+
+This way all the gizmo logic and drawing methods can be implemented in a new clas extending
+:ref:`EditorSpatialGizmo<class_EditorSpatialGizmo>`, like so:
+
+::
+
+    # MyCustomGizmo.gd
+
+    extends EditorSpatialGizmo
+
+    # You can store data in the gizmo itself (more useful when working with handles)  
+    var gizmo_size = 3.0
+
+    func redraw():
+        clear()
+
+        var spatial = get_spatial_node()
+        
+        var lines = PoolVector3Array()
+        
+        lines.push_back(Vector3(0, 1, 0))
+        lines.push_back(Vector3(gizmo_size, spatial.my_custom_value, 0))
+
+        var handles = PoolVector3Array()
+
+        handles.push_back(Vector3(0, 1, 0))
+        handles.push_back(Vector3(gizmo_size, spatial.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 :ref:`EditorSpatialGizmo<class_EditorSpatialGizmo>`
+ to get properly working handles.