|
|
|
|
Reply From: |
Hyper Sonic |
This is because internally, the matrix will not store negative scale on X but rather oppose the scale on Y and rotate by 180 degrees.
Node2D.scale documentation:
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.
See this issue “global_scale property wrong in Node2D when scale is negative in x” at global_scale property wrong in Node2D when scale is negative in x · Issue #17405 · godotengine/godot · GitHub
In your case, when you’re holding right, each frame, the character is flipped horizontally, but via scale.y *= -1 and rotation of 180 degrees.
1st frame: the character is flipped right as expected, but scale.x is still 1, while scale.y = -1 and rotation_degrees = 180
2nd frame: scale.x = -1 but it was 1 previously, so the character is flipped again. scale.y goes back to 1, and rotation_degrees = 0.
The process repeats each frame, so you have flip flickering.
I found several alternatives:
a. Do not modify scale.x. Instead, set scale.y = -1
and rotation_degrees = 180
. To revert, set scale.y = 1
and rotation_degrees = 0
.
b. (thanks to Timofey from a similar question) Set transform.x.x = -1
and to revert, transform.x.x = 1
. Caution: this only works if you work with a default scaling and simply flip left and right from there. You’re modifying the base axes directly with this, so it may not make sense in particular questions where the transform was rotated, etc.
c. Variant of b. but setting the transform completely from scratch: transform = Transform2D.FLIP_X.translated(transform.origin)
. To revert, do transform = Transform2D.IDENTITY.translated(transform.origin)
or transform = Transform2D(0, transform.origin)
d. Always work relatively, i.e. detect whether direction actually changed or not (based on previous direction stored in custom variable like an enum; obviously don’t rely on scale.x). When direction changes, apply scale.x *= -1
. This will, in practice, always set scale.x = -1 and do the rotation, so you can also write scale.x = -1
but it’s a little less intuitive. The advantage of scale.x *= -1
is that it’s agnostic and would totally work in an engine not having this particularity. However, I’d still recommend adding a comment explaining what’s going on behind the hood, in case some developer tries to refactor the code to something they’d think is equivalent, but is not.
e. Variant of d, when direction truly changes, apply transform.x *= -1. I would also expect it to work if you didn’t apply other transformations like rotation.
f. Do not use scale at all, flip every sprite with flip_h. Obviously it doesn’t work if you also need to flip things like collision areas (which was my case).
Common advice: you can put all the objects that need flipping (sprite, collision area) under a common parent and only scale that one, so you don’t have to repeat the trick for every of them.
I’ll try to link as many similar questions to this page as I can, but the amount of similar questions shows how surprising it was for new users. Fortunately, the extra note was added in the doc in 2022, and I see no similar question from after this date, so hopefully it means users checked the scale doc when they got the issue and could solve the issue themselves.