Godot: Splitting editor and runtime scripts.

Created on 11 Nov 2017  路  12Comments  路  Source: godotengine/godot

Just a feature suggestion. X)

I use tool script quite a lot in my projects, and it is one of the big highlights for me. However, there are situations where a lot of extra work is needed to prevent code from colliding. Walling off code that is editor only versus code for run time only.

For some scripts this is exactly fine, because almost all the same code is used. Though quite often in other situations the code gets littered with extra branches, and the logic of the tool takes almost entirely different turns. It can get very bloated and difficult to organize, especially when they have different start up behaviors.

I was thinking it would be a nice added option if nodes could optionally have a resource slot for Editor Only Script. Something that specifically only executes in the editor, and can be discarded when a project is exported.

_Note:_ Leaving the old way intact though, because there would be some trade off between code duplication and branch complexity.

archived feature proposal gdscript

Most helpful comment

Related to #8735. Copy-pasting my proposal here:

element.gd:

tool "element_tool.gd"
# This script isn't tool by itself, but is replaced by the other in-editor
extends Node2D

export(int, "Fire", "Water", "Ice") var element_type = 0

func get_damage():
  return ConfigAutoload.elements[type].damage

element_tool.gd:

tool # Might be unnesesary, discuss
extends "element.gd"
# extends is not required, but better if you do this so you would get all the exported vars

# Discuss: do we really need to extend the other script for that?
# We might handle it like placeholder scripts, exporting/autocompleting the same, but
# having different in-editor logic.

export(bool) var debug = true setget set_debug

func _ready():
  set_debug(debug)

func _draw():
  if debug:
    pass # Draw circles, whatever..

# Needed since we don't have a cool way to observe property changes yet
func set_debug(new_debug):
  debug = new_debug
  update()

All 12 comments

Last time I thought about this I thought it could be done with #ifdef macros, although they only get rid of execution bloat, not maintainance. On the other hand you could use a child editor-only node (or a mere script) and put tool functionality in it, I used to do that in my game for debugging in order to keep code separate (leaving only one if in my main gameplay script)

@Zylann I like that idea. Thanks for sharing it.

I had idea of EditorOnlyNode, that would be stripped when doing Export.
Mare idea never consulted it with anybody.

@n-pigeon That also sounds interesting. Especially for addon custom nodes that act as editor only helper nodes.

See #12453

Related to #8735. Copy-pasting my proposal here:

element.gd:

tool "element_tool.gd"
# This script isn't tool by itself, but is replaced by the other in-editor
extends Node2D

export(int, "Fire", "Water", "Ice") var element_type = 0

func get_damage():
  return ConfigAutoload.elements[type].damage

element_tool.gd:

tool # Might be unnesesary, discuss
extends "element.gd"
# extends is not required, but better if you do this so you would get all the exported vars

# Discuss: do we really need to extend the other script for that?
# We might handle it like placeholder scripts, exporting/autocompleting the same, but
# having different in-editor logic.

export(bool) var debug = true setget set_debug

func _ready():
  set_debug(debug)

func _draw():
  if debug:
    pass # Draw circles, whatever..

# Needed since we don't have a cool way to observe property changes yet
func set_debug(new_debug):
  debug = new_debug
  update()

Would this work with C# and GDNative?

Well, it would work as long as there is some way to specify the target tool script. So, attributes in C#, and some additional registration function in GDNative. Examples:
```c#
// c#
namespace X {
[Godot.Tool(typeof(ElementTool))]
class Element { /* ... */ }

class ElementTool : Element { /* ... */ }

}

```c++
// C++
NATIVESCRIPT_INIT() {
    register_class<Element, ElementTool>(); // Not sure if it would be here. This is just an example.
    register_class<ElementTool>();
}

Likely, it would be done internally as Ref<Script> Script::get_tool_mode_script() {}, with the current implementation being equivalent to Ref<Script> GDScript::get_tool_mode_script() {return this;}.

Also it must be taken into account that we may want to not include such editor stuff when exporting ;)

As an aside, one of the unhappy things I keep bumping into with tool script (and why splitting would be handy), is that they will not allow you to reference Autoload Singletons. Even if it isn't being called directly by tool code, it will flag it when it parses.

This doesn't compile:

extends Node2D

func _ready(): 
    if(not get_tree().is_editor_hint()):
        Utils.my_utility_function(123)

@avencherus That's a bug, #4236

Closing this. Appears a different and more robust feature/direction is being considered going forward.

As an aside, one of the unhappy things I keep bumping into with tool script (and why splitting would be handy), is that they will not allow you to reference Autoload Singletons.

This was very thankfully resolved: https://github.com/godotengine/godot/pull/18545

Was this page helpful?
0 / 5 - 0 ratings