The limits checked by "is_equal_approx()" appears to be too small

Godot Version

4.2.1

Question

I’m currently making a sokoban/box pushing game similar to Steven Sausage Roll. The player can move around and push boxes, but it can also rotate depending its orientation. To achieve this, I’m making use of the player object’s sprite.transform.y to move in the direction the sprite is facing. I’m using the sprite, because I’d like certain objects like the raycast to not rotate along with the parent.

The way it works is that, if the player is facing upwards, and the player press up or down keys, the player will move up or down. However, if the player is facing upwards and presses for instance, the right arrow key, it will rotate to the right instead. Then if the player presses the right arrow key, it will move to the right.

r/godot - The limits checked by is_equal_approx() appears to be too small

For the most part this works. The problem is, because of the way radians work, I get floating point errors. This is obvious, hence I use the is_equal_approx() function to compare the player’s sprite.transform.y with the input the player pressed (each input has a Vector2 assigned to them). The only issue is that, sometimes, the player’s sprite.transform.y component has a value like 0.000012 (as seen in the image). However, this value is slightly above the limits that is_equal_approx() considers when comparing two values. It works fine when the value is something like 0.000005 (has 5 decimal zeros), but as soon as it goes above 0.000010 (has 4 decimal zeros), the is_equal_approx() returns false.

I was wondering if there was anyway I could solve this issue? Perhaps some kind of regression? Or do I create my own function for this purpose?


Code:

extends StaticBody2D

@onready var raycast = $RayCast2D
@onready var sprite = $Sprite2D

var grid_size: int = 64
var input_dir: Dictionary = {
	"up": Vector2.UP,
	"down": Vector2.DOWN,
	"left": Vector2.LEFT,
	"right": Vector2.RIGHT,
}


func _unhandled_input(event):
	for key in input_dir:
		if event.is_action_pressed(key):
			move(input_dir[key])
			print(input_dir[key], sprite.transform.y)


func move(dir:Vector2):
	raycast.target_position = dir * grid_size
	raycast.force_raycast_update()
	
	if dir.is_equal_approx(sprite.transform.y) or dir.is_equal_approx(sprite.transform.y * -1):
		if not raycast.is_colliding():
			position += dir * grid_size
		else:
			var neighbour: Object = raycast.get_collider()
			if neighbour.is_in_group("wall"):
				print("Collided with wall!")
			elif neighbour.is_in_group("block"):
				if neighbour.move(dir):
					position += dir * grid_size
	else:
		var target_angle: float = sprite.transform.y.angle_to(dir) + PI
		sprite.rotation_degrees += rad_to_deg(target_angle)
		print(sprite.rotation_degrees)

You can write your own equality function roughly like this

func my_equal_approx(a: float, b: float) -> bool:
    return abs(a - b) < 0.000005

With whatever tolerance value you need.

Even when the limits of is_equal_approx() would be increased, you would still encounter the same issues, but later in time, because the floating point errors sum up over time.

Have you tried the following method:

  • represent the rotation of the sprite as four discrete states (up, down, left right) and not by radians and compare the states instead of the rotation angle
2 Likes

I’ve tried something similar to what you mentioned. Instead of comparing the sprite’s transform directly, I made a new variable that stores the transform, then rounds it by doing var facing_dir: Vector2 = round(sprite.transform.y). Works nicely, I get nice vectors that are much easier to compare. So far no issue, even in extreme rotations.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.