Hi,
I've been stuck on this for literal days, completely unable to make progress, and really need some handholding.

I'm trying to make a system like old 2.5d games with 8 directional sprites. Depending on the angle between the NPC and player, the NPC sprite should change.

I could get this working until the NPC is rotated.

Here's the relevant code:

var dir = rad2deg((NPC.get_rotation().y))
var player_adj_origin : Vector2
player_adj_origin.x = player.global_transform.origin.x - NPC.global_transform.origin.x
player_adj_origin.y = player.global_transform.origin.z - NPC.global_transform.origin.z

var raddir = deg2rad(dir)
angle_to_player = rad2deg(Vector2(cos(raddir),sin(raddir)).angle_to(player_adj_origin))


# simple checker to show which sprite is displayed at which angles
if (abs(angle_to_player) < 22.5):
	sprite_dir_string = "Front"
elif (angle_to_player>=22.5 and angle_to_player<67.5):
	sprite_dir_string = "FrontRight"
elif (angle_to_player>=67.5 and angle_to_player<112.5):
	sprite_dir_string = "Right"
elif (angle_to_player>=112.5 and angle_to_player<157.5):
	sprite_dir_string = "BackRight"
elif (abs(angle_to_player)>=157.5):
	sprite_dir_string = "Back"
elif (angle_to_player<=-112.5 and angle_to_player>-157.5):
	sprite_dir_string = "BackLeft"
elif (angle_to_player<=-67.5 and angle_to_player>-112.5):
	sprite_dir_string = "Left"
elif (angle_to_player<=-22.5 and angle_to_player>-67.5):
	sprite_dir_string = "FrontLeft"

If this was working, then I would be able to stand directly (1 unit) in front of my NPC, in the direction of its rotation, and Vector2 player_adj_origin would be equal to Vector2(cos(raddir),sin(raddir)).

Also, if standing in front of the NPC, angle_to_player should be 0.

But the results I get do not match at all, and weirdly, when I try rotating my NPC at different angles, the results of player_adj_origin and angle_to_player change in ways that I cannot easily understand.

Here's what I'm seeing at each 45 degree angle:

**get_rotation().y = 0**
angle_to_player = -90
cos,sin			= 1, 0
player_adj_origin	= 0,-1

**get_rotation().y = -45**
angle_to_player = 0
cos,sin			= 0.707107, -0.707107
player_adj_origin	= 0.707107, -0.707107

**get_rotation().y = -90**
angle_to_player = 90
cos,sin			= -0, -1
player_adj_origin	= 1,0

**get_rotation().y = -135**
angle_to_player = 180
cos,sin			= -0.707107, -0.707107
player_adj_origin	= 0.707107, 0.707107

**get_rotation().y = 180**
angle_to_player = -90
cos,sin			= -1, -0
player_adj_origin	= 0,1

**get_rotation().y = 135**
angle_to_player = 0
cos,sin			= -0.707107, 0.707107
player_adj_origin	= -0.707107, 0.707107

**get_rotation().y = 90**
angle_to_player = 90
cos,sin			= -0, 1
player_adj_origin	= -1,-0

**get_rotation().y = 45**
angle_to_player = 180
cos,sin			= 0.707107, 0.707107
player_adj_origin	= -0.707107, -0.707107

Note how at -45 degrees and 135 degrees, everything matches and it works.

The best way I can understand it is that is seems there are two coordinate systems, or two bases. Imagine two unit circles. On the first, 0 degrees is N, -90 is E, what you'd expect. On the second, -90 degrees is N, 0 is E, 180 is W.
It's like when the first one is rotated clockwise, the second one is rotated counterclockwise, if that makes any sense. I think it has something to do with the coordinate system used for the angle_to() function?

I don't know where to go from here. Can somebody please walk me through how to fix this? I don't think a link to the documentation will be enough, I've been reading it for hours and do not think I can solve this without seeing specific code.

Thank you SO much for taking the time to read this and respond!

  • Yep, it's a coordinate system issue.
    Vector2(cos,sin) is a cartesian coordinate system where 0 degrees is to the right and the y axis is up. But looking overhead at Godot's 3D coordinate system you get z down instead of y up, and you probably want 0 degrees to be either +Z or -Z, not to the right.

    Luckily you can mess with the cos and sin to change systems. Swapping them and making them positive or negative will change the coordinate system by 90 or 180 degrees (and reverse the rotation direction).

    A little trial and error (it's 3:37am, I didn't feel like working it out mathematically) gives this:

    	var offset = player.global_transform.origin - NPC.global_transform.origin
    	var dir = Vector2(offset.x,offset.z)
    	var NPCAngle = NPC.get_rotation().y
    	var angleBetween = -rad2deg(dir.angle_to(Vector2(-sin(NPCAngle),-cos(NPCAngle))))

    The value of angleBetween will now match the angles you are checking on (eg. if the right side of the NPC is facing the player, an angle around 90 will be returned)

    This does assume your NPC's forward direction is along -Z in 3D (so right is +X). If your NPC faces +Z, change the angleBetween to:

    var angleBetween = -rad2deg(dir.angle_to(Vector2(sin(NPCAngle),cos(NPCAngle))))

    removing the - from the sin and cos. That rotates the angle 180 degrees.

Yep, it's a coordinate system issue.
Vector2(cos,sin) is a cartesian coordinate system where 0 degrees is to the right and the y axis is up. But looking overhead at Godot's 3D coordinate system you get z down instead of y up, and you probably want 0 degrees to be either +Z or -Z, not to the right.

Luckily you can mess with the cos and sin to change systems. Swapping them and making them positive or negative will change the coordinate system by 90 or 180 degrees (and reverse the rotation direction).

A little trial and error (it's 3:37am, I didn't feel like working it out mathematically) gives this:

	var offset = player.global_transform.origin - NPC.global_transform.origin
	var dir = Vector2(offset.x,offset.z)
	var NPCAngle = NPC.get_rotation().y
	var angleBetween = -rad2deg(dir.angle_to(Vector2(-sin(NPCAngle),-cos(NPCAngle))))

The value of angleBetween will now match the angles you are checking on (eg. if the right side of the NPC is facing the player, an angle around 90 will be returned)

This does assume your NPC's forward direction is along -Z in 3D (so right is +X). If your NPC faces +Z, change the angleBetween to:

var angleBetween = -rad2deg(dir.angle_to(Vector2(sin(NPCAngle),cos(NPCAngle))))

removing the - from the sin and cos. That rotates the angle 180 degrees.

    Kojack This worked perfectly! Yes my NPC's forward direction was -Z.

    Seriously, thank you so much.