Camera rotation not applying correctly

Godot Version

4.2.2

Question

My camera used to work fine, I had it all set up properly and it was functional. Then I added multiplayer functionality and had to move some things around to and move the inputs from the player themselves the the multiplayer synchronizer. Before my camera moved 1:1 and going back to a previous version it still does. I am very new to multiplayer as a whole and dont know if I messed up on a single line of code or something?
old code

class_name MainCamera
extends Node3D
##Body
var target_body:CharacterBody3D=null
##Camera
@onready var gimble = $Gimble
@onready var camera = $Gimble/player_camera
##camera dynamics
@export_range(0.1,10,.1) var camera_smoothing:float=10.0
#camera zoom
var camera_zoom_direction:float=0
@export var camera_zoom_start:float = 5
@export_range(0,100,1) var camera_zoom_speed = 5
@export_range(0,100,0.01) var camera_zoom_min = 0
@export_range(0,100,1) var camera_zoom_max = 10
@export_range(0,2,.1) var camera_zoom_damp:float= .92 #this needs to be less then 1.0
#camera rotate
var camera_rotate_direction:int=0
@export_range(0,10,0.1) var camera_rotate_speed:float= 0.20
@export_range(0,20,1) var camera_rotate_y_speed:float=2
@export_range(0,10,1) var camera_rotate_x_min:float= deg_to_rad(-90)
@export_range(0,10,1) var camera_rotate_x_max:float= deg_to_rad(90)
##Checks
# mouse
var mouse_last_position:Vector2=Vector2.ZERO
var can_camera_mouse_rotate:bool=true
var mouse_is_rotating_camera:bool=false
#keyboard
var keyboard_is_rotating_camera:bool=true
#camera
var can_camera_move:bool=true
var can_camera_target_move:bool=false
var can_camera_zoom:bool=true
var can_camera_rotate:bool=true
var can_camera_rotate_y:bool=true
var can_camera_rotate_x:bool=true



func _ready():
	pass

func _process(delta):
	follow_target(delta)
	camera_zoom(delta)
	camera_rotate(delta)
	camera_mouse_rotate(delta)
	if is_mouse_over_menu():
		can_camera_zoom=false
	else:
		can_camera_zoom=true

##Set target+follow
func set_target(entity:CharacterBody3D)->void:
	target_body=entity

func follow_target(delta)->void:
	if target_body==null:
		BodyCameraTarget.connect("set_camera_target", set_target)
		print("target not found")
		return
	position=lerp(position,target_body.position,camera_smoothing*delta)

##Inputs (Keyboard and/or Mouse)
#unhandled input
func _unhandled_input(event)->void:
#zoom
	if event.is_action("zoomin"):
		camera_zoom_direction=-1
	if event.is_action("zoomout"):
		camera_zoom_direction=1
#rotate from keyboard
	if event.is_action_pressed("camerarotateright"):
		camera_rotate_direction=-1
		keyboard_is_rotating_camera=true
	if event.is_action_pressed("camerarotateleft"):
		camera_rotate_direction=1
		keyboard_is_rotating_camera=true
	elif event.is_action_released("camerarotateright") or event.is_action_released("camerarotateleft"):
		keyboard_is_rotating_camera=false
#rotate from mouse
	if event.is_action_pressed("cameramouserotate"):
		mouse_last_position=get_viewport().get_mouse_position()
		mouse_is_rotating_camera=true
	elif event.is_action_released("cameramouserotate"):
		mouse_is_rotating_camera=false

#menu inputs
func is_mouse_over_menu():
	var system_menu=get_node_or_null("/root/GatheringHub/main_screen_hud/seed_system_menu_settings/system_menu/Primary Container")
	var mouse_position=get_viewport().get_mouse_position()
	if system_menu!=null:
		var menu_rectangle=system_menu.get_global_rect()
		if menu_rectangle.has_point(mouse_position):
			return true
	var branch_selection_group_nodes=get_tree().get_nodes_in_group("branch_bud_selection")
	for node in branch_selection_group_nodes:
		var container=node.find_child("PanelContainer")
		if container!=null:
			var panel_rect=container.get_global_rect()
			if panel_rect.has_point(mouse_position):
				return true
	return false

##Camera Zoom
func camera_zoom(delta:float)->void:
	if !can_camera_zoom:
		return
	var new_zoom:float=clamp(camera.position.z+camera_zoom_speed*camera_zoom_direction*delta,camera_zoom_min,camera_zoom_max)
	camera.position.z=new_zoom
	camera_zoom_direction*=camera_zoom_damp

##Camera Rotation
#camera rotate position (y axis)
func camera_rotate(delta:float)->void:
	if !can_camera_rotate or !keyboard_is_rotating_camera:
#print("camera rotate locked") This will spam messages because keyboard_is_rotating_camera unless pressed
		return
#to rotate
	camera_rotate_y(delta, camera_rotate_direction)

#camera rotate base (y axis)
func camera_rotate_y(delta:float, direction:float)->void:
	if !can_camera_rotate_y:
		print("camera rotate y locked")
		return
	rotation.y+=direction*camera_rotate_y_speed*delta #this turned off stops it from left/right
	BodyCameraTarget.emit_camera_rotation(rotation.y)

#camera rotate gimble (x axis)
func camera_rotate_x(delta:float, direction:float)->void:
	if !can_camera_rotate_x:
		print("camera rotate x locked")
		return
	var new_rotation_x:float=gimble.rotation.x #turn off these 4 lines of code and it stops rotating up/down
	new_rotation_x-=direction*camera_rotate_speed*delta
	new_rotation_x=clamp(new_rotation_x,camera_rotate_x_min,camera_rotate_x_max)
	gimble.rotation.x=new_rotation_x

#camera mouse rotate function
func camera_mouse_rotate(delta:float)->void:
	if !can_camera_mouse_rotate or !mouse_is_rotating_camera:
		#print("mouse rotated locked") #it will SPAM messages because mouse_is_rotating_camera function is turned OFF unless pressed
		return
	var mouse_offset:Vector2=get_viewport().get_mouse_position()
	mouse_offset=mouse_offset-mouse_last_position
	mouse_last_position=get_viewport().get_mouse_position()
	camera_rotate_y(delta,mouse_offset.x)
	camera_rotate_x(delta,mouse_offset.y)```
new code
extends MultiplayerSynchronizer


##Body
var input_direction :=Vector2()

##Camera
@export var camera_base : Node3D
@export var camera_rotation : Node3D
@export var camera : Camera3D
##camera dynamics
@export_range(0.1,10,.1) var camera_smoothing:float=10.0
#camera zoom
var camera_zoom_direction:float=0
@export var camera_zoom_start:float = 5
@export_range(0,100,1) var camera_zoom_speed = 5
@export_range(0,100,0.01) var camera_zoom_min = 0
@export_range(0,100,1) var camera_zoom_max = 10
@export_range(0,2,.1) var camera_zoom_damp:float= .92 #this needs to be less then 1.0
#camera rotate
var camera_rotate_direction:int=0
@export_range(0,10,0.1) var camera_rotate_speed:float= 0.20
@export_range(0,20,1) var camera_rotate_y_speed:float=2
@export_range(0,10,1) var camera_rotate_x_min:float= deg_to_rad(-90)
@export_range(0,10,1) var camera_rotate_x_max:float= deg_to_rad(90)
##Checks
# mouse
var mouse_last_position:Vector2=Vector2.ZERO
var can_camera_mouse_rotate:bool=true
var mouse_is_rotating_camera:bool=false
#keyboard
var keyboard_is_rotating_camera:bool=true
#camera
var can_camera_move:bool=true
var can_camera_target_move:bool=false
var can_camera_zoom:bool=true
var can_camera_rotate:bool=false
var can_camera_rotate_y:bool=true
var can_camera_rotate_x:bool=true

func _ready():
##Checks Authority
	if get_multiplayer_authority() == multiplayer.get_unique_id():
		camera.make_current()
	else:
		set_process(false)
		set_process_input(false)

func _process(delta):
	body_controller()
	camera_controller(delta)
	is_search_bar_selected()

func body_controller():
	if can_camera_move:
		input_direction=Input.get_vector("moveleft","moveright","moveforward","movebackward")

##Camera
func camera_controller(delta):
	if !can_camera_target_move:
		camera_zoom(delta)
		camera_rotate(delta)
		camera_mouse_rotate(delta)
	if is_mouse_over_menu():
		can_camera_zoom=false
	else:
		can_camera_zoom=true

##Inputs (Keyboard and/or Mouse)
#unhandled input
func _unhandled_input(event)->void:
#zoom
	if event.is_action("zoomin"):
		camera_zoom_direction=-1
	if event.is_action("zoomout"):
		camera_zoom_direction=1
#rotate from keyboard
	if event.is_action_pressed("camerarotateright"):
		camera_rotate_direction=-1
		keyboard_is_rotating_camera=true
	if event.is_action_pressed("camerarotateleft"):
		camera_rotate_direction=1
		keyboard_is_rotating_camera=true
	elif event.is_action_released("camerarotateright") or event.is_action_released("camerarotateleft"):
		keyboard_is_rotating_camera=false
#rotate from mouse
	if event.is_action_pressed("cameramouserotate"):
		mouse_last_position=get_viewport().get_mouse_position()
		mouse_is_rotating_camera=true
	elif event.is_action_released("cameramouserotate"):
		mouse_is_rotating_camera=false

##Camera Zoom
func camera_zoom(delta:float)->void:
	if !can_camera_zoom:
		return
	var new_zoom:float=clamp(camera.position.z+camera_zoom_speed*camera_zoom_direction*delta,camera_zoom_min,camera_zoom_max)
	camera.position.z=new_zoom
	camera_zoom_direction*=camera_zoom_damp

##Camera Rotation
#camera rotate position (y axis)
func camera_rotate(delta:float)->void:
	if !can_camera_rotate or !keyboard_is_rotating_camera:
#print("camera rotate locked") This will spam messages because keyboard_is_rotating_camera unless pressed
		return
#to rotate
	camera_rotate_y(delta, camera_rotate_direction)
	print (camera_rotate_direction)

#camera rotate base (y axis)
func camera_rotate_y(delta:float, direction:float)->void:
	if !can_camera_rotate_y:
		print("camera rotate y locked")
		return
	camera_base.rotation.y+=direction*camera_rotate_y_speed*delta #this turned off stops it from left/right

#camera rotate gimble (x axis)
func camera_rotate_x(delta:float, direction:float)->void:
	if !can_camera_rotate_x:
		print("camera rotate x locked")
		return
	var new_rotation_x:float=camera_rotation.rotation.x #turn off these 4 lines of code and it stops rotating up/down
	new_rotation_x-=direction*camera_rotate_speed*delta
	new_rotation_x=clamp(new_rotation_x,camera_rotate_x_min,camera_rotate_x_max)
	camera_rotation.rotation.x=new_rotation_x

#camera mouse rotate function
func camera_mouse_rotate(delta:float)->void:
	if !can_camera_mouse_rotate or !mouse_is_rotating_camera:
		#print("mouse rotated locked") #it will SPAM messages because mouse_is_rotating_camera function is turned OFF unless pressed
		return
	var mouse_offset:Vector2=get_viewport().get_mouse_position()
	mouse_offset=mouse_offset-mouse_last_position
	mouse_last_position=get_viewport().get_mouse_position()
	camera_rotate_y(delta,mouse_offset.x)
	camera_rotate_x(delta,mouse_offset.y)

func get_camera_rotation_basis()->Basis:
	return camera_base.global_transform.basis

func get_camera_rotation():
	return camera_base.rotation.y

##Menus
func _set_process_input(enable:bool)->void:
	if can_camera_move:
		can_camera_move=enable

#menu search
func is_search_bar_selected():
	var search_bar=get_node_or_null("/root/GatheringHub/main_screen_hud/seed_system_menu_settings/system_menu/Primary Container/primary_path/direction/root_menu_text_display/menu_display_text")
	if search_bar!=null:
		if search_bar.has_focus():
			_set_process_input(false)
		else:
			_set_process_input(true)
	return false

#menu inputs
func is_mouse_over_menu():
	var system_menu=get_node_or_null("/root/GatheringHub/main_screen_hud/seed_system_menu_settings/system_menu/Primary Container")
	var mouse_position=get_viewport().get_mouse_position()
	if system_menu!=null:
		var menu_rectangle=system_menu.get_global_rect()
		if menu_rectangle.has_point(mouse_position):
			return true
	var branch_selection_group_nodes=get_tree().get_nodes_in_group("branch_bud_selection")
	for node in branch_selection_group_nodes:
		var container=node.find_child("PanelContainer")
		if container!=null:
			var panel_rect=container.get_global_rect()
			if panel_rect.has_point(mouse_position):
				return true

To me both codes feel functionally the same. All the print functions though do very different though
new code compared with new code
starting rotation
0
end rotation (roughly 360)
3.36666512489319
old code (roughly 360)
start
0
end
6.23333
(I would do a side by side comparingson of the prints but its an obnoxious amount of numbers, its just one is roughly x2 the other)
It seems that the camera rotation speed is roughly half of what the code used to be? I dont understand why the rotation got messed with this weirdly by switching to the inputs to multiplayer script?
I tried doing
current_body.rotation.y=player_input.get_camera_rotation()*2 (hoping that simply multiplying the number by 2 would fix it but instead of needing 720 to do a full turn its closer to 560? (to get this print 6.26666450500488)
*4 is about 450 (to get this print 6.26666593551636)
its bizarre to me how they got such different results. I am curious why my camera rotation is no longer 1:1 and seems to be 1:? (Its difficutlt to figure out how much to *? as it seems x12 prints 6.40000104904175 after only 380 rotation (so the math is not linear?)
little bizarre to me. Anyone have advice? or should I just scratch this and try a different approach?

Just be aware that rotation logic internally is in radians, and 2*PI, or 6.26, is 360°.

Also the inspector can go above 360° but internally it will rollover to 0 radians. It’s kind of like a modulus happens 900° % 360° = 180° or (3.145 radians, PI)

There will also be some floating point error that could cause drifts. Which can cause the basis matrix to “unsquare” and it is advise to orthonormalize the basis if necessary.

I learned something new from thinking outside the box. I had a personal question of hmmm, does the camera base (the empty space) rotate different then the body?
I set my camera base to the body itself using @export : Node3D .apparently characterbody3D functions normally? lol I dont know if I should change that part, or what problems it might cause, but it works. Before It was just set to an empty space near the body. So that problem is fixed. I do have a camera Zoom that is still a little broken right now. Went to an older version of the code and it worked thier just fine. I think the problem might be something to do with camera.position.z not being updated to the server? but when I added it to the syncronization it didnt adjust. I think the zoom technically works because it wiggles every time I try to use it (the position keeps being reset to starting point).

##Camera Zoom
#currently broken
func camera_zoom(delta:float)->void:
	if !can_camera_zoom:
		return
	var new_zoom:float=clamp(camera.position.z+camera_zoom_speed*camera_zoom_direction*delta,camera_zoom_min,camera_zoom_max)
	camera.position.z=new_zoom
	camera_zoom_direction*=camera_zoom_damp

code for convienience but I feel its more of a server syncronization issue?
althought me changing the camera_base to the body fixed my rotation issue… I just dont know what to change my camera?
image
I do have a spring arm, it might be yanking me back now that I think about it… yep It was the spring arm LOL, oh boy, im just solving all sorts of things today.
any advice on how to allow zoom to work even with a spring arm? are spring arms even necassary? its just for cameras bumping into walls and stuff and adjusting them.

1 Like
func _process(delta)
	Aim(delta)


func Aim(delta):
	if Input.is_action_pressed("Right click"):
		$Neck/SpringArm3D.spring_length = move_toward($Neck/SpringArm3D.spring_length,zoominlength, delta*zoomspeed)
#if you don't know already about move_toward() it goes (from,to,howmuch)
#negatives can be used in the howmuch to make it move away
#by using delta in the howmuch you can make a smoother transition
	else:
		$Neck/SpringArm3D.spring_length = move_toward($Neck/SpringArm3D.spring_length,defultlength, delta*zoomspeed)

but otherwise springarms are used to for stuff bumping into other stuff, but not just for cameras, if you want a laser that get stop on contact with a block or something, it could be a solution