How to tell where my Character will land

Godot Version

v4.2.1

Question

Hello,

I am making a rollerblading game right now and when my character jumps off a cliff or a ramp, I want him to slowly rotate toward the normal of where I will land (Basically like the skate games). I know how to rotate my character to align with whatever the normal is below me but I am having trouble trying to figure out where I will land.

I would just shoot a raycast straight forward and rotate toward whatever normal I hit, but because of the nature of ramps and other things that might be in the way of that straight line it doesn’t work, so I need a way to find the trajectory of my flight path to the spot where I will land so that I can cast a raycast at that point and find the normal to rotate my character to match the landing spot slowly.

Below is my player movement script if that helps. Any help is greatly appreciated


class_name Player
extends CharacterBody3D

@onready var ray = $FloorRayCast
@onready var test = $Camera_Mount

# Jump
var JUMP_VELOCITY = 0 # The actual jump height of the player
var MAX_JUMP = 20 # Maximum height the player will jump
var MIN_JUMP = 14 # Minimum height the player will jump
var jump_timer = 15 # Speed at which the jump velocity rises when holding space

# Movement
var MOVESPEED = 0.0 # current speed for the player
var max_movespeed = 15.0 # max move speed
var speed_acceleration = 0.1 # Speed acceleration to slowly lerp the speed to max speed
var speed_deceleration = 0.001 # Speed deceleration to slowly lerp the speed to 0

# Rotation
var rot_speed = 0.0 # player left and right rotate speed based on A & D input

# Player States
var grind_state = false # Tells weather or not the player is grinding

# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")

var testBool = false
var startHeight
#var current_scene = get_tree().get_current_scene()
#var current_scene = get_root().get_node("TestWorld")
func _ready():
	pass
	
func _physics_process(delta):
	
	#Clamps the velocity so the player doesnt go spinning off into the ether
	velocity.z = clamp(velocity.z, -max_movespeed, max_movespeed)
	velocity.x = clamp(velocity.x, -max_movespeed, max_movespeed)
	
	# Moves forward if the raycast is colliding with the ground or if grinding(This is so the player can jump off the rail in the direction hes facing
	if is_on_floor() or grind_state == true:
		velocity = velocity.move_toward(-global_transform.basis.z * MOVESPEED, 2) # Move the player forward based on facing direction


	# The normal below the player
	var normal = $FloorRayCast.get_collision_normal() # Grabs the normal below

	#Finds the differnce between angles to stand the player upright depending on the normal below
	var newUp = Basis()
	newUp.x = normal.cross(global_transform.basis.z)
	newUp.y = normal
	newUp.z = global_transform.basis.x.cross(normal)
	global_transform.basis = newUp
	scale = Vector3(1,1,1) #Without this the player disappears

	# Add the gravity.
	#Gravity for when the player is not on the ground and not grinding
	if not is_on_floor() and grind_state == false:
		velocity.y -= gravity * 2 * delta
	# Gravity for when the player is on the ground
	# This is so the player sticks to ramps when going down them easier
	if normal != Vector3(0, 1, 0) and is_on_floor():
		velocity.y -= gravity * 8 * delta
		
	# Handles Jump.
	# Starts the jumping build up if the player is on the ground and clicks space or if grinding
	if Input.is_action_pressed("ui_accept") and is_on_floor() or grind_state == true: # and jump_state == true:	
		JUMP_VELOCITY += jump_timer * delta # ramps up the jump velicity while holding down space
		JUMP_VELOCITY = clamp(JUMP_VELOCITY, MIN_JUMP, MAX_JUMP) # Stops the player from jumping too high
	if Input.is_action_just_released("ui_accept") and is_on_floor(): # Actually does the jump when space is released
		_player_jump.call()
		JUMP_VELOCITY = MIN_JUMP # Resets the jump velocity
		
	#Left and right rotation input
	rot_speed = 0 #Resets to 0 every frame to stop the player from spinning on its own
	if Input.is_action_pressed("ui_left"):
		#player_turn_obj.rotate(Vector3(player_turn_obj.rotation.x, rot_speed, player_turn_obj.rotation.z).normalized(), 0.03)
		rot_speed += 0.05
	if Input.is_action_pressed("ui_right"):
		#player_turn_obj.rotate(Vector3(player_turn_obj.rotation.x, -rot_speed, player_turn_obj.rotation.z).normalized(), 0.03)
		rot_speed -= 0.05
	if Input.is_action_pressed("ui_left") or Input.is_action_pressed("ui_right"):
		# Had to put this in here to stop the player from rotating after input was let go
		rotate_object_local(Vector3.UP, rot_speed) # Rotates the player based on its up direction
	
	#Handles forward speed
	if Input.is_action_pressed("ui_up"):
		
		MOVESPEED = lerp(MOVESPEED, max_movespeed, speed_acceleration) #Lerps to max speed
		
		#anim_tree.set("parameters/BlendSpace1D/blend_position", velocity.length()) # Test skate animation
		#if grind_state == false:
		#anim_tree.set("parameters/BlendSpace2D/blend_position", Vector2(0, 1)) # Test skate animation
	
	else:
			MOVESPEED = lerp(MOVESPEED, 0.0, speed_deceleration) #Lerps back down to 0 when no input is made
	
	move_and_slide()
	
# Players jump function, called in the grinding script
func _player_jump():
	#velocity.y = JUMP_VELOCITY
	velocity += get_floor_normal() * JUMP_VELOCITY

func _player_grind():
	pass