Godot Version
4.3
Question
In my TPS game, I have a camera that follows the player and uses a state machine to change behavior between Free Look, Free Aim, and, Target Locked Aim.
The Target Locked Aim state behaves weird when looking at targets from their sides, the camera looks down and then pulls up to where its supposed to look. But this only happens when standing at the targets’ sides. When you’re standing directly in front or behind it works fine.
I’m pretty sure it has something to do with how I handle the x-axis rotation, but I’ve tried so many things and can’t figure it out 
Here is a video showing the problem:
And the code for this camera state:
func _physics_update(_delta: float)-> void:
# actor is the root node of the camera scene which handles y-rotation
# this code removes the y-component to calculate the angle between
# current direction and new direction around the y-axis
var angles: Vector2 = Vector2()
var forward: Vector3 = (Vector3(1.0, 0.0, 1.0) * -actor.global_basis.z).normalized()
var targ_vect: Vector3 = (Vector3(1.0, 0.0, 1.0) * (target.global_position - actor.global_position)).normalized()
angles.y = forward.signed_angle_to(targ_vect, Vector3.UP)
# height adds 2/3rds the height of the target character so the camera looks
# roughly at the chest area instead of the ground, this is used for the x-rotation
var height: float = 0.0
if target is Character:
height = target.get_height() * 0.66
# x_piv is the second node in the camera scene which handles rotation around
# x-axis
# this code does the same thing as before, but this time it removes the
# x-component to get the angle around the x-axis from the x_piv node to the target
forward = (Vector3(0.0, 1.0, 1.0) * -actor.x_piv.global_basis.z).normalized()
targ_vect = (Vector3(0.0, 1.0, 1.0) * ((target.global_position + Vector3(0.0, height, 0.0)) - actor.x_piv.global_position)).normalized()
angles.x = forward.signed_angle_to(targ_vect, actor.x_piv.global_basis.x)
var camera_joy: Vector2 = Vector2(Input.get_axis("LookLeft", "LookRight"), Input.get_axis("LookUp","LookDown"))
if camera_joy:
# when looking at a target from their left side this code also behaves weird
# but this isn't my immediate concern
angles -= Vector2(camera_joy.y, camera_joy.x) * crosshair_sensitivity
if not is_zero_approx(angles.length()):
actor.rotation.y = lerp(actor.rotation.y, actor.rotation.y + angles.y, (rotation_sensitivity * 10.0) * _delta)
actor.x_piv.rotation.x = lerp(actor.x_piv.rotation.x, actor.x_piv.rotation.x + angles.x, (rotation_sensitivity * 10.0) * _delta)
# this code moves the camera to the right position relative to the player
var transf: Transform3D = player.global_transform
# uses camera's basis because character isnt usually facing the target
# when entering this state, this prevents the camera from circling around
# while we wait for the character to face target
transf.basis = actor.global_basis
actor.global_position = actor.global_position.lerp(transf * cam_offset, 15.0 * _delta)
yes. very interesting.
so this is the code?
func _physics_update(_delta: float)-> void:
var angles: Vector2 = Vector2()
var forward: Vector3 = (Vector3(1.0, 0.0, 1.0) * -actor.global_basis.z).normalized()
var targ_vect: Vector3 = (Vector3(1.0, 0.0, 1.0) * (target.global_position - actor.global_position)).normalized()
angles.y = forward.signed_angle_to(targ_vect, Vector3.UP)
var height: float = 0.0
if target is Character:
height = target.get_height() * 0.66
forward = (Vector3(0.0, 1.0, 1.0) * -actor.x_piv.global_basis.z).normalized()
targ_vect = (Vector3(0.0, 1.0, 1.0) * ((target.global_position + Vector3(0.0, height, 0.0)) - actor.x_piv.global_position)).normalized()
angles.x = forward.signed_angle_to(targ_vect, actor.x_piv.global_basis.x)
var camera_joy: Vector2 = Vector2(Input.get_axis("LookLeft", "LookRight"), Input.get_axis("LookUp","LookDown"))
if camera_joy:
angles -= Vector2(camera_joy.y, camera_joy.x) * crosshair_sensitivity
if not is_zero_approx(angles.length()):
actor.rotation.y = lerp(actor.rotation.y, actor.rotation.y + angles.y, (rotation_sensitivity * 10.0) * _delta)
actor.x_piv.rotation.x = lerp(actor.x_piv.rotation.x, actor.x_piv.rotation.x + angles.x, (rotation_sensitivity * 10.0) * _delta)
transf.basis = actor.global_basis
actor.global_position = actor.global_position.lerp(transf * cam_offset, 15.0 * _delta)
What debugging have you done? Have you used print at different parts of your code to find where the bug is?
It is harder to debug for you since we don’t have your setup. I would use print statements at each level of your code printing your characters camera rotation, then replicate the bug, and see where exactly it is being set incorrectly.
it’s a wierd question honestly.
why are the angles in Vector2?
Because the designed z depth of a Vector2 is 0.
And, apparently flipped. So in 3D space the z depth moves around the y axis.
So I see in your code the targeting takes care of height. Which is set to 0.0
So either that’s the problem. Or, just simply the use of Vector2
Whatever harvard you got going on. Maybe if you flipped that vector2 to closest field at 90, degrees, perpendicular, opposite, form, conforming.
I can go a little further. Just bear.
I think what is happening. Is this setup is actually working correctly. But, you are describing the problem with the sides of Vector2 in 3D space.
Your active model is divided to two parts by this Vector2 plane.
That’s what I see.
And, obviously we all can agree there’s nothing to look at if we view the model from the side.
Working to distinguish the differences from left to right and right to left is already done in your code. When you use the code in a form of a equation like you do. It takes the differences as negative and positive automatically. That’s why your model moves left and right correctly. And, not inverted. Because I would want to find out about controls inversions before even asking about your problems. Because control inversions are more important to me. The only problem is there’s no Z axis in Vector2 to move to.
My apologies for replying late to my own post, i guess my email notifications stopped.
The Vector2 is only used to store the rotation amounts for the x-axis and y-axis, its not used for any calculations
I’ve printed the angle to rotate on the x-axis and the first 2 frames have the angle at much too big a number, around 2 radians when it should definitely be less than 1. it quickly drops down to the real amount which is when the camera finally centers on the target correctly. So whatever is making the angle a ridiculous amount for the first 2 frames is whats causing this I believe.
@gionggone I will try messing with the height variable and see if thats affecting it. Im sorry but I don’t really understand the rest of your replies. Unless I’m missing something, it doesn’t seem like the vector2 would affect anything because its not used for any 3d calculations, it simply stores the amount of rotations needed for each axis, x and y, because nothing should be rotating on the z-axis.
Okay. I get it.
angles.y = forward.signed_angle_to(targ_vect, Vector3.UP)
- why is it specifically Y axis on the angles.
- why is it controlling Vector3.UP
Because the code is doing exactly what is described.
How do you know the code set up is working around your Y axis?
e.g. in your 3D space that is.
I have to say that Vector3 is just more powerful in this situation.
And, I do believe the original problem will persist.
angles.y = forward.signed_angle_to(targ_vect, Vector3.FORWARD)
Maybe try that. I’m pretty sure you will just have to try out for X vs Y after that. Which will just be inverted. It’s not that big of a problem.
Ahh that makes sense, I totally missed the axis of rotation in the signed_angle_to function. Thank you!
Since my last reply, I’ve actually found a much cleaner, elegant solution that works.
y rotation is calculate with
atan2(direction_to.x, direction_to.z)
and the x rotation is calculated with
asin(direction_to.y)
Then just use lerp_angle, its so much nicer than my original code