I've got a fixed projection perspective camera with a vertical FOV of 70 deg and rotated 45deg on Y axis.

Player movement is along Camera's X axis (right/left), Z axis (front/back), Y axis is set to 0.

What I'd like to achieve is for the player to move directly towards the screen top/bottom when pressing forward/back keys instead of moving along camera's Z axis which causes and illusion of traveling at an angle the further from the center of the screen you are (which makes sense but is not desired in this case).

The screenshot below depicts:

Green: Direction to camera
Blue: Camera's Z axis (player movement forward/back)
Red: Desired forward/back movement vector

I was able to brute-force some values like if camera's Z axis are at 45deg to the player-camera direction then the desired rotation would be half of that (22.5deg) but I can't seem to find a solution for the rest of the world positions due to my lack of Math knowledge.

So you have the player's position and the camera's position. You can subtract those two, and you have a vector (a direction) from the player to the camera. Since you want to have the player walk on the floor (I am assuming) you will have to project that vector onto the ground. Since you ground is a flat plane, this is relatively easy (you can just zero out the y value). Even if the floor is sloped, as long as it is generally on the horizon line, you can assume it travels on the X-Z plane, we can normalize the direction vector so not to distort it. Then you simply take this vector, normalize it (so it will be length 1.0) and then multiply by the speed of movement. This will get you forward and back movement.

For left and right, it is fairly similar. But in this case you will take the right vector of the camera (rather than the vector between the player and the camera). As long as the camera is mostly facing level, this will work (it wouldn't work in a 6DOF space game like Descent). Then you do the same calculation, project the right vector onto the floor, normalize it, and then multiply but the player speed. This is right, if you press the left button, then just multiply by -1.0.

    cybereality Thanks for your answer. I have movement setup already as per the camera projection, what i'd like is different from that. What you suggested would be identical to the green vector in the screenshot which would make the player move towards the camera (bottom center) rather than perpendicular to the bottom of the screen. Left/right movement is working fine as it is.

    I'm not sure I understand the issue. Can you explain it a little differently?

      cybereality Sure! The current movement is OK as far as camera projection works, the player travels on camera's Z and X axis. However I'd like to change that for the Z axis. The reason for that is with a static camera the Z axis has very noticeable distortion the further the player is from the center of the camera. Here are a few examples:

      1. Player is at (0, 0, 0) and Camera pointing to (0, 0, 0)

      2. Player moves in direction of (0, 0, -1) or down to the bottom of the screen

      3. If if you draw that vector it's a straight line from player position to bottom of the screen

      4. Player is at (0, 0, 0) and Camera pointing to (0, 0, 0)

      5. Player moves in direction of (1, 0, 0) or to the right of the screen

      6. Player moves in direction o f (0, 0, -1) or down to the bottom of the screen

      7. If you draw that vector it's no longer a straight line from player position to bottom of the screen.

      The further the player is from the origin the greater the angle of the Z axis becomes when projected to the screen. And again this is expected with the 3d camera's perspective, but I'd like to change that so that no matter how far way from the center the player is, his down movement would always result in a straight line towards the bottom of the screen, thus no longer being just on Z axis but both Z and X. This desired behavior is depicted in the screenshot as a straight red line. One of the things I have tried is using Camera.unproject_position with players transform.origin and alter it's y coords, but couldn't quite project that Vector2 back to the world space properly. I'm sure there has to be some way to do this with camera/player transforms, I just lack the math experience to figure it out myself.

      Okay, I have to go out for a bit, but I will check when I get back.

      I'm not sure what you mean by "that vector it's no longer a straight line", a vector is always a straight line, by definition. And your text example and screenshot don't really make sense to me. I believe my original answer is the correct one. Since you normalize the vector, the distance doesn't matter, you are only dealing with a direction (sort of like an arrow pointing at where you want to go). There are a few ways to get the target position. You can project the vector onto XZ, as I said. You can take the camera position and replace the y value with the player's y value. Or you can do a ray cast straight down from the camera and use the first object that is hit. If your floor is flat, then all 3 positions will be the same value.

        cybereality I'm sorry I can't make it any clearer. I know what vectors are and as I said, movement is working fine. By straight line I'm referring to the screen space as opposed to world space. When the player vector in Z axis is viewed on a screen it's not at 90deg to the screen's X axis, that's what I want. Wherever the player may be in world space (3d) I want it to have such a vector so that it would move only on the screen's Y axis.
        Your answer is correct as far as setting up basic vectors, but it does not at all account for the horizontal perspective distortion I'm trying to fix.

        Maybe if you explain what your game is, and how it works, I would understand. It still doesn't make any sense.

          You should be able to use the basis of the camera for that. This is the camera's orientation. By default in local space (for example, if the camera is a child of another node) but you can use the global_transform to get the basis and then that will be in global space. So the camera basis y axis will be aligned with the screen. The only complex part is that the pixel movement will depend on the distance from the camera. The easiest thing is to normalize the vector and then multiply by the player speed. This will result in uniform movement in global world space. However, in pixel screen space if will differ by the distance to the camera. I believe if you divide the movement vector by the inverse distance from the camera, this will result in steady movement. However, it will be dependent on the resolution of the window or monitor resolution, so you will also have to multiply by the pixel size so that it is the same no matter the resolution.

            cybereality Thanks, indeed it does sort of work, it does have one issue though, when going up the Y axis in screen coordinates it also moves along the Y axis in world coordinates which removes any sense of depth and makes the player no longer be on the "floor". It does work ok when going the opposite way.

            EDIT: A note for anyone who stumbles upon this. I had to bump up my speed from 14 up to a whoping 1000 and invert the X axis input to offset the division by inverse of distance to camera

            Yes, it will move up on the screen. I thought that was what you were asking. If you want to constrain to the floor you have a couple options. The most robust would be to cast a ray straight down (in the player's space) and lock to the collision hit position. This will be the most accurate, and account for uneven terrain. If the floor is always a flat plane, then you can just manually set the player's y value to the floor y value, after you do the screen calculation. Or you can add gravity manually and use the physics engine.

              cybereality Thanks, altering the y value always negates the effect, I'll look into ray casting, but ultimately I was looking for some sort of projection matrix manipulation to offset the distortion.
              Here's my initial code:

              var move_dir := Vector3.ZERO
              move_dir.x = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
              move_dir.z = Input.get_action_strength("ui_down") - Input.get_action_strength("ui_up")
              move_dir = move_dir.normalized().rotated(Vector3.UP, Camera.rotation.y)
              
              velocity.x = move_dir.x * speed
              velocity.z = move_dir.z * speed
              velocity.y = 0
              velocity = move_and_slide(velocity, Vector3.UP)
              
              transform.basis = transform.basis.slerp(transform.looking_at(transform.origin - move_dir, Vector3.UP).basis, 0.1)

              Oh, I see what you are saying. In that case, you'll have to cast a ray from the new player position (in screen space) along the camera normal. This will hit the point on the floor that is directly in the middle of what is obscured by the player graphic. Then you can move the player to that hit collision point. However, then the scale will be different, so you have to handle the scale separately (but it will be at the correct position). Here is the code to cast from the center of the camera forward (where the camera is looking). You will just have to modify so that it takes the player's screen space position instead of the mouse position on the screen.

              https://docs.godotengine.org/en/stable/tutorials/physics/ray-casting.html