The scene builder builds the Viewport
s while the proxy builder creates duplicates controlled with RemoteControl
nodes. We call these the proxies.
In this chapter, we’ll make the proxy builder to create proxies and remote transforms.
Add a MeshInstance
to the scene and assign it a sphere. We use that as a guinea pig to develop the add on with.
Add a new Node
to the mesh and assign it a script. Save it in addons/gdquest.toon-controller/Tools/ToonProxyBuilder.gd
.
It’s an editor script so needs the tool
keyword. We give it a class so Godot adds it to the Add Node dialog. Since it’s inside the addons folder, Godot will list it when the plugin is activate but not before.
tool class_name ToonProxyBuilder extends Node
When assigned to lights, the proxy keeps track of what kind of light the parent is (key, fill or kick) using an enum. We add a setter since it has to change data on the proxy lights.
It stores whether a light should emit shadows.
enum LightRole { KEY, FILL, KICK } export (LightRole) var light_role := 0 setget _set_light_role export var emits_shadows := false setget _set_emits_shadows func _set_light_role(value: int) -> void: pass func _set_emits_shadows(value: bool) -> void: pass
It also holds a reference to the proxies and RemoteTransform
nodes it creates.
var light_proxy: Node var specular_proxy: Node var light_remote: RemoteTransform var specular_remote: RemoteTransform
It first finds the scene root and the ToonSceneBuilder
, as it depends on it to find viewports and data.
onready var scene_root: Node = ( get_tree().edited_scene_root if Engine.editor_hint else get_tree().root ) onready var builder: ToonSceneBuilder = scene_root.find_node("ToonSceneBuilder") func _ready(): if not builder: return
It first grabs its parent, and uses its name to find its proxies on the light and specular viewports. Note the use of the optional boolean in find_node
. The first defaults to true and uses a recursive search, but the second is false because the viewports do not own the proxies (the scene root does).
var parent := get_parent() light_proxy = builder.light_data.find_node(parent.name, true, false) specular_proxy = builder.specular_data.find_node(parent.name, true, false)
If they’re missing and we’re in the editor, we build them. We create a function _build_missing_proxies
which takes the parent as its argument.
func _build_missing_proxies(parent: Node) -> void: var light_missing: bool = light_proxy == null var specular_missing: bool = specular_proxy == null
If either of them are missing, we remove the proxy from the object. Otherwise, it would get duplicated and the proxy code would run again until Godot either crashes or detects an error! We add it again once we’re done.
Note the call to yield
before we start messing around with hierarchy. When you add the node directly from the Add Child Node dialog, the hierarchy is already established and there’s no issue. But when loading the scene, Godot is busy setting children up and it would throw errors. We wait a frame for it to finish before proceeding.
yield(get_tree(), "idle_frame") if light_missing or specular_missing: parent.remove_child(self)
When one of the proxies is missing, we need to build and configure it. For that, we build yet another function _build_remote_duplicates
which takes the parent and the viewport. It returns a duplicate node and a RemoteTransform
inside a Dictionary
.
func _build_remote_duplicates(parent: Node, type: int) -> Dictionary: var proxy: Node = parent.duplicate() var proxy_remote := RemoteTransform.new() parent.add_child(proxy_remote) proxy_remote.owner = scene_root
We name the remote and add the proxy as a child to the correct viewport based on the data type we specify. We also set the initial color of any light to red since the default is the key light. The setter is what changes it to fill or kick.
match type: ToonSceneBuilder.DataType.LIGHT: builder.light_data.add_child(proxy) if proxy is Light: proxy.light_color = Color.red proxy_remote.name = "LightRemote" ToonSceneBuilder.DataType.SPECULAR: builder.specular_data.add_child(proxy) proxy_remote.name = "SpecularRemote" proxy.owner = scene_root
We set the remote’s remote_path
, and then return the proxy and the remote in a dictionary.
proxy_remote.remote_path = "../%s" % parent.get_path_to(proxy) return {"proxy": proxy, "proxy_remote": proxy_remote}
Back in _build_missing_proxies
, we call this new function for the missing light and specular, and extract the information into the proxy and remote variables. If they’re not missing, we just grab the remote by name.
if light_missing: var result := _build_remote_duplicates( parent, ToonSceneBuilder.DataType.LIGHT ) light_proxy = result.proxy light_remote = result.proxy_remote else: light_remote = parent.find_node("LightRemote", true, false) if specular_missing: var result := _build_remote_duplicates( parent, ToonSceneBuilder.DataType.SPECULAR ) specular_proxy = result.proxy specular_remote = result.proxy_remote else: specular_remote = parent.find_node("SpecularRemote", true, false)
If the light or specular were missing, we re-add the builder to the parent.
if light_missing or specular_missing: parent.add_child(self) owner = scene_root
Now, go back to _ready
to finally call the _build_missing_proxies
function.
if Engine.editor_hint: _build_missing_proxies(parent)