Godot Version
4.4.1
Question
Hey folks, I’m trying to implement a free look camera on a game controller using the right analogue stick to rotate the camera around a flying spaceship in 360 degrees so the player can look anywhere. The left stick controls the movement of the ship.
I already have this working very nicely with a mouse but I’d like to include controller support as well. I’m using the PhantomCamera3D add-on which helps immensely with the camera movement but I need help with passing it the correct values to smoothly rotate around the ship.
The code I have written sorta works but there a few problems with it which I’ll list below:
if Input.is_action_pressed("free_look_up"):
free_look_rotation_x -= deg_to_rad(1)
elif Input.is_action_pressed("free_look_down"):
free_look_rotation_x += deg_to_rad(1)
elif Input.is_action_pressed("free_look_right"):
free_look_rotation_y -= deg_to_rad(1)
elif Input.is_action_pressed("free_look_left"):
free_look_rotation_y += deg_to_rad(1)
pcam_rotation_degrees.x = free_look_rotation_x
pcam_rotation_degrees.y = free_look_rotation_y
var camera_rotation = pcam.get_third_person_quaternion()
var target_rotation = Quaternion.from_euler(pcam_rotation_degrees)
camera_rotation = camera_rotation.slerp(target_rotation, 0.9)
# camera_rotation = camera_rotation * target_rotation
pcam.set_third_person_quaternion(camera_rotation)
The issue I have are:
- The movement is very stuttery and inconsistent
- The rotation goes off-axis very easily and goes all wibbly-wobbly unless you’re super gentle with the stick.
- Pushing the stick one way should keep the rotation rate consistently moving on that axis but what happens is it only registers an input when you start pushing it. If you move it to the edge of the ring it just stops moving altogether. Is this hitting the deadzone or something?
I tried using slerp() to interpolate the rotation which also kinda worked but was still a worse result than the above code.
What changes would you make to my code to help with the above issues?
Is this sample in a _process
function or is in under _input
? if the latter then it will only trigger rotation when a input is pressed, not over time.
Ah I see! It is in the _input()
function yes. I changed it and it works far better, thanks!
I have the mouse look functionality in an _unhandled_input()
so I figured the joypad functionality should go in _input()
for some reason.
The movement of the camera is still a bit janky and will randomly jump to a different axis but the movement is completely smooth now. I reckon I’ll just need to play around with how I get the input from the controller to fix that.
Yeah, not too sure what causes the random jumps, maybe the slerp
isn’t what you need but it’s tough to say for sure.
I would make sure your units are correct, seems like you are taking radians and applying them to “rotation_degrees”, luckily this variable is being used as radians in Quaternion.from_euler
.
free_look_rotation_y += deg_to_rad(1)
pcam_rotation_degrees.x = free_look_rotation_x
pcam_rotation_degrees.y = free_look_rotation_y
var target_rotation = Quaternion.from_euler(pcam_rotation_degrees)
But if used in _process
this will be 1° per frame which will mean slower movement on slower computers and faster on high-framerate computers. Use * delta
and a higher value to make this per second
# rotates 60 degrees per second
free_look_rotation_y += deg_to_rad(60) * delta
The mouse movement makes sense in a _input()
function because the mouse’s movement is accumulated, event.screen_relative
tells how many dots the mouse moved since last _input()
where as += 1° does not tell how far or how long the controller has been held, and process helps to solve the latter, acting as long as the control is held.
1 Like
Yeah the degrees part is just leftover from a previous approach but I found out using radians and quats yields better results.
I will try multiplying the rotation with delta and report back. Perhaps the framerate dependency is causing some weirdness. Hopefully that’s all it is.
1 Like
So I got it much much closer. Pretty psyched about this approach. It’s still juddery and sharp but it’s pretty damn close to what I wanted. I want the speed to ramp a bit more rather than just GO. I know I need something like lerp but I’m unclear which values to lerp to which others.
Down the line I’d like to have a way to reset the camera to the XZ plane of the world because even though it’s space with no up or down it’s still a little jarring for me as a player when it goes off that plane.
For those curious here is my 90%-of-the-way code:
func _look_around() -> void:
var rotation_rate: Vector2
# We want to change the rotation in predictable steps
# while the joystick is pushed in a certain direction rather than using
# the discrete values sent by the controller.
if Input.is_action_pressed("free_look_up"):
# free_look_rotation_x = Vector2.DOWN
rotation_rate.x = -Input.get_action_strength("free_look_up")
elif Input.is_action_pressed("free_look_down"):
# free_look_rotation_x = Vector2.UP
rotation_rate.x = Input.get_action_strength("free_look_down")
elif Input.is_action_pressed("free_look_right"):
# free_look_rotation_y = Vector2.RIGHT
rotation_rate.y = -Input.get_action_strength("free_look_right")
elif Input.is_action_pressed("free_look_left"):
# free_look_rotation_y = Vector2.LEFT
rotation_rate.y = Input.get_action_strength("free_look_left")
pcam_rotation_vector.x = deg_to_rad(remap(rotation_rate.x, -1, 1, -5, 5))
pcam_rotation_vector.y = deg_to_rad(remap(rotation_rate.y, -1, 1, -5, 5))
pcam_rotation_degrees.z = 0.0
var camera_rotation = pcam.get_third_person_quaternion()
var target_rotation = Quaternion.from_euler(pcam_rotation_vector)
camera_rotation = camera_rotation * target_rotation
pcam.set_third_person_quaternion(camera_rotation)
Small advice you can reduce all of your if/action_strengths by using Input.get_vector
, and your remap
is functionaly equivalent to multiplying by 5. I don’t see delta
in the equation anymore either so it may be frame dependent again.
func _look_around() -> void:
var rotation_rate: Vector2 = Input.get_vector("free_look_right", "free_look_left", "free_look_up", "free_look_down")
pcam_rotation_vector = Vector3(rotation_rate.x, rotation_rate.y, 0) * 5
var camera_rotation = pcam.get_third_person_quaternion()
var target_rotation = Quaternion.from_euler(pcam_rotation_vector)
camera_rotation = camera_rotation * target_rotation
pcam.set_third_person_quaternion(camera_rotation)
I am not familiar with phantom camera but maybe using the set_third_person_rotation
instead would be better?
var accumulated_rotation := Vector3.ZERO
func _process(delta: float) -> void:
var rotation_rate: Vector2 = Input.get_vector("free_look_right", "free_look_left", "free_look_up", "free_look_down")
var pcam_rotation_vector := Vector3(rotation_rate.x, rotation_rate.y, 0) * 100 * delta
accumulated_rotation += pcam_rotation_vector
var old_rotation := pcam.get_third_person_rotation()
var new_rotation := old_rotation.move_toward(accumulated_rotation, 80 * delta)
pcam.set_third_person_rotation(new_rotation)
Thanks, I may well try this. The quaternion method does make the camera go in unexpected directions.
I tried it with delta and it didn’t really work but could have been a me problem I’m not sure.