I'm trying to create a 3D camera for a simulation game based on this video that shows how to do it in Unity. When translating the code to GDScript, I ran into a problem: I am unable to get the camera to rotate. Here is my GDScript interpretation of the rotation part of script from the video (timestamps: 8:50-10:10):
export(float) var rotation_amount = 1
export(Quat) var new_rotation
func _ready():
new_rotation = rotation
func _process(delta):
handle_movement_input(delta)
func handle_movement_input(delta):
if(Input.is_action_pressed("cam_rotate_left")):
new_rotation *= Quat(Vector3.UP * rotation_amount).get_euler()
if(Input.is_action_pressed("cam_rotate_right")):
new_rotation *= Quat(Vector3.UP * -rotation_amount).get_euler()
rotation = rotation.linear_interpolate(new_rotation, delta * movement_time)
You claim new_rotation
is a Quat
(export(Quat)
). But you don't only override it right away (in _ready
), you override it with a Vector3
(rotation
is a Vector3
that represent euler angles):
func _ready():
new_rotation = rotation
If you made a typed export variable, Godot would have told you about that problem:
export var new_rotation:Quat
Yes, GDScript has typed variables. Use them.
If you wanted to work with quaternions (as in the video), you can get the rotation Quat
like this:
var new_rotation:Quat
func _ready():
new_rotation = transform.basis.get_rotation_quat()
Then you can compose quaternions by multiplication, interpolate them with slerp
and use get_euler()
at the end.
By the way, when you create a Quat
passing a Vector3
, it is understood as euler angles. Which is equivalent to Unity's Quaternion.Euler
(which they use in the video). Also, calling to_euler
converts the Quat
to euler angles, which makes using Quat
pointless since it was just created from euler angles (and it is not at all what they do on the video).
If you wanted to work with euler angles, you should be adding them instead of multiplying them.
However, since you only care about rotation around the vertical, I'm here to tell you: use a float
. Keep it simple. The key where do you put the code.
The weight of Vector3.linear_interpolate
(or of Quat.slerp
or of lerp
) is a value between 0 and 1. If it is 0, you get the original value. If it is 1, you get the new value.
Adding interpolation will smooth the movement, but also gives you a deceleration/slowing down effect. So you lose sharp control.
If you smooth rotation and stop sharply, multiply the angle by delta and some scaling constant. That is, instead of doing this: Quat(Vector3.UP * rotation_amount)
you do this: Quat(Vector3.UP * rotation_amount * delta)
. And do no interpolation.
If you are writing that code in the Camera
node it is not going to work. Because rotation
changes the direction the camera is looking. And that is precisely what we don't want. Instead we want to rotate around some other point in space.
Thus, set this up in the scene tree:
Pivot:Spatial
└ Camera
You would place the Pivot
(an Spatial
node) on the ground, then position the Camera
looking at it from the air. And you put the rotation logic in the Pivot
. And since that Pivot
is only in charge of that rotation, you can use a float
for the angle. And life is easy and simple (use lerp
to interpolate float
).
Can you do it in a single node? Yes. I have an over-engineered solution that does exactly that elsewhere (including the explanation of why it isn't the best idea).