Godot: Ability to encapsulate tool subscenes (New ways of using tool keyword)

Created on 12 May 2017  路  21Comments  路  Source: godotengine/godot

Issue description:
Quite often I have tool scene which is working inside editor. Sometimes I need for those scenes to be encapsulated inside other bigger scenes. To make them work in reaction to level designer actions I need to heavily modify the bigger scene, and this usually a lot of work and sometimes it's not even possible (when scenes are using/based on many other scenes/scripts which should not work in tool mode).

What I would want to have would be some kind of ability to notify child tool subscene about exported variable change without making owner scene a tool, maybe some kind of 'tool redirect' keyword for exported variables?
Maybe even better would be to have more general ability of using 'tool' keyword not for whole scripts, but only for single members/functions instead.

Though for a second I will be able to overpass this issue with multi-script (different script for normal usage and different script for working inside the editor), but as many people pointed out there is little benefit from introducing multiscript and a lot of problems.

archived discussion feature proposal plugin usability

Most helpful comment

Wouldn't having something like this be cleaner and more maintainable? :

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 21 comments

I have a ton of tool scripts and child scenes and this would be an awesome quality of life improvement.

I think it would be the best if we could use Curly braces with tool keyword to determine what parts of the script should be exacutable inside editor, example code could look like this:

tool{
export (NodePath) var sourceStateID setget setSourceStateID;
export (NodePath) var targetStateID;
}

onready var fsm = Global.findParentNodeWithType(self, FSM)

tool{
func _ready():
    setSourceStateID(sourceStateID);
        }
    fsm.init();


tool{
func setSourceStateID(inStateId):
    sourceStateID = inStateId;
    if(!is_inside_tree()): return;
}

(... rest of the class here...)

I'm wondering if it would be possible to write editor plugin that would do just that. Let's assume for example that tool{ and } must be preceded by #(comment). Now I should be able to write tool plugin inside which you could select your 'tool' scripts, and this plugin should be able to load those scripts inside editor, remove all the parts that are not between 'tool{' and '}' and attach this new script (with set_script() (without saving it as a file) to the node).

But I think the downside of this solution would be the fact that you would see just the tool part after opening the script in Godot :(

Magic code modification will just make things complicated I think.
What about just, marking lines about tooling with a pipe | symbol and make parser treat them depending on a sort of environment parameter.

|export (NodePath) var sourceStateID setget setSourceStateID;
|export (NodePath) var targetStateID;

onready var fsm = Global.findParentNodeWithType(self, FSM)

func _ready():
|   setSourceStateID(sourceStateID);
    fsm.init();

|func setSourceStateID(inStateId):
|   sourceStateID = inStateId;
|   if(!is_inside_tree()): return;

(... rest of the class here...)

A pipe symbol will only lead to unending questions 'what's that symbol do?'

But it seems more pythonic than curly braces (IMHO) and shorter than marking each line "tool". At first encounter this may seem different but when meaning is known this will be most easy on the eye and parser I think. Character for marking lines may be different. ^ can be used instead which have less usage than pipe while coding.

^export (NodePath) var sourceStateID setget setSourceStateID;
^export (NodePath) var targetStateID;

onready var fsm = Global.findParentNodeWithType(self, FSM)

func _ready():
^    setSourceStateID(sourceStateID);
    fsm.init();

|func setSourceStateID(inStateId):
^    sourceStateID = inStateId;
^    if(!is_inside_tree()): return;

(... rest of the class here...)

Only down side is this causes alignment for tool lines will be one character left of regular lines. This makes curly braces more acceptable and easy on the eye.

How about:

tool:
    export (NodePath) var sourceStateID setget setSourceStateID;
    export (NodePath) var targetStateID;

or Java/Python-like annotations?

@tool 
    export (NodePath) var sourceStateID setget setSourceStateID;
    export (NodePath) var targetStateID;

@tool
func foo():
    pass

func bar():
    @tool
        setSourceStateID(sourceStateID);

Another syntax without additional indentation might be :

@tool@
export ....
@@
export ....

@tool@
func foo():
     pass
@@

or

%tool%
export ....
export ....
%%

Wouldn't having something like this be cleaner and more maintainable? :

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()

@bojidar-bg interesting concept, I quite like it, like I said in first post I had hope it would be possible to use multi-script to achieve something similar. But I'm not sure how you want to handle exported variables in this scenario. Mean if for the purpose of editor we replace script with another one we will have in editor exported variables of another one

@kubecz3k That's what I mentioned in the # Discussion comment -- we are already replacing the non-tool scripts with the so-called "placeholder" scripts in the editor.

If we can get the tool GDScript instance to know that it should also export some variables of another script, it would have no problem doing so. Since we can set that other script at initialisation time, it would all work "just fine".

Thus, the second script would become:

tool # Might be unnecessary
extends Node2D
# ... ...

I'm a bit confused by the two scripts idea...

@bojidar-bg another thing to consider: I'm not sure how tool script would work when it's extending original script. Mean some part of the reason why we want to distinguish between tool and non tool part is because currently tool scripts sometimes cant even pass through the code validation inside the editor (like singletons are invisible in tool for example).
Besides that I think your proposition is good especially since _you are the one of the few_ people who actually know how this stuff is working, and how to implement this in the most straightforward way.
When it comes to exposing exported vars of runtime script I think any solution would be fine for me personally, even if there would be some functions that we would need to use manually.

if we will have tool scripts to replace original one in editor, there should be ability to not extend original one. What I mean is currently it's hard to use tools also because some of Godot features are not present

@kubecz3k Well, you would be free to not extend the other script at all, yes.

@Zireael07 Why? You can just think of it as swapping the script at editor time, with some nifty details about exported vars.

@bojidar-bg also I have yet another use case. In fsm plugin which I made, there is Transition class which is working in tool mode. It need to work in tool mode because visualisation part of the plugin need to know to which node states this transition is linked to (implemented by exported nodePath). The problem is there are generated classes which are extending this Transition class (users are doing this via UI), and those classes also need to be in tool mode because of the fact that their ancestor is sharing some data with plugin (this is a problem since if user make syntax error in this script, it will be gone for editor plugin until fix). Users are using this subclass because they need to implement some functions.
conclusion: In ideal world ancestor could be a tool but subclass would not need to be tool in order for this ancestor to work in editor.

@reduz could you take look at this issue? Specifically at @bojidar-bg proposition? Having this would be huge quality of life improvement and would be cool to have it in 3.0. Not sure if I'm right man to do the job but I think I might want to give it a try.

tool # Might be unnesesary, discuss

For the discussion part, I think marking tool scripts in a clear way is a handy requirement. So that they can be searched and processed in batch when necessary both by Godot and outside processes. Using a regex from terminal to find all tool scripts in a project folder may be handy.

Use tool scriptname as import is awesome.

Feature and improvement proposals for the Godot Engine are now being discussed and reviewed in a dedicated Godot Improvement Proposals (GIP) (godotengine/godot-proposals) issue tracker. The GIP tracker has a detailed issue template designed so that proposals include all the relevant information to start a productive discussion and help the community assess the validity of the proposal for the engine.

The main (godotengine/godot) tracker is now solely dedicated to bug reports and Pull Requests, enabling contributors to have a better focus on bug fixing work. Therefore, we are now closing all older feature proposals on the main issue tracker.

If you are interested in this feature proposal, please open a new proposal on the GIP tracker following the given issue template (after checking that it doesn't exist already). Be sure to reference this closed issue if it includes any relevant discussion (which you are also encouraged to summarize in the new proposal). Thanks in advance!

Was this page helpful?
0 / 5 - 0 ratings