I am trying to make the character be able to move and rotate independent of the camera. moving forward moves the character towards where the character is facing. moving backwards would make it seem as though the player is running towards the real player, behind the camera. All the tutorials Ive seen tend to pack these two things together and as a beginner I can't begin to pick them apart. the camera is to be controlled with mouse movement, not clicks or drags but like in an FPS. Basically: an MMO style camera similar to black desert, cube world, dark souls. I can stitch together some aspects of certain tutorials together but I lack the glue and understanding. Might have to come back and get good, im Malding over here

Do you have the camera script? It sounds like it's a third person camera?

Yes, for third person. For simplicity I'll also attach the whole file that comes from a tutorial with a good camera but lack of orbiting or "looking around" any direction automatically makes the player face that way. not what i desire here: extends KinematicBody


export var speed : float = 20
export var acceleration : float = 15
export var air_acceleration : float = 5
export var gravity : float = 0.98
export var max_terminal_velocity : float = 54
export var jump_power : float = 20

export(float, 0.1, 1) var mouse_sensitivity : float = 0.3
export(float, -90, 0) var min_pitch : float = -90
export(float, 0, 90) var max_pitch : float = 90

var velocity : Vector3
var y_velocity : float
#ive tried switching these around, changing what the pivot node is to no avail
onready var camera_pivot = $CameraPivot
onready var camera = $CameraPivot/CameraBoom/Camera

func _ready():
	Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)

func _process(delta):
	if Input.is_action_just_pressed("ui_cancel"):
		Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
		
func _input(event):
	if event is InputEventMouseMotion:
		rotation_degrees.y -= event.relative.x * mouse_sensitivity
		camera_pivot.rotation_degrees.x -= event.relative.y * mouse_sensitivity
		camera_pivot.rotation_degrees.x = clamp(camera_pivot.rotation_degrees.x, min_pitch, max_pitch)

func _physics_process(delta):
	handle_movement(delta)

func handle_movement(delta):
	var direction = Vector3()
	
	if Input.is_action_pressed("move_forward"):
		direction -= transform.basis.z
	
	if Input.is_action_pressed("move_backward"):
		direction += transform.basis.z
		
	if Input.is_action_pressed("move_left"):
		direction -= transform.basis.x
	
	if Input.is_action_pressed("move_right"):
		direction += transform.basis.x
	
	direction = direction.normalized()
	
	var accel = acceleration if is_on_floor() else air_acceleration
	velocity = velocity.linear_interpolate(direction * speed, accel * delta)
	
	if is_on_floor():
		y_velocity = -0.01
	else:
		y_velocity = clamp(y_velocity - gravity, -max_terminal_velocity, max_terminal_velocity)
	
	if Input.is_action_just_pressed("jump") and is_on_floor():
		y_velocity = jump_power
	
	velocity.y = y_velocity
	velocity = move_and_slide(velocity, Vector3.UP)

https://github.com/codewithtom/third-person-godot/tree/character-controller the only script is attached to the player. Ive asked a question similar to this before and one answer I got was to have a script attached to a camera instead that rotated it's parenting spacial (rig). I don't have the brains to implement the suggestion yet. the player file (the only one in the script), is set up as Player, with a spacial child, with a grandchild spring arm and finally a camera stemming from that arm.

@Dschoonmaker said: Do you have the camera script? It sounds like it's a third person camera?

I do it with two variables:

var cameraAngle = Vector2.ZERO
var cameraZoom = 2.25

cameraAngle is a vector2, X is horizontal rotation and Y is vertical. cameraZoom is the distance the camera should be away from the player.

I clamp them with:

cameraAngle.x = fmod(cameraAngle.x, 2*PI)
cameraAngle.y = clamp(cameraAngle.y, 0.01, PI-0.01)

(X isn't clamped, but if the angle is greater than 360, it goes back to 0. Both 0.01s are just a small margin to prevent error.)

and apply camera position with:

X+=sqrt(cameraZoom)*sin(cameraAngle.y)*(sqrt(cameraZoom)*cos(cameraAngle.x))
Z+=sqrt(cameraZoom)*sin(cameraAngle.y)*(sqrt(cameraZoom)*sin(cameraAngle.x))
Y+=cameraZoom*cos(cameraAngle.y)

XY&Z should be the camera's XY&Z position, I shortened it to make it easier to read.

Then for movement(I use a variable called moveDir, works the same as your direction), instead of something like this:

	moveDir.x -= Input.get_action_strength("p1_move_left")

I have this:

	moveDir.x -= cos(cameraAngle.x)*Input.get_action_strength("p1_move_forward")
	moveDir.z -= sin(cameraAngle.x)*Input.get_action_strength("p1_move_forward")
	moveDir.x -= cos(cameraAngle.x+PI)*Input.get_action_strength("p1_move_backward")
	moveDir.z -= sin(cameraAngle.x+PI)*Input.get_action_strength("p1_move_backward")
	moveDir.x -= cos(cameraAngle.x+(270*PI/180))*Input.get_action_strength("p1_move_left")
	moveDir.z -= sin(cameraAngle.x+(270*PI/180))*Input.get_action_strength("p1_move_left")
	moveDir.x -= cos(cameraAngle.x+(90*PI/180))*Input.get_action_strength("p1_move_right")
	moveDir.z -= sin(cameraAngle.x+(90*PI/180))*Input.get_action_strength("p1_move_right")

which handles movement in all directions(+ analog strength, works with joysticks). You probably don't need to understand this part, I've forgotten how exactly it works. But this code should do the trick.

I also clamp moveDir:

	moveDir = Vector3(clamp(moveDir.x, -1, 1),clamp(moveDir.y, -1, 1),clamp(moveDir.z, -1, 1))

I can explain more, tell me if you have any questions(and this is one method, I think I used another one in a different game).

Thanks for the reply. I will try this out. All of this script belongs to the camera script yes? input strength for joysticks wont conflict with key board presses either im guessing. I realize i worded the title of this to something quite misleading so im linking a video that quickly shows the desired movements. If your script mimics this system then I am golden.

as I was typing out some of your lines I got an error for this line ; cameraAngle.x = fmod(cameraAngle.x, 2*PI) "unexpected token: identifier: cameraAngle"

I don't know why you get that error. I might need to see the code to find the problem.

It's actually not in the camera script(at least the way I do it), I position the camera in the player script. So camera movement is handled in the player script(I do have a camera script, for smooth camera movement).

It does do everything mentioned in the video.

@Dschoonmaker said: I don't know why you get that error. I might need to see the code to find the problem.

It's actually not in the camera script(at least the way I do it), I position the camera in the player script. So camera movement is handled in the player script(I do have a camera script, for smooth camera movement).

It does do everything mentioned in the video.

Here is the code to the player script , all of the entire project:

extends KinematicBody
#old variables
export var speed : float = 20
export var acceleration : float = 15
export var air_acceleration : float = 5
export var gravity : float = 0.98
export var max_terminal_velocity : float = 54
export var jump_power : float = 20

export(float, 0.1, 1) var mouse_sensitivity : float = 0.3
export(float, -90, 0) var min_pitch : float = -90
export(float, 0, 90) var max_pitch : float = 90

#define moveDir as Vector3
var moveDir : Vector3
var y_velocity : float

onready var camera_pivot = $CameraPivot
onready var camera = $CameraPivot/CameraBoom/Camera

#new variables 
var cameraAngle = Vector2.ZERO
var cameraZoom = 2.25
#the error here V
X+=sqrt(cameraZoom)*sin(cameraAngle.y)*(sqrt(cameraZoom)*cos(cameraAngle.x))
Z+=sqrt(cameraZoom)*sin(cameraAngle.y)*(sqrt(cameraZoom)*sin(cameraAngle.x))
Y+=cameraZoom*cos(cameraAngle.y)
#clamping camera varibales
cameraAngle.x = fmod(cameraAngle.x, 2*PI)
cameraAngle.y = clamp(cameraAngle.y, 0.01, PI-0.01)


#capturing mouse input
func _ready():
	Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)

#making cursor visible on ui cancel
func _process(delta):
	if Input.is_action_just_pressed("ui_cancel"):
		Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
		
		
#rotate camera with mouse input? probaly invalid now with new clamping variables
func _input(event):
	if event is InputEventMouseMotion:
		rotation_degrees.y -= event.relative.x * mouse_sensitivity
		camera_pivot.rotation_degrees.x -= event.relative.y * mouse_sensitivity
		camera_pivot.rotation_degrees.x = clamp(camera_pivot.rotation_degrees.x, min_pitch, max_pitch)

func _physics_process(delta):
	handle_movement(delta)

func handle_movement(delta):
	#clamping moveDir
	moveDir = Vector3(clamp(moveDir.x, -1, 1),clamp(moveDir.y, -1, 1),clamp(moveDir.z, -1, 1))
	#moveDir
	moveDir.x -= cos(cameraAngle.x)*Input.get_action_strength("move_forward")
	moveDir.z -= sin(cameraAngle.x)*Input.get_action_strength("move_forward")
	moveDir.x -= cos(cameraAngle.x+PI)*Input.get_action_strength("move_backward")
	moveDir.z -= sin(cameraAngle.x+PI)*Input.get_action_strength("move_backward")
	moveDir.x -= cos(cameraAngle.x+(270*PI/180))*Input.get_action_strength("move_left")
	moveDir.z -= sin(cameraAngle.x+(270*PI/180))*Input.get_action_strength("move_left")
	moveDir.x -= cos(cameraAngle.x+(90*PI/180))*Input.get_action_strength("move_right")
	moveDir.z -= sin(cameraAngle.x+(90*PI/180))*Input.get_action_strength("move_right")
	direction = direction.normalized()
	
	var accel = acceleration if is_on_floor() else air_acceleration
	velocity = velocity.linear_interpolate(direction * speed, accel * delta)
	
	if is_on_floor():
		y_velocity = -0.01
	else:
		y_velocity = clamp(y_velocity - gravity, -max_terminal_velocity, max_terminal_velocity)
	
	if Input.is_action_just_pressed("jump") and is_on_floor():
		y_velocity = jump_power
	
	velocity.y = y_velocity
	velocity = move_and_slide(velocity, Vector3.UP)

probably a mess. I added the code you provided to the tutorial project mentioned earlier. That could explain some of the problems. Honestly, I have a hard time understanding it all, gdscript in general. moveDir was never defined so i figured it would be a Vector3

#the error here V
X+=sqrt(cameraZoom)*sin(cameraAngle.y)*(sqrt(cameraZoom)*cos(cameraAngle.x))
Z+=sqrt(cameraZoom)*sin(cameraAngle.y)*(sqrt(cameraZoom)*sin(cameraAngle.x))
Y+=cameraZoom*cos(cameraAngle.y)
#clamping camera varibales
cameraAngle.x = fmod(cameraAngle.x, 2*PI)
cameraAngle.y = clamp(cameraAngle.y, 0.01, PI-0.01)

Your problem here is you call all of this outside any function. It should be in process or physics process(I have it in physics process, which is probably the right thing to do in my case because I have camera collisions), or some function called inside them; like your handle_movement()(I have update_camera()).

Also, X Y & Z are not defined. If your camera is a child of the player node, you probably want this instead:

cameraAngle.x = fmod(cameraAngle.x, 2*PI)
cameraAngle.y = clamp(cameraAngle.y, 0.01, PI-0.01)
#I have $Camera.translation now, $ means to get a child of this node.

#I also switched the order of clamping and moving, it's good to clamp after
#to avoid the camera moving where it shouldn't for a single frame.
#I think I had it in this order in my code, but I explained it in the opposite order.

$Camera.translation.x+=sqrt(cameraZoom)*sin(cameraAngle.y)*(sqrt(cameraZoom)*cos(cameraAngle.x))
$Camera.translation.z+=sqrt(cameraZoom)*sin(cameraAngle.y)*(sqrt(cameraZoom)*sin(cameraAngle.x))
$Camera.translation.y+=cameraZoom*cos(cameraAngle.y)

moveDir is a Vector3, it's a variable used for changing velocity from input(like your direction).

@Dschoonmaker node set up is Player (kinematic body) | cameraPivot (spatial) (also contains the current script) | cameraBoom (springarm) | camera (camera) | mesh Instance |_ collision shape. code now looks like this:


extends KinematicBody
#old variables
export var speed : float = 20
export var acceleration : float = 15
export var air_acceleration : float = 5
export var gravity : float = 0.98
export var max_terminal_velocity : float = 54
export var jump_power : float = 20
export(float, 0.1, 1) var mouse_sensitivity : float = 0.3
export(float, -90, 0) var min_pitch : float = -90
export(float, 0, 90) var max_pitch : float = 90
#define moveDir as Vector3
var moveDir : Vector3
var y_velocity : float
onready var camera_pivot = $cameraRig
onready var camera = $CameraPivot/CameraBoom/Camera
#new variables
var cameraAngle = Vector2.ZERO
var cameraZoom = 2.25
#defining x, y, z
var X = Vector3(1,0,0)
var Y = Vector3(0,1,0)
var Z = Vector3(0,0,1)

#clamping camera variables
#capturing mouse input
func _ready():
	Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
#making cursor visible on ui cancel
func _process(delta):
	if Input.is_action_just_pressed("ui_cancel"):
		Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
#getting errors here now, compiler doesn't know what rotation_degrees is. I thought that was built in. 
func _input(event):
	if event is InputEventMouseMotion:
		rotation_degrees.y -= event.relative.x * mouse_sensitivity
		camera_pivot.rotation_degrees.x -= event.relative.y * mouse_sensitivity
		camera_pivot.rotation_degrees.x = clamp(camera_pivot.rotation_degrees.x, min_pitch, max_pitch)
func _physics_process(delta):
	handle_movement(delta)
	update_camera()
	
	
func update_camera():
	cameraAngle.x = fmod(cameraAngle.x, 2*PI)
	cameraAngle.y = clamp(cameraAngle.y, 0.01, PI-0.01)
	#X+=sqrt(cameraZoom)*sin(cameraAngle.y)*(sqrt(cameraZoom)*cos(cameraAngle.x))
	#Z+=sqrt(cameraZoom)*sin(cameraAngle.y)*(sqrt(cameraZoom)*sin(cameraAngle.x))
	#Y+=cameraZoom*cos(cameraAngle.y)
	
	$Camera.translation.x+=sqrt(cameraZoom)*sin(cameraAngle.y)*(sqrt(cameraZoom)*cos(cameraAngle.x))
	$Camera.translation.z+=sqrt(cameraZoom)*sin(cameraAngle.y)*(sqrt(cameraZoom)*sin(cameraAngle.x))
	$Camera.translation.y+=cameraZoom*cos(cameraAngle.y)
func handle_movement(delta):
	#clamping moveDir
	moveDir = Vector3(clamp(moveDir.x, -1, 1),clamp(moveDir.y, -1, 1),clamp(moveDir.z, -1, 1))
	#moveDir
	moveDir.x -= cos(cameraAngle.x)*Input.get_action_strength("move_forward")
	moveDir.z -= sin(cameraAngle.x)*Input.get_action_strength("move_forward")
	moveDir.x -= cos(cameraAngle.x+PI)*Input.get_action_strength("move_backward")
	moveDir.z -= sin(cameraAngle.x+PI)*Input.get_action_strength("move_backward")
	moveDir.x -= cos(cameraAngle.x+(270*PI/180))*Input.get_action_strength("move_left")
	moveDir.z -= sin(cameraAngle.x+(270*PI/180))*Input.get_action_strength("move_left")
	moveDir.x -= cos(cameraAngle.x+(90*PI/180))*Input.get_action_strength("move_right")
	moveDir.z -= sin(cameraAngle.x+(90*PI/180))*Input.get_action_strength("move_right")
	moveDir = moveDir.normalized()
	var accel = acceleration if is_on_floor() else air_acceleration
	moveDir = moveDir.linear_interpolate(moveDir * speed, accel * delta)
	if is_on_floor():
		y_velocity = -0.01
	else:
		y_velocity = clamp(y_velocity - gravity, -max_terminal_velocity, max_terminal_velocity)
	if Input.is_action_just_pressed("jump") and is_on_floor():
		y_velocity = jump_power
		
	moveDir.y = y_velocity
	moveDir = move_and_slide(moveDir, Vector3.UP)

I think the camera rotation from this script is broken now.

You have X Y & Z as variables. They should be references to the camera's X Y & Z position. For example, X should be $camera.translation.x.

@Dschoonmaker said: You have X Y & Z as variables. They should be references to the camera's X Y & Z position. For example, X should be $camera.translation.x.

Okay. Ive changed the code in func camera to:

 func update_camera():
    	cameraAngle.x = fmod(cameraAngle.x, 2*PI)
    	cameraAngle.y = clamp(cameraAngle.y, 0.01, PI-0.01)
    	$camera.translation.x=sqrt(cameraZoom)*sin(cameraAngle.y)*(sqrt(cameraZoom)*cos(cameraAngle.x))
    	$camera.translation.z=sqrt(cameraZoom)*sin(cameraAngle.y)*(sqrt(cameraZoom)*sin(cameraAngle.x))
    	$camera.translation.y+=cameraZoom*cos(cameraAngle.y)
    	
    	$Camera.translation.x+=sqrt(cameraZoom)*sin(cameraAngle.y)*(sqrt(cameraZoom)*cos(cameraAngle.x))
    	$Camera.translation.z+=sqrt(cameraZoom)*sin(cameraAngle.y)*(sqrt(cameraZoom)*sin(cameraAngle.x))
    	$Camera.translation.y+=cameraZoom*cos(cameraAngle.y)

or did you mean: X = $camera.translation.x ?

Oh, you did have it. I was looking at the code you commented out. Also, I forgot to add one crucial line before the camera position gets modified:

$Camera.translation = Vector3.ZERO

The camera position needs to be reset, because the camera movement code adds to its position. In my game, I have a variable called cameraOffset:

$Camera.translation = cameraOffset

it's a Vector3, it offsets the position of the camera. Usually it looks good to have the camera focus a little above the player's head, so in my case I have it set to:

cameraOffset = Vector3.UP*1.5

or

cameraOffset = Vector3(0,1.5,0)

But cameraOffset isn't necessary, if it looks good without it you probably don't need it.

@Dschoonmaker when I implement this line of code: $Camera.translation = Vector3.ZERO within my update_camera function, i get the error invalid set index 'translation' (on base: null instance) with value of type 'Vector3.'

entire code looks like this now:

extends KinematicBody
#old variables
export var speed : float = 20
export var acceleration : float = 15
export var air_acceleration : float = 5
export var gravity : float = 0.98
export var max_terminal_velocity : float = 54
export var jump_power : float = 20
export(float, 0.1, 1) var mouse_sensitivity : float = 0.3
export(float, -90, 0) var min_pitch : float = -90
export(float, 0, 90) var max_pitch : float = 90
#define moveDir as Vector3
var moveDir : Vector3
var y_velocity : float
onready var camera_pivot = $cameraRig
onready var camera = $cameraRig/SpringArm/Camera
#new variables
var cameraAngle = Vector2.ZERO
var cameraZoom = 2.25
var cameraOffset = Vector3(0,1.5,0)
#the error here V

#clamping camera varibales
#capturing mouse input
func _ready():
	Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
#making cursor visible on ui cancel
func _process(delta):
	if Input.is_action_just_pressed("ui_cancel"):
		Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
#rotate camera with mouse input? probaly invalid now with new clamping variables
func _input(event):
	if event is InputEventMouseMotion:
		rotation_degrees.y -= event.relative.x * mouse_sensitivity
		camera_pivot.rotation_degrees.x -= event.relative.y * mouse_sensitivity
		camera_pivot.rotation_degrees.x = clamp(camera_pivot.rotation_degrees.x, min_pitch, max_pitch)
func _physics_process(delta):
	handle_movement(delta)
	update_camera()
	
	
func update_camera():
	cameraAngle.x = fmod(cameraAngle.x, 2*PI)
	cameraAngle.y = clamp(cameraAngle.y, 0.01, PI-0.01)
	$Camera.translation = Vector3.ZERO
	#X+=sqrt(cameraZoom)*sin(cameraAngle.y)*(sqrt(cameraZoom)*cos(cameraAngle.x))
	#Z+=sqrt(cameraZoom)*sin(cameraAngle.y)*(sqrt(cameraZoom)*sin(cameraAngle.x))
	#Y+=cameraZoom*cos(cameraAngle.y)
	
	$Camera.translation.x+=sqrt(cameraZoom)*sin(cameraAngle.y)*(sqrt(cameraZoom)*cos(cameraAngle.x))
	$Camera.translation.z+=sqrt(cameraZoom)*sin(cameraAngle.y)*(sqrt(cameraZoom)*sin(cameraAngle.x))
	$Camera.translation.y+=cameraZoom*cos(cameraAngle.y)
	
func handle_movement(delta):
	#clamping moveDir
	moveDir = Vector3(clamp(moveDir.x, -1, 1),clamp(moveDir.y, -1, 1),clamp(moveDir.z, -1, 1))
	#moveDir
	moveDir.x -= cos(cameraAngle.x)*Input.get_action_strength("move_forward")
	moveDir.z -= sin(cameraAngle.x)*Input.get_action_strength("move_forward")
	moveDir.x -= cos(cameraAngle.x+PI)*Input.get_action_strength("move_backward")
	moveDir.z -= sin(cameraAngle.x+PI)*Input.get_action_strength("move_backward")
	moveDir.x -= cos(cameraAngle.x+(270*PI/180))*Input.get_action_strength("move_left")
	moveDir.z -= sin(cameraAngle.x+(270*PI/180))*Input.get_action_strength("move_left")
	moveDir.x -= cos(cameraAngle.x+(90*PI/180))*Input.get_action_strength("move_right")
	moveDir.z -= sin(cameraAngle.x+(90*PI/180))*Input.get_action_strength("move_right")
	moveDir = moveDir.normalized()
	var accel = acceleration if is_on_floor() else air_acceleration
	moveDir = moveDir.linear_interpolate(moveDir * speed, accel * delta)
	
	if is_on_floor():
		y_velocity = -0.01
	else:
		y_velocity = clamp(y_velocity - gravity, -max_terminal_velocity, max_terminal_velocity)
	if Input.is_action_just_pressed("jump") and is_on_floor():
		y_velocity = jump_power
		
	moveDir.y = y_velocity
	moveDir = move_and_slide(moveDir, Vector3.UP)




Camera should be a child node of the player(I'm just clarifying, it looks like you have it that way already). Also, is the name of your camera node "Camera"? With a capital C(it's case sensitive)?

@Dschoonmaker said: Camera should be a child node of the player(I'm just clarifying, it looks like you have it that way already). Also, is the name of your camera node "Camera"? With a capital C(it's case sensitive)?

yes. the name of the spatial, which is the first root node of this scene involving the player, is "player" and the camera is a child of it. And it is also spelled that way. with the capital C and all. Does the hierarchy of the nodes matter? I have the camera coming before the kinematic body from top to bottom

I would probably have the kinematic body above the camera, but depending on what you are going for maybe your way makes some sense? I'm not sure why you'd wan't to have the camera above though.

Wait, are all your nodes on the same level? You might want some actually parented to others, for an example camera to camera boom.

The order of nodes in the sceenetree does matter, especially when it comes to 2D since things are drawn in an order defined by it.

Additionally, remember that any nodes parented to another nodes will inherit their transform. This is great for things like a Camera boom, as Megalomaniak mentioned, but remember not to parent nodes you want to have separate transforms. Since you want the Camera and the player to have two separate transforms, you will need to make sure the Camera is not a child of the player, as otherwise it will inherit any changes to rotation/position/scale made to the player.

@TwistedTwigleg said: Additionally, remember that any nodes parented to another nodes will inherit their transform. This is great for things like a Camera boom, as Megalomaniak mentioned, but remember not to parent nodes you want to have separate transforms. Since you want the Camera and the player to have two separate transforms, you will need to make sure the Camera is not a child of the player, as otherwise it will inherit any changes to rotation/position/scale made to the player.

Okay. I have the camera, as a child of the root (spatial) and not the kinematic body which is also attached to the spatial. Is this viable or will the camera have to have its own scene attached to the world, separate from the kinematic body?

@empty_grimoire said:

@TwistedTwigleg said: Additionally, remember that any nodes parented to another nodes will inherit their transform. This is great for things like a Camera boom, as Megalomaniak mentioned, but remember not to parent nodes you want to have separate transforms. Since you want the Camera and the player to have two separate transforms, you will need to make sure the Camera is not a child of the player, as otherwise it will inherit any changes to rotation/position/scale made to the player.

Okay. I have the camera, as a child of the root (spatial) and not the kinematic body which is also attached to the spatial. Is this viable or will the camera have to have its own scene attached to the world, separate from the kinematic body?

Yup, that should be viable! As long as it is separate from the KinematicBody, it shouldn’t be affected by the KinematicBody’s rotation.