My question is: is this a massive anti-pattern that will have more unforeseen consequences than I foresee?
Will this forever tarnish my reputation? (don’t really have much of a reputation really)
Or can I just say: “Trust me, I know what I’m doing”
The tool I do not want to force upon my trusted early plugin adopters to have to look up themselves
@tool
extends Bone2D
class_name SVSBone2D
signal transform_changed(ref : SVSBone2D)
func _init() -> void:
set_notify_transform(true)
func _notification(what: int) -> void:
if what == NOTIFICATION_TRANSFORM_CHANGED:
transform_changed.emit(self)
The evil anti-pattern I’m not afraid to use (yet)
(work in progress of course)
func _connect_to_bone_signals(current_bone_node : Node = null) -> void:
if current_bone_node == null:
current_bone_node = skeleton
if current_bone_node is Bone2D and not current_bone_node is SVSBone2D:
# NEVER USE EVAL! EVAL=EVIL!
# (except in game development of course)
current_bone_node.set_script(load("res://addons/curved_lines_2d/svs_bone_2d.gd"))
if current_bone_node is SVSBone2D:
var bone = current_bone_node as SVSBone2D
if not bone.transform_changed.is_connected(_on_bone_transform_changed):
bone.transform_changed.connect(_on_bone_transform_changed)
for bone in current_bone_node.get_children():
if bone is Bone2D:
_connect_to_bone_signals(bone)
I am aware of the potential performance impact of executing code on all these transforms… but in this case it is kind of the point: when a bone changes rotation, position or scale, the animated polygons (in my case curves) have to respond.
func _connect_to_bone_signals(current_bone_node : Node = null) -> void:
if current_bone_node == null:
current_bone_node = skeleton
#if current_bone_node is Bone2D and not current_bone_node is SVSBone2D:
#current_bone_node.set_script(load("res://addons/curved_lines_2d/svs_bone_2d.gd"))
if current_bone_node is Bone2D:
var bone = current_bone_node as Bone2D
if not bone.draw.is_connected(_on_bone_transform_changed):
bone.draw.connect(_on_bone_transform_changed)
for bone in current_bone_node.get_children():
if bone is Bone2D:
_connect_to_bone_signals(bone)
Just wondering now why invisible things emit draw, although it sort of makes sense to name it like that, but sounds like a case of: “we’re committed now, there is no way back”
And how to access the object that emitted any given signal. (like the spookily scoped javascript this arg)
I tried that actually, but I must have made a mistake, because my _on_bone_transform_changed(bone : Bone2D) callback was expecting a single argument bone… Should I have used _on_bone_transform_changed(bone : Object) ? Because the error message appeared to me as a argument count error, not a type error..
The type of the bound object should match the type declared in the handler (or its downstream type). For a custom signal you can just go typeless if you like.
If the issue is argument count, maybe some of the previous connections persist without the bound argument? What’s the exact error message, was the function called with 0 arguments?
Yes, because CanvasItem.draw has zero args, I assumed. Does bind override, or make an additional argument possible?
I usually use a lambda to add a var known in the connect’s scope, but I cannot disconnect that later..
..I strictly do not need to know the invoker, because I can check all the bones’ transforms for changes individually. That is not really expensive… But it does take another caching var to compare with.
It’s a runtime check against the handler function. The total number/type of additional arguments bound to the callable must match what you declared in the handler function. The default handler prototype doesn’t need to be strictly respected. You can add/bind arguments to it.
I will try again and report the exact error I ran into.
Maybe, just maybe, it was a code change after initialization of the connects, because I adapted code while the shape node already had a skeleton assigned in the scene.
Ok, yeah, now it works! Which is great, because I may need it later, but sadly in this specific case I reasoned I need to re-tessellate the whole curve anyway, no matter how many points were moved around.
I have decided to go with this variant any way (unless something better comes up), to make it work at runtime:
svs_bone_2d.gd
extends Bone2D
func _init():
set_notify_transform(true)
func _notification(what: int) -> void:
if what == NOTIFICATION_TRANSFORM_CHANGED:
draw.emit()
scalable_vector_shape_2d.gd
var _bone_transform_emit_script : Script = load("res://addons/curved_lines_2d/svs_bone_2d.gd")
func _ready():
# (...)
if is_instance_valid(skeleton) and not Engine.is_editor_hint():
for i in skeleton.get_bone_count():
skeleton.get_bone(i).set_script(_bone_transform_emit_script)
I opened a pull request. You’re more than welcome to leave a scathing review there!
I’d still prefer manual checks every frame instead of forcing unsolicited scripts on user’s nodes. What to do if some of the bones already have scripts attached?