I have a problem with orientation and direction.
This is the code for movement:

func _physics_process(delta: float) -> void:
	# Main movements
	var input_dir: = Input.get_vector("move_left", "move_right", "move_up", "move_down")
	var direction: = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()

In the first GIF image I have the result I want: I'm rotating the camera around my character as he moves.
(but with the problem of inverted commands when I move back.)

In the second GIF, I solve the problem of inverted commands
(but every time I move the mouse the character follows the rotation, i dont want this).

@onready var playerbase_mount: = $PlayerBaseMount as Node3D
var direction: = (playerbase_mount.transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()

Who can help me to solve the situation?
Thanks.

[UPDATE] Here the final solution:

extends CharacterBody3D


#Basis variables
@onready var visuals_basis: Basis = global_transform.basis
@onready var right_direction: = Vector3.RIGHT
@onready var front_direction: = Vector3.FORWARD

@onready var player_speed: = 3
@onready var player_is_free: = false


func _physics_process(delta: float) -> void:
   # Main movements
   var input_dir: = Input.get_vector("move_left", "move_right", "move_up", "move_down")
   if not is_on_floor(): velocity.y -= 12.0 * delta

   # Mesh movements
   var visuals_rotation: = player_speed * 3
   if input_dir.length_squared() > 0.9 and is_on_floor():
      visuals_basis.z = (-input_dir.x * right_direction + -input_dir.y * front_direction).normalized()
      visuals_basis.y = Vector3.UP.normalized()
      visuals_basis.x = visuals_basis.y.cross(visuals_basis.z)
      visuals_basis = visuals_basis.orthonormalized()
      global_transform.basis = global_transform.basis.slerp(visuals_basis, visuals_rotation * delta).orthonormalized()

   # Direction and velocity
   var _move_and_slide = move_and_slide()
   if player_is_free:
       if is_on_floor(): 
          velocity = (right_direction * input_dir.x + front_direction * input_dir.y) * player_speed
   else:
       player_is_free = false
       velocity.x = move_toward(velocity.x,0, player_speed)
       velocity.z = move_toward(velocity.z,0, player_speed)


# Input-follow camera
func _input(event: InputEvent) -> void:
   # reference to node3d on camera's scene
   var playerbase_mount: = get_parent().get_node("PlayerBaseMount") as Node3D
   if event is InputEventKey:
      if event.is_pressed() and not event.is_echo():
         var camera_fix_direction: = playerbase_mount.global_transform.basis.orthonormalized()
         right_direction = camera_fix_direction.x.normalized()
         front_direction = Vector3(camera_fix_direction.z.x, 0.0, camera_fix_direction.z.z).normalized()

(PlayerbaseMount) Camera in separate scene with this script:

extends Node3D


@onready var playerbase_spring: = $PlayerBaseSpring as SpringArm3D
@onready var camera_is_free: = false
@onready var mouse_sensitivity: = 0.5


func _ready() -> void:
	Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
 

func _process(_delta: float) -> void:
     # reference to node3d on player's tree structure placed on its neck
      var player: = get_parent().get_child(0).get_node("PlayerBaseLookAt")
      global_position = (player as Node3D).global_position


func _input(event: InputEvent) -> void:
   if event is InputEventMouseMotion && Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
      if camera_is_free:
         rotate_y(deg_to_rad( - (event as InputEventMouseMotion).relative.round().x * mouse_sensitivity))
         playerbase_spring.rotate_x(deg_to_rad( - (event as InputEventMouseMotion).relative.round().y * mouse_sensitivity))
         playerbase_spring.rotation.x = clampf(playerbase_spring.rotation.x, -PI/4, PI/4)
         playerbase_spring.rotation.z = clampf(playerbase_spring.rotation.y, -PI/4, PI/4)
  • fatalox Technically you could do everything with one scene but with your current understanding of math involved in this it will be much easier to separate the player and the camera scenes.

    Each 3D node in Godot has the transform property. A transform consists of the basis and the origin. The basis defines rotation (and scale) while origin defines position. Mathematically, a transform is expressed with 16 numbers arranged in a 4x4 matrix. First three rows of the matrix represent basis vectors, and the last row represents the origin.

    Changing node's transform will affect all of its descendant nodes, as transforms are always inherited from the parent, resulting in accumulation of transformations as we go down the hierarchy. That's the whole point of the scene tree. Since the basis is part of transform, then changing the basis of one node will affect all of its descendants.

    The basis consists of 9 numbers. Each triplet represents a vector that defines one of the coordinate axes.
    When node is in its default position, those basis axes correspond to world x, y and z axes. But once you rotate the node, the basis vectors can get any values, forming a rotated coordinate space.

    So when you move the player using left-right and forward-back controls, you basically move them along two perpendicular axes. If you do this relative to world space then those axes correspond to X and Z axes, or in other words the X and Z basis of the movement are vectors [1, 0, 0] and [0, 0, 1].

    However if you want to move the player in two perpendicular axes relative to camera direction, then those two axes may become some arbitrary (but still perpendicular) vectors, whose values depend on camera orientation.

    The catch is to figure out those two movement axis vectors from the camera orientation (basis), and then move the player along each one of them depending on inputs. That's what those two lines of code are doing. Move along the left-right vector depending on the x input and move along the forward-back vector depending on the y input.

    If your movement is done via kinematic body velocity, then the velocity vector should be set to point along one of those axes, or their sum, depending on the current state of input.

You need to modify the direction by the camera's forward direction, something along the lines of:

input_dir = camera_controller.global_transform.basis * input_dir
input_dir.y = 0.0

fatalox Can't be solve it without having to create a camera system separate from the movements from scratch?

It's best to have them separated. Camera should never be paranted to the player. You should first make a movement system you're happy with and then introduce the camera follow on top of that.

    xyz
    Thanks for the reply! I have this script for the camera.
    Do I need to make it even more independent to solve the problem related to changes of direction?
    It is true that I am looking for a convenient solution....because I like this code... the problem is only that change of direction on mouse movement.

    @onready var playerbase_spring: = $PlayerBaseSpring as SpringArm3D
    
    func _unhandled_input(event: InputEvent) -> void:
       if event is InputEventMouseMotion && Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
           if MainVariables.camera_is_free:
    	 self.rotate_y(deg_to_rad( - (event as InputEventMouseMotion).relative.x * MainVariables.mouse_sensitivity))
    	 playerbase_spring.rotate_x(deg_to_rad( - (event as InputEventMouseMotion).relative.y * MainVariables.mouse_sensitivity))
    	 playerbase_spring.rotation.x = clampf(playerbase_spring.rotation.x, -PI/4, PI/4)
             playerbase_spring.rotation.z = clampf(playerbase_spring.rotation.y, -PI/4, PI/4)
    • xyz replied to this.

      fatalox Your gifs do not really communicate the problem clearly. Maybe post a video from some existing game of the player/camera movement you want to achieve. It'll be easier to suggest how to actually do it the easiest way.

      fatalox Still not clear how exactly you want it to behave. How does the camera orientation affects player controls? You want controls to flip in certain positions? Is there a game you can refer to that uses similar camera/movement system?

      fatalox So you basically want camera orientation to determine movement directions. In that case, as I said, the camera shouldn't be player's child. Otherwise rotating the player will screw with the camera.

      So your playerbase_mount node needs to be player's sibling. Add a script to it that moves it to player's position every frame. Alternatively you can use RemoteTransform3D instead of script.

      Now you'll have camera rig follow only player's position but not its orientation. Add another Node3D as a child of playerbase_mount and rotate that node when orbiting camera. Let's call this node cam_orbit

      The second part is figuring front/back and left/right axes for the player, depending on the camera orientation. You can indeed get it from the basis of cam_orbit:

      Vector3 player_right = cam_orbit.global_transform.basis.x.normalized()
      Vector3 player_front = cam.orbit.global_transform.basis.z
      player_front.y = 0.0
      player_front = player_front.normalized()

      fatalox New node is not needed but some people like to separate camera positioning from camera orbiting. So each node handles one thing. It can be done with a single camera control node.

      Here's the smallest possible demo:

      cam-movement.zip
      3kB

        xyz I see the miss understanding, the demo has the main problem of this topic.
        If you hold down "ui_up" (or any direction key) and move the mouse left or right the cube moves in those directions.
        That's what I don't want.

        • xyz replied to this.

          xyz The cube should continue to move in the direction of the key, and the mouse should rotate only the camera.

          • xyz replied to this.

            fatalox Well then just update front and right vectors only when key goes down instead of every frame.

            fatalox player.gd:

            extends MeshInstance3D
            
            var right = Vector3.RIGHT
            var front = Vector3.FORWARD
            
            func _process(delta):
            	var input = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
            	
            	# move player
            	position += input.x * right * 10.0 * delta
            	position += input.y * front * 10.0 * delta
            
            func _input(event):
            	if event is InputEventKey:
            		if event.is_pressed() and not event.is_echo():
            			var b = $"../cam_placer".global_transform.basis
            			right = b.x.normalized()
            			front = Vector3(b.z.x, 0.0, b.z.z).normalized()

            fatalox First make the camera do what you want without smooth orientation. Comment out smooth orientation code and replace it with instant orientation. Once you got that working, introduce the smoothing back. Tackle one problem at a time.

            Btw don't just copy-paste the demo code. It probably won't work. Instead try to understand what it does and re-apply the principles it demonstrates to your actual code.

            fatalox Have you un-parent the camera from the player? That's the first thing you need to do.

              xyz Yes... do i need to create a new scene for the camera and link somehow the nodes with maybe groups? I tryed but with no result aswell.
              The mesh (PlayerBaseVisuals) do not follow the transform of the camera (PlayerBaseMount).

              • xyz replied to this.

                fatalox They're still both parented to PlayerBase. If your scripts are changing any transformations of this node then camera will be affected in undesired ways. Camera rig should be completely separated from anything player-related. If the hierarchy you posted is the player scene, then yes, the camera rig should be a different scene or its nodes just be placed directly onto the main scene.

                fatalox Technically you could do everything with one scene but with your current understanding of math involved in this it will be much easier to separate the player and the camera scenes.

                Each 3D node in Godot has the transform property. A transform consists of the basis and the origin. The basis defines rotation (and scale) while origin defines position. Mathematically, a transform is expressed with 16 numbers arranged in a 4x4 matrix. First three rows of the matrix represent basis vectors, and the last row represents the origin.

                Changing node's transform will affect all of its descendant nodes, as transforms are always inherited from the parent, resulting in accumulation of transformations as we go down the hierarchy. That's the whole point of the scene tree. Since the basis is part of transform, then changing the basis of one node will affect all of its descendants.

                The basis consists of 9 numbers. Each triplet represents a vector that defines one of the coordinate axes.
                When node is in its default position, those basis axes correspond to world x, y and z axes. But once you rotate the node, the basis vectors can get any values, forming a rotated coordinate space.

                So when you move the player using left-right and forward-back controls, you basically move them along two perpendicular axes. If you do this relative to world space then those axes correspond to X and Z axes, or in other words the X and Z basis of the movement are vectors [1, 0, 0] and [0, 0, 1].

                However if you want to move the player in two perpendicular axes relative to camera direction, then those two axes may become some arbitrary (but still perpendicular) vectors, whose values depend on camera orientation.

                The catch is to figure out those two movement axis vectors from the camera orientation (basis), and then move the player along each one of them depending on inputs. That's what those two lines of code are doing. Move along the left-right vector depending on the x input and move along the forward-back vector depending on the y input.

                If your movement is done via kinematic body velocity, then the velocity vector should be set to point along one of those axes, or their sum, depending on the current state of input.

                fatalox You'd want to brush up on your linear algebra skills. This is not particularly difficult but some understanding of vector math is required. Unfortunately there's no way around it if you want to code stuff like this with confidence.

                You don't apply velocity to basis. Velocity is a separate thing from basis but it is also a vector. The direction of this vector determines the direction of movement while its magnitude determines the speed of that movement. So if you want your character body to move along some basis vector then just assign the basis vector to the velocity vector and multiply it by speed:

                velocity = global_transform.basis.x.normalized() * speed
                move_and_slide()

                If you're serious about graphics/game programming, invest some time into a linear algebra course. The one at Khan Academy is not bad. Learning this stuff in invaluable.

                  xyz I don't know about graphic or programming as a career, but I'm seriously obsessed with understanding 3D movement in godot as exercise and mental challenge..
                  (I had already followed the initial lessons of the Khan Academy, I will gladly resume if it can help).
                  About the velocity I confuse the application, because if I write this code I have an error:
                  (Invalid set index 'x' (on base: 'Vector3') with value of type 'Vector3')

                  if direction:
                     velocity.z = direction.z * world_front * 10

                  Because world_front doesn't work without specifying the vector world_front.z (or viceversa with velocity)
                  But if i want to transmit the transform basis I think it loses the logic?

                  world_front = Vector3(camera_basis.z.x, 0.0, camera_basis.z.z).normalized()

                  I guess this step is more complex (with variables to store, maybe?) considering that the transformation is out of process (driven by func input).

                  • xyz replied to this.