So, I'm trying to implement a 6 DOF character controller a la Space Engineers. My problem is 2-fold.

-When rotating the player around the Z(forward) axis any linear movement stays locked to the global XYZ. I'm at a loss on how to fix this issue.

-When switching between XY rotation(mouse) and Z rotation(mapped Input Events), player rotation resets, Although it seems to maintain the previous rotation when switching back. My assumption is that it is obviously due to player.transform.basis = Basis() however I can't get the rotations to work "correctly" without it.

I have read through the Transform3D Docs etc. but I've obviously missed something or may be approaching this from completely the wrong angle(pun intended).

The only thing in my player script at the moment is variable declarations and move_and_slide()

extends Node
class_name InputManager

@export var player: Player
var mouse_sensitivity = 0.002
var rot_x = 0
var rot_y = 0
var rot_z = 0
var rot_speed = 0.01

# Called when the node enters the scene tree for the first time.
func _ready():
	pass # Replace with function body.

func _process(delta):
	var input_dir = Input.get_vector("Strafe Left", "Strafe Right", "Move Forward", "Move Backward")
	var direction: Vector3
	
	direction = (player.transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
# If player is not on ground
	if Input.is_action_pressed("Jump"):
		direction.y = 1
	elif Input.is_action_pressed("Crouch"):
		direction.y = -1
	else: direction.y = 0
	
	if direction:
		player.velocity += direction * player.THRUSTER_SPEED
	else:
		player.velocity.x = move_toward(player.velocity.x, 0, player.THRUSTER_SPEED)
		player.velocity.y = move_toward(player.velocity.y, 0, player.THRUSTER_SPEED)
		player.velocity.z = move_toward(player.velocity.z, 0, player.THRUSTER_SPEED)
	
	# Rotate player around z axis
	if Input.is_action_pressed("Rotate Clockwise"):
		rot_z -= rot_speed
		player.transform.basis = Basis() # reset rotation
		player.rotate_z(rot_z)
	elif Input.is_action_pressed("Rotate Counterclockwise"):
		rot_z += rot_speed
		player.transform.basis = Basis() # reset rotation
		player.rotate_object_local(Vector3(0, 0, 1), rot_z)
	
func _input(event):
	if event is InputEventMouseMotion:
		# Mouse Look
		rot_x += -event.relative.y * mouse_sensitivity # set player x axis rotation amount from mouse y
		rot_y += -event.relative.x * mouse_sensitivity # set player y axis rotation amount from mouse x
		player.transform.basis = Basis() # reset rotation
		player.rotate_y(rot_y) # first rotate in Y
		player.rotate_x(rot_x) # then rotate in X
		player.rotation.x = clampf(player.rotation.x, -deg_to_rad(70), deg_to_rad(70))
  • xyz replied to this.
  • seemsvanjest Here's a simple example of full 6 dof control:

    extends Node3D
    
    const MOVE_SPEED = 20.0
    const ROTATE_SPEED = PI
    const MOUSE_SENSITIVITY = .04
    
    func _input(e):
    	if e is InputEventMouseMotion:
    		rotate_object_local(Vector3.UP, -e.relative.x * MOUSE_SENSITIVITY) 	# yaw
    		rotate_object_local(Vector3.RIGHT, -e.relative.y * MOUSE_SENSITIVITY) 	# pitch
    
    func _process(dt):
    	rotate_object_local(Vector3.FORWARD, Input.get_axis("roll_ccw", "roll_cw") * ROTATE_SPEED * dt) # roll
    	
    	var vel = Vector3(
    		Input.get_axis("left", "right"), Input.get_axis("up", "down"), Input.get_axis("forward", "back")
    	) * MOVE_SPEED
    	global_position += global_basis * vel * dt

    seemsvanjest Why are you clamping x rotation in a 6 dof controller?
    You complain that the rotation gets reset while your code explicitly resets it in several places by assigning identity basis to player's local basis.
    Also, what's the meaning of "jump" when you have 6 degrees of freedom?

      xyz The clamping is left over from earlier work and was there to keep excessive rotations in check while testing.

      As stated in the post, I realise that I am resetting the basis. Without doing so, the rotations are completely incorrect(wrong axis, wrong speed, seem to be inconsistent). I'm obviously missing something, hence the question.

      • xyz replied to this.

        kuligs2 I don't have a way to capture video at the moment. I'll sort that out tomorrow as it's getting a little late in my neck of the woods.

          seemsvanjest For yaw, pitch and roll you should rotate around player's current basis axes not around parent's local axes, unless you want some axes to be "absolute"
          Or if you keep yaw, pitch and roll in custom properties then when constructing the final basis you need to apply all rotations, always in the same order.
          For more specific advice, best to show an example of the kind of movement control you want to implement.

            xyz I figured it was something like that, but haven't been able to figure out how to make it happen. I realise I am probably missing something obvious but it just escapes me.

            xyz The intended movement control is exactly the same as Space Engineers' jetpack control. Jetpack in space.
            The linear motion works exactly as intended, it's just the rotation that's screwy.

            For yaw, pitch and roll you should rotate around player's current basis axes

            This is the bit that's confusing me. My understanding was that rotate_x/y/z did this. Obviously I was wrong...

            • xyz replied to this.

              seemsvanjest Well best to find a video example. You can't expect everyone here played Space Engineers 🙂 The important thing to determine is does it uses full 6 dof or a variant with some limitations.

                seemsvanjest My understanding was that rotate_x/y/z did this.

                rotate_*() rotates around axes of the parent's coordinate system.
                rotate_object_local() rotates around axes specified in object's local coordinate system.

                kuligs2 Current behaviour.
                [
                After any rotation, linear movement remains along global axes.

                • xyz replied to this.

                  xyz

                  You can't expect everyone here played Space Engineers

                  Fair enough.

                  rotate_object_local() rotates around axes specified in object's local coordinate system.

                  I was originally using rotate_object_local() with the same results. I switched to rotate_* while trying to fix it.

                  • xyz replied to this.

                    seemsvanjest After any rotation, linear movement remains along global axes.

                    Same problem as with rotations. You need to move along player's basis axes, not along parent's x, y, z axes, which your code currently does.

                    seemsvanjest Here's a simple example of full 6 dof control:

                    extends Node3D
                    
                    const MOVE_SPEED = 20.0
                    const ROTATE_SPEED = PI
                    const MOUSE_SENSITIVITY = .04
                    
                    func _input(e):
                    	if e is InputEventMouseMotion:
                    		rotate_object_local(Vector3.UP, -e.relative.x * MOUSE_SENSITIVITY) 	# yaw
                    		rotate_object_local(Vector3.RIGHT, -e.relative.y * MOUSE_SENSITIVITY) 	# pitch
                    
                    func _process(dt):
                    	rotate_object_local(Vector3.FORWARD, Input.get_axis("roll_ccw", "roll_cw") * ROTATE_SPEED * dt) # roll
                    	
                    	var vel = Vector3(
                    		Input.get_axis("left", "right"), Input.get_axis("up", "down"), Input.get_axis("forward", "back")
                    	) * MOVE_SPEED
                    	global_position += global_basis * vel * dt

                      xyz All working now. It makes a lot more sense after the example. global_basis was the missing piece of the puzzle for me. Thanks again for your patience and taking the time to post the example.