Why my character scale keep changing?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By X

Hi there.
I wanna my player to scale to (scale.x = 1) when i press the right key and -1 when i press left.
I have the following code:

if Input.is_action_just_pressed("ui_left"):
	scale.x = -1
if Input.is_action_just_pressed("ui_right"):
	scale.x = 1

But every time that i push left the scale alter between 1 and -1.
And when i push right nothing happen.

1 Like
:bust_in_silhouette: Reply From: jgodfrey

It sounds like you’ve mapped the left arrow key to both the ui_left and ui_right actions. Check your key mappings and I think you’ll see the problem.

I did check and them aren’t.

X | 2021-01-12 19:12

Errrr… Are you sure? That would cause exactly the problem you mention, and I can’t think of anything else that would cause it… The code you posted looks fine.

jgodfrey | 2021-01-12 19:41

Im pretty sure, man.

X | 2021-01-12 23:32

Is the full project available some place where it can be looked at?

jgodfrey | 2021-01-13 00:03

I am sure too

CharlesMoor | 2021-01-13 19:06

Here

X | 2021-01-13 15:43

:bust_in_silhouette: Reply From: Andrea

with this code you are flipping the entire player, not only the sprite, meaning left and right get reversed, and all the rest of your player code will loose sense.
change it to to flip the sprite only

if Input.is_action_pressed("ui_left"):
	$NeoPlayer.scale.x = -1
elif Input.is_action_pressed("ui_right"):
	$NeoPlayer.scale.x = 1	

also, why did you created your own INPUT() function inside process, instead of using the _input(event) function that fires every time an input is received

I actually already do that, but thanks anyway.
Isn’t the method that i prefer, cuz i also wanna to flip the colliders though it, instead of using code, but them can not be a child of the Node2D node, but that is better that nothing.
Also, the input(event) wasn’t working pretty fine for me, so i do it myself.

X | 2021-01-13 22:54

well you can flip the spirte AND the collider separately, it’s just one additional line of code.
or you can place the sprite as a child of the collider and flip the collider to flip both

Andrea | 2021-01-14 23:01

:bust_in_silhouette: Reply From: Amateur.game.dev.

If I were you and this happened, I would most likely create a variable which would control them. Try this.

var scaleup = false
var scaledown = false


func _input(event):
    if scaleup == false:
        if Input.is_action_just_pressed("ui_left"):
            scale.x = -1
            scaledown = true
            scaleup = false
    if scaledown == false:
        if Input.is_action_just_pressed("ui_right"):
            scale.x = 1
            scaleup = true
            scaledown = false

if this doesn’t work in the input(event) either, then try again in the process.

:bust_in_silhouette: 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.

1 Like

Had the same problem, did this and it seems to work fine :

func handle_flip():
	if Input.get_axis("ui_left", "ui_right") < 0:
		if Input.get_axis("ui_left", "ui_right") < 0 && !find_child("Sprite2D").flip_h:
			print("L : " + str(Input.get_axis("ui_left", "ui_right")) + " / " + str(find_child("Sprite2D").flip_h))
			find_child("Sprite2D").flip_h = true
			scale.x = -scale.x
	elif Input.get_axis("ui_left", "ui_right") > 0:
		if Input.get_axis("ui_left", "ui_right") > 0 && find_child("Sprite2D").flip_h:
			print("R : " + str(Input.get_axis("ui_left", "ui_right")) + " / " + str(find_child("Sprite2D").flip_h))
			find_child("Sprite2D").flip_h = false
			scale.x = -scale.x

I cannot edit my message copied over from the old forum so adding a manual edit here to clarify method d and why scale.x = -1 is not intuitive:

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 to flip the entity horizontally, but it’s a little less intuitive: when flipping back to original scale, you should write scale.x = -1 a 2nd time and most people would think that assigning the same value a second time does nothing)

EDIT 2024-10-04:
Method b. transform.x.x = -1, this doesn’t work not only if changing scale but also rotation, as this would affect the transform matrix. It really only works if you left the root node untouched (but you could still apply transform to children). I just had the issue today, where having the root node rotated by 90 degrees before applying even no change with ``transform.x.x = 1` would result in skewing the whole node by 45 degrees.

Method c. sets transform from scratch, so you will lose any scale and rotation (unless you reinject them in the formula)