3D rigidbody movement around a sphere in Godot4

extends CharacterBody3D

const SPEED = 15.0
const JUMP_VELOCITY = 10
const SENSITIVITY = 0.004
const GRAVITY = 9.8

var gravity_direction = Vector3.DOWN

@export var planet: StaticBody3D

@onready var mesh = $PlayerMesh
@onready var head = $PlayerHead
@onready var camera = $PlayerHead/Camera3D

# Hides the cursor
func _ready():
	Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)

func _process(_delta):
	# Calculate the gravity direction from the player
	gravity_direction = (planet.global_position - global_position).normalized()

	# Update the relative up direction
	up_direction = -gravity_direction
	set_up_direction(up_direction)
	
# Handles mouse movement
func _unhandled_input(event):
	if event is InputEventMouseMotion:
		head.rotate_y(-event.relative.x * SENSITIVITY)
		camera.rotate_x(-event.relative.y * SENSITIVITY)
		
		camera.rotation.x = clamp(camera.rotation.x, deg_to_rad(-40), deg_to_rad(60))

func _physics_process(delta):
	# If we are in the air, apply gravity over time
	if not is_on_floor():
		velocity += gravity_direction * GRAVITY * delta

	# Apply a jump force in the opposite direction of gravity
	if Input.is_action_just_pressed("jump") and is_on_floor():
		velocity += -gravity_direction * JUMP_VELOCITY

	var direction = _get_oriented_input()

	if is_on_floor():
		velocity = direction * SPEED

	rotate_player(delta)	
	move_and_slide()

func rotate_player(delta) -> void:
	var target_transform = Transform3D()
	target_transform.origin = global_position
		
	# Find the left hand side of the player
	var left_axis = -gravity_direction.cross(transform.basis.z).normalized()
	# Keep the z axis the same
	var my_z = global_transform.basis.z
	# Set target rotation
	target_transform.basis = Basis(left_axis, -gravity_direction, my_z).orthonormalized()
		
	var current_rotation = global_transform.basis.get_rotation_quaternion()
	var target_rotation = target_transform.basis.get_rotation_quaternion()
	
	# Lerp between the two quaternions
	var interpolated_rotation = current_rotation.slerp(target_rotation, delta * 15.0) 
	
	global_transform.basis = Basis(interpolated_rotation)

func _get_oriented_input() -> Vector3:
	var input_dir = Input.get_vector("left", "right", "backward", "forward")

	# Get the camera's global transform basis
	var camera_basis = camera.global_transform.basis
	

	# Orient the input direction using the camera's basis
	var direction = camera_basis.x * input_dir.x + camera_basis.z * (-input_dir.y)

	return direction.normalized() # Important to normalize the direction

This is my new full script. I believe that rotate_player works correctly now, but I am strugglign to ensure that the movement is oriented corectly with get orietned input

2 Likes