Godot Version
Godot_v4.7-stable_linux.x86_64
Question
So initially, I (naively) have Utility functions for parenting/unparenting:
func parent_add_child_without_transform(parent: Node2D, child: Node2D) -> Transform2D:
var global_transform = child.global_transform
parent.add_child(child)
child.global_transform = global_transform
return global_transform
func parent_remove_child_without_transform(parent: Node2D, child: Node2D) -> Transform2D:
var global_transform = child.global_transform
parent.remove_child(child)
child.global_transform = global_transform
return global_transform
# for debugging
func print_gl_lc_sc_rot(card: Node2D) -> void:
print("%s:\nG sx sy r %+.2f %+.2f %+.2f L sx sy r %+.2f %+.2f %+.2f\n" %
[card, card.global_scale.x, card.global_scale.y, card.global_rotation, card.scale.x, card.scale.y, card.rotation])
However, I encountered some weird behavior, where transitioning from a positive to negative (and vice-versa) global_scale.x would not change global_scale.x itself, but would instead flip the sign of global_scale.y and set global_rotation = -PI.
This only happens for the x value of scaling, y works normally.
And to be clear, this is already documented and intentional.
Vector2 scale [default: Vector2(1, 1)] set_scale(value) setterget_scale() getter
The node’s scale, relative to the node’s parent. Unscaled value: (1, 1). See also global_scale.
Note: Negative X scales in 2D are not decomposable from the transformation matrix. Due to the way scale is represented with transformation matrices in Godot, negative scales on the X axis will be changed to negative scales on the Y axis and a rotation of 180 degrees when decomposed.
So,
func remove_cards(p_cards: Array[Card]) -> void:
for card in p_cards:
if card.temp_tracker:
print("start removing from pile")
Utils.print_gl_lc_sc_rot(card)
if card.get_parent() == card_container:
var xform: Transform2D = Utils.parent_remove_child_without_transform(card_container, card)
if card.temp_tracker:
print("Transform2D:\n%s\n" % [xform])
if card.temp_tracker:
print("finish removing from pile")
Utils.print_gl_lc_sc_rot(card)
gives
start removing from pile
Card:<Node2D#56186898182>:
G sx sy r +0.09 -0.09 -3.14 L sx sy r -1.00 +1.00 +0.00
Transform2D:
[X: (-0.09, 0.0), Y: (0.0, 0.09), O: (1697.5, 966.5)]
finish removing from pile
Card:<Node2D#56186898182>:
G sx sy r +0.09 -0.09 -3.14 L sx sy r +0.09 -0.09 -3.14
Note that the global scale and rotation are maintained, but y/rotation flip happens locally.
Arbitrary examples
Here are some arbitrary examples:
- 1a, 1b modify y so they work fine,
- 2a modifies the local scale.x, so it works fine for the local variables, but you do see the effect I’m describing on the global scale and
- 2b completely bugs out, as we’re modifying the global scale x
func print_gl_lc_sc_rt(node: Node2D) -> void:
print("G sx sy r %+.2f %+.2f %+.2f L sx sy r %+.2f %+.2f %+.2f" %
[node.global_scale.x, node.global_scale.y, node.global_rotation, node.scale.x, node.scale.y, node.rotation])
func _ready() -> void:
var node: Node2D
node = Node2D.new()
print("\n\n\nExample 1")
var node_ex1: Node2D = node.duplicate()
for _i in range(9):
node_ex1.scale.y -= 0.1
print_gl_lc_sc_rt(node_ex1)
var node_ex1a: Node2D = node_ex1.duplicate()
print("\nExample 1a: CORRECT")
for _i in range(11):
node_ex1a.scale.y -= 0.1
print_gl_lc_sc_rt(node_ex1a)
var node_ex1b: Node2D = node_ex1.duplicate()
print("\nExample 1b: CORRECT")
for _i in range(11):
node_ex1b.global_scale.y -= 0.1
print_gl_lc_sc_rt(node_ex1b)
print("\n\n\nExample 2")
var node_ex2: Node2D = node.duplicate()
for _i in range(9):
node_ex2.scale.x -= 0.1
print_gl_lc_sc_rt(node_ex2)
var node_ex2a: Node2D = node_ex2.duplicate()
print("\nExample 2a: CORRECT")
for _i in range(11):
node_ex2a.scale.x -= 0.1
print_gl_lc_sc_rt(node_ex2a)
var node_ex2b: Node2D = node_ex2.duplicate()
print("\nExample 2b: INCORRECT")
for _i in range(11):
node_ex2b.global_scale.x -= 0.1
print_gl_lc_sc_rt(node_ex2b)
breakpoint
This returns
Example 1
G sx sy r +1.00 +0.90 +0.00 L sx sy r +1.00 +0.90 +0.00
G sx sy r +1.00 +0.80 +0.00 L sx sy r +1.00 +0.80 +0.00
G sx sy r +1.00 +0.70 +0.00 L sx sy r +1.00 +0.70 +0.00
G sx sy r +1.00 +0.60 +0.00 L sx sy r +1.00 +0.60 +0.00
G sx sy r +1.00 +0.50 +0.00 L sx sy r +1.00 +0.50 +0.00
G sx sy r +1.00 +0.40 +0.00 L sx sy r +1.00 +0.40 +0.00
G sx sy r +1.00 +0.30 +0.00 L sx sy r +1.00 +0.30 +0.00
G sx sy r +1.00 +0.20 +0.00 L sx sy r +1.00 +0.20 +0.00
G sx sy r +1.00 +0.10 +0.00 L sx sy r +1.00 +0.10 +0.00
Example 1a: CORRECT
G sx sy r +1.00 +0.00 +0.00 L sx sy r +1.00 +0.00 +0.00
G sx sy r +1.00 -0.10 +0.00 L sx sy r +1.00 -0.10 +0.00
G sx sy r +1.00 -0.20 +0.00 L sx sy r +1.00 -0.20 +0.00
G sx sy r +1.00 -0.30 +0.00 L sx sy r +1.00 -0.30 +0.00
G sx sy r +1.00 -0.40 +0.00 L sx sy r +1.00 -0.40 +0.00
G sx sy r +1.00 -0.50 +0.00 L sx sy r +1.00 -0.50 +0.00
G sx sy r +1.00 -0.60 +0.00 L sx sy r +1.00 -0.60 +0.00
G sx sy r +1.00 -0.70 +0.00 L sx sy r +1.00 -0.70 +0.00
G sx sy r +1.00 -0.80 +0.00 L sx sy r +1.00 -0.80 +0.00
G sx sy r +1.00 -0.90 +0.00 L sx sy r +1.00 -0.90 +0.00
G sx sy r +1.00 -1.00 +0.00 L sx sy r +1.00 -1.00 +0.00
Example 1b: CORRECT
G sx sy r +1.00 +0.00 +0.00 L sx sy r +1.00 +0.00 +0.00
G sx sy r +1.00 -0.10 +0.00 L sx sy r +1.00 -0.10 +0.00
G sx sy r +1.00 -0.20 +0.00 L sx sy r +1.00 -0.20 +0.00
G sx sy r +1.00 -0.30 +0.00 L sx sy r +1.00 -0.30 +0.00
G sx sy r +1.00 -0.40 +0.00 L sx sy r +1.00 -0.40 +0.00
G sx sy r +1.00 -0.50 +0.00 L sx sy r +1.00 -0.50 +0.00
G sx sy r +1.00 -0.60 +0.00 L sx sy r +1.00 -0.60 +0.00
G sx sy r +1.00 -0.70 +0.00 L sx sy r +1.00 -0.70 +0.00
G sx sy r +1.00 -0.80 +0.00 L sx sy r +1.00 -0.80 +0.00
G sx sy r +1.00 -0.90 +0.00 L sx sy r +1.00 -0.90 +0.00
G sx sy r +1.00 -1.00 +0.00 L sx sy r +1.00 -1.00 +0.00
Example 2
G sx sy r +0.90 +1.00 +0.00 L sx sy r +0.90 +1.00 +0.00
G sx sy r +0.80 +1.00 +0.00 L sx sy r +0.80 +1.00 +0.00
G sx sy r +0.70 +1.00 +0.00 L sx sy r +0.70 +1.00 +0.00
G sx sy r +0.60 +1.00 +0.00 L sx sy r +0.60 +1.00 +0.00
G sx sy r +0.50 +1.00 +0.00 L sx sy r +0.50 +1.00 +0.00
G sx sy r +0.40 +1.00 +0.00 L sx sy r +0.40 +1.00 +0.00
G sx sy r +0.30 +1.00 +0.00 L sx sy r +0.30 +1.00 +0.00
G sx sy r +0.20 +1.00 +0.00 L sx sy r +0.20 +1.00 +0.00
G sx sy r +0.10 +1.00 +0.00 L sx sy r +0.10 +1.00 +0.00
Example 2a: CORRECT
G sx sy r +0.00 +1.00 +0.00 L sx sy r +0.00 +1.00 +0.00
G sx sy r +0.10 -1.00 -3.14 L sx sy r -0.10 +1.00 +0.00
G sx sy r +0.20 -1.00 -3.14 L sx sy r -0.20 +1.00 +0.00
G sx sy r +0.30 -1.00 -3.14 L sx sy r -0.30 +1.00 +0.00
G sx sy r +0.40 -1.00 -3.14 L sx sy r -0.40 +1.00 +0.00
G sx sy r +0.50 -1.00 -3.14 L sx sy r -0.50 +1.00 +0.00
G sx sy r +0.60 -1.00 -3.14 L sx sy r -0.60 +1.00 +0.00
G sx sy r +0.70 -1.00 -3.14 L sx sy r -0.70 +1.00 +0.00
G sx sy r +0.80 -1.00 -3.14 L sx sy r -0.80 +1.00 +0.00
G sx sy r +0.90 -1.00 -3.14 L sx sy r -0.90 +1.00 +0.00
G sx sy r +1.00 -1.00 -3.14 L sx sy r -1.00 +1.00 +0.00
Example 2b: INCORRECT
G sx sy r +0.00 +1.00 +0.00 L sx sy r +0.00 +1.00 +0.00
G sx sy r +0.10 -1.00 -3.14 L sx sy r -0.10 +1.00 +0.00
G sx sy r +0.00 -1.00 +0.00 L sx sy r +0.00 -1.00 +0.00
G sx sy r +0.10 +1.00 -3.14 L sx sy r -0.10 -1.00 +0.00
G sx sy r +0.00 +1.00 +0.00 L sx sy r +0.00 +1.00 +0.00
G sx sy r +0.10 -1.00 -3.14 L sx sy r -0.10 +1.00 +0.00
G sx sy r +0.00 -1.00 +0.00 L sx sy r +0.00 -1.00 +0.00
G sx sy r +0.10 +1.00 -3.14 L sx sy r -0.10 -1.00 +0.00
G sx sy r +0.00 +1.00 +0.00 L sx sy r +0.00 +1.00 +0.00
G sx sy r +0.10 -1.00 -3.14 L sx sy r -0.10 +1.00 +0.00
G sx sy r +0.00 -1.00 +0.00 L sx sy r +0.00 -1.00 +0.00
So my questions are:
- Is “unexpected” behavior exclusively restricted to cases where scale.x moves between positive and negative?
- What is the cleanest approach for defining parenting/unparenting while retaining the physical properties of my Node2D, while avoiding all of the weirdness above? Is it just to perform the global translation, and then compare and adjust local variables before/after within the parenting/unparenting method?
Appreciate any help, thanks!