camera based controls with a top down 90 degree camera

Godot Version

4.5.1 Stable

Question

Godot and Programming Novice here, I found that when using camera.transform.basis to use camera based movement, z velocity becomes reverse proportional to the camera’s x rotation, if the camera is top down (x rotation at 90.0 degrees), my z movement is 0, I become unable to move forwards and backwards at all, only left and right (x movement is just fine). Z movement gets faster the further away from 90 degrees the camera becomes. Is there a way to keep camera based movement and not have this side effect?

Example Video: https://youtu.be/1oINYdJkVjw

Context

The game I want to make will have a camera system like metal gear solid with dynamic angles and follow rates based on global position, which will include top down perspective pov

I use the phantom camera plugin for this, but I tested this in a fresh scene with only my player character scene, a static body 3d floor and a camera3D node and received the same results
(I originally thought this was a phantom camera problem)

My player character uses state machine level movement (no movement code in the character body 3d root node script), here is the full script
(note 1, handle_input and Physics_Update calls _input and _physics_process respectively)
(note 2, my custom class “State” contains the @onready for my character body 3D node, as well at the func in this script, which only calls the named functions of the active state)

extends State
class_name Running

@onready var cam1: Camera3D = $"../../../MainCamera3D"
@onready var anim: AnimationPlayer = %AnimationPlayer

func Enter():
	print("you are in running")
	anim.play("Snoke Movements Test 2/Run")
	
func handle_input(_event: InputEvent):
	if Input.is_action_just_pressed("testroll"):
		state_machine.change_state("Tackle")
	
func Physics_Update(_delta: float):
	var input_vector = Input.get_vector("testrunleft", "testrunright", "testrunforward", "testrundown")
	var direction := (cam1.transform.basis * Vector3(input_vector.x, 0, input_vector.y)).normalized()
	
	if direction == Vector3.ZERO:
		state_machine.change_state("idle")
		return


	player.velocity.x = direction.x * SPEED
	player.velocity.z = direction.z * SPEED
	
	player.move_and_slide()

Any help and advice is greatly appreciated.

I think it might be the pitch component of your Camera that’s causing the forward vector to point downward. You need to remove it like this for example:

var camera_basis := cam1.global_transform.basis

# Remove the camera's pitch so forward/right stay horizontal
camera_basis.y = Vector3.UP
# Get the flattened basis
camera_basis = camera_basis.orthonormalized()

# Replace the cam1.transform.basis in the direction with the flattened camera_basis
var direction := (camera_basis * Vector3(input_vector.x, 0, input_vector.y)).normalized()

Try this if it works. This basically just removes the tilt from the Y axis and creates a flattened, normalized orthogonal movement (not view), which prevents the pitch from affecting horizontal movement. This does not remove the camera-relative movement, just the vertical influence.

Edit: I had to try if this works, and it does, but when looking straight down, a gimbal lock happens sometimes and causes random rotations. Here is a cleaner way I found:

var input_vector = Input.get_vector("testrunleft", "testrunright", "testrunforward", "testrundown")

# Get the camera basis
var camera_basis := cam1.global_transform.basis

# Get the horizontal and vertical directions using the camera's X and Z
# The X for Left/Right and Z for Forward/Backward
# Since Godot has negative Z as forward, we use negative for Z
var movement_dir = (cam_basis.x * input_vector.x) - (cam_basis.z * input_vector.y)

# Then we "flatten" the movement by removing the Y influence
movement_dir.y = 0

var direction := movement_dir.normalized()

This ensures proper camera-relative movement while preventing potential Gimbal Lock. If the forward/backward is still reversed, just change the subtract in the movement_dir to add. I hope this helps!

1 Like

You solved the core problem of the movement being locked on the camera’s x axis! However a few other issues popped up in that my z movement was now mirrored under a 90 degree camera and in one camera up and right and down and left were going in the same left/right direction.

Perhaps it’s not optimal, but I found a compromise using your code as the new var direction and I added this

# Alternative direction specifically for -90 degree x rotated camera
	var directiontop := (player.transform.basis * Vector3(-input_vector.x, 0, -input_vector.y)).normalized()

# Conditional movement depending on top down view or not
	if cam1.rotation_degrees == Vector3(-90.0, 0.0, 0.0):
		player.velocity.x = directiontop.x * SPEED
		player.velocity.z = directiontop.z * SPEED
	else:
		player.velocity.x = direction.x * SPEED
		player.velocity.z = direction.z * SPEED

This achieves the result I need, so i suppose it’s all worked out!
Thank you!

I will need more help in the future!

1 Like

Yeah, input vectors can be tricky in Godot. It messed up my controller as well and I had to improvise to make the correct Z direction.

1 Like