Issue with slopes

Godot Version

4.5

Question

I am making a 3D Platformer inspired by sonic and slope physics break when going upside down please help, I dont know if this is a CharacterBody thing or not

extends CharacterBody3D

const SPEED = 10.0
const JUMP_VEVO = 8
enum states{Normal,Air,Spindash,Roll}
var vevo = Vector3.ZERO
var state = states.Normal
var savevevo = vevo
var time = 0
var direction = 0
var animation = 0
var slope = 0
var size = 0
var surface_normal = Vector3.ZERO
@export var camera = Node3D
# Build a stable orthonormal basis that uses `new_y` as the up vector.
func get_surface_tangent() -> Vector3:
	var up = surface_normal
	var forward = -global_transform.basis.z
	return (forward - up * forward.dot(up)).normalized()

func align_with_y(xform: Transform3D, new_y: Vector3) -> Transform3D:
	new_y = new_y.normalized()
	# choose a forward vector that's not parallel to new_y
	var forward = -xform.basis.z
	if abs(forward.dot(new_y)) > 0.999: # almost parallel -> pick another axis
		forward = xform.basis.x
	forward = (forward - new_y * forward.dot(new_y)).normalized() # project onto plane orthogonal to new_y
	var right = forward.cross(new_y).normalized()
	# set orthonormal basis: x = right, y = new_y, z = -forward (convention: z points forward)
	var b = Basis()
	b.x = right
	b.y = new_y
	b.z = -forward
	xform.basis = b.orthonormalized()
	return xform

func _process(delta: float) -> void:
	print(surface_normal)
	time += delta * 30
	$Sprite3D.frame_coords.x = fmod(time,40)
	$Sprite3D.frame_coords.y = direction + (animation * 5)
	print(round(rotation_degrees.x / 45))
	slope = round(rotation_degrees.x / 45)
	var p_fwd = -camera.global_transform.basis.z
	var fwd = -$Node3D.transform.basis.z
	var left = -$Node3D.transform.basis.x
	var l_dot = left.dot(p_fwd)
	var f_dot = fwd.dot(p_fwd)
	$Sprite3D.flip_v = (rotation_degrees.x > 90)
	if f_dot < -0.85:
		if (rotation_degrees.x < 90):
			direction=2
		else:
			direction=3
	elif f_dot > 0.85:
		if (rotation_degrees.x < 90):
			direction=3
		else:
			direction=2
	else:
		$Sprite3D.flip_h = l_dot < 0
		if abs(f_dot) < 0.3:
			direction=4
		elif f_dot < 0:
			if (rotation_degrees.x < 90):
				direction=1
			else:
				direction=0
		else:
			if (rotation_degrees.x < 90):
				direction=0
			else:
				direction=1

func _physics_process(delta : float) -> void:
	if(($Sprite3D2.position.y + 10)/10) > 0:
		size = ($Sprite3D2.position.y + 10) / 10
	else:
		size = 0
	$Sprite3D2.scale = Vector3(size,size,size)
	$Sprite3D2.global_position = $RayCast3D2.get_collision_point() + (Vector3(0,0.10,0)*Basis(-basis.x,basis.y,-basis.z))
	match state:
		states.Normal:
			Idle(delta)
		states.Air:
			Jump(delta)

func converting():
	print(get_floor_angle())
	if(abs(get_floor_angle()*57.2957779) > 90):
		velocity = Vector3((global_transform.basis * vevo).x,(global_transform.basis * vevo).z,(global_transform.basis * vevo).y)
	else:
		velocity = global_transform.basis * vevo

func Jump(delta: float):
	$RayCast3D.target_position = Vector3(0,-0.4,0)
	if vevo.y < 20.5 && (!$RayCast3D.is_colliding()) && (!$Node3D/RayCast3D.is_colliding() && !$Node3D/RayCast3D2.is_colliding()):
		global_basis.y = Vector3(0,1,0)
		global_basis.x = Vector3(1,0,0)
		global_basis.z = Vector3(0,0,1)
	# Gravity (only when not on floor)
	if not $RayCast3D.is_colliding():
		if(round(global_basis.y) != Vector3(0,1,0)):
			vevo = savevevo
			global_transform = lerp(global_transform,align_with_y(global_transform, Vector3(0,1,0)),delta * 20)
		vevo += ProjectSettings.get_setting("physics/3d/default_gravity_vector") * ProjectSettings.get_setting("physics/3d/default_gravity") * delta
	else:
		state = states.Normal
		
	if($RayCast3D.is_colliding()):
		state = states.Normal
	horizontal(delta)
	converting()
	move_and_slide()

func horizontal(delta: float):
	var input_dir := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
	var direction_local := Vector3(input_dir.x, 0.0, input_dir.y)
	if direction_local.length() > 0.001:
		$Node3D.rotation.y = -input_dir.angle() + -1.57079637050629
		direction_local = direction_local.normalized()
		vevo.x = move_toward(vevo.x,direction_local.x * SPEED, SPEED * delta * 5.0)
		vevo.z = move_toward(vevo.z,direction_local.z * SPEED, SPEED * delta * 5.0)
	else:
		# smoothly decelerate (tweak the third argument if you want faster/slower stop)
		vevo.x = move_toward(vevo.x, 0.0, SPEED * delta * 2.0)
		vevo.z = move_toward(vevo.z, 0.0, SPEED * delta * 2.0)

func Idle(delta: float):
#Slope Collision
	# Get valid collision normals from raycasts, only if they hit.
	var n_sum = Vector3.ZERO
	var count = 0
	if $Node3D/RayCast3D.is_colliding():
		n_sum += $Node3D/RayCast3D.get_collision_normal()
		count += 1
	if $Node3D/RayCast3D2.is_colliding():
		n_sum += $Node3D/RayCast3D2.get_collision_normal()
		count += 1
	
#Ground Collision
	var n = Vector3.UP
	$RayCast3D.target_position = Vector3(0,-0.4,0)
	
	if count > 0:
		n = (n_sum / float(count)).normalized()
	surface_normal = n
	# Align to slope when on floor
	if $RayCast3D.is_colliding():
		vevo.y = 0
		global_transform = lerp(global_transform,align_with_y(global_transform, n),delta * 20)
	else:
		state = states.Air
	# Jump
	if Input.is_action_pressed("ui_accept") and $RayCast3D.is_colliding():
		vevo.y = JUMP_VEVO

	# Input-based movement in *local* X/Z (X = right, Z = forward)

	# If falling, snap to floor (optional)
	if vevo.y < 0 && $RayCast3D.is_colliding():
		apply_floor_snap()

	# **Important:** convert local `vevo` into global velocity correctly
	# Use basis.xform to transform a local vector into global space
	
	if(abs(get_floor_angle()*57.2957779) > 90):
		savevevo = Vector3((global_transform.basis * vevo).x,(global_transform.basis * vevo).z,(global_transform.basis * vevo).y)
	else:
		savevevo = global_transform.basis * vevo
	horizontal(delta)
	converting()
	print(global_transform.basis * vevo)
	# Move; pass the slope normal as the up direction so CharacterBody3D handles slopes correctly
	move_and_slide()

nevermind I fixed it

Would you care to explain how..? This may help others with the same, or similar issues. Just a thought. :dotted_line_face:

1 Like

There was an error with converting and I messed with some values here is my code for more decoding

extends CharacterBody3D

const SPEED = 15
const JUMP_VEVO = 8
enum states{Normal,Air,Spindash,Roll}
var vevo = Vector3.ZERO
var state = states.Normal
var savevevo = vevo
var time = 0
var direction = 0
var animation = 0
var slope = 0
var size = 0
@export var gravity_scale = 1.0
var surface_normal = Vector3.ZERO
@export var camera = Node3D
# Build a stable orthonormal basis that uses `new_y` as the up vector.
func get_surface_tangent() -> Vector3:
	var up = surface_normal
	var forward = -global_transform.basis.z
	return (forward - up * forward.dot(up)).normalized()

func align_with_y(xform: Transform3D, new_y: Vector3) -> Transform3D:
	new_y = new_y.normalized()
	# choose a forward vector that's not parallel to new_y
	var forward = -xform.basis.z
	if abs(forward.dot(new_y)) > 0.999: # almost parallel -> pick another axis
		forward = xform.basis.x
	forward = (forward - new_y * forward.dot(new_y)).normalized() # project onto plane orthogonal to new_y
	var right = forward.cross(new_y).normalized()
	# set orthonormal basis: x = right, y = new_y, z = -forward (convention: z points forward)
	var b = Basis()
	b.x = right
	b.y = new_y
	b.z = -forward
	xform.basis = b.orthonormalized()
	return xform

func _process(delta: float) -> void:
	print(surface_normal)
	time += delta * 30
	$Sprite3D.frame_coords.x = fmod(time,40)
	$Sprite3D.frame_coords.y = direction + (animation * 5)
	print(round(angle() / 45))
	slope = round(angle() / 45)
	var p_fwd = -camera.global_transform.basis.z
	var fwd = -$Node3D.transform.basis.z
	var left = -$Node3D.transform.basis.x
	var l_dot = left.dot(p_fwd)
	var f_dot = fwd.dot(p_fwd)
	$Sprite3D.flip_v = (angle() > 180)
	if f_dot < -0.85:
		if (angle() < 181):
			direction=2
		else:
			direction=3
	elif f_dot > 0.85:
		if (angle() < 181):
			direction=3
		else:
			direction=2
	else:
		$Sprite3D.flip_h = l_dot < 0
		if abs(f_dot) < 0.3:
			direction=4
		elif f_dot < 0:
			if (angle() < 181):
				direction=1
			else:
				direction=0
		else:
			if (angle() < 181):
				direction=0
			else:
				direction=1

func _physics_process(delta : float) -> void:
	if(($Sprite3D2.position.y + 10)/10) > 0:
		size = ($Sprite3D2.position.y + 10) / 10
	else:
		size = 0
	$Sprite3D2.scale = Vector3(size,size,size)
	$Sprite3D2.global_position = $RayCast3D2.get_collision_point() + (Vector3(0,0.10,0)*Basis(-basis.x,basis.y,-basis.z))
	match state:
		states.Normal:
			Idle(delta)
		states.Air:
			Jump(delta)

func converting():
	print(abs(angle()))
	velocity = global_transform.basis * vevo

func Jump(delta: float):
	surface_normal = Vector3(0,1,0)
	if vevo.y < 20.5 && (!$RayCast3D.is_colliding()) && (!$Node3D/RayCast3D.is_colliding() && !$Node3D/RayCast3D2.is_colliding()):
		up_direction = lerp(up_direction,Vector3.UP,delta * 5)
		rotation.y = lerp(rotation.y,0.0,delta * 5)
		global_transform = lerp(global_transform,align_with_y(global_transform,Vector3(0,1,0)),delta * 5)
	# Gravity (only when not on floor)
	if not $RayCast3D.is_colliding():
		if(round(global_basis.y) != Vector3(0,1,0)):
			vevo = savevevo
			global_transform = lerp(global_transform,align_with_y(global_transform, Vector3(0,1,0)),delta * 20)
		vevo += Vector3(0,-9.8 * gravity_scale,0) * delta
	else:
		state = states.Normal
		
	if($RayCast3D.is_colliding()):
		state = states.Normal
	horizontal(delta)
	converting()
	move_and_slide()

func angle():
	var angle_rad := acos(clamp(surface_normal.dot(Vector3.UP), -1.0, 1.0))
	return rad_to_deg(angle_rad)

func horizontal(delta: float):
	var input_dir := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
	var direction_local := Vector3(input_dir.x, 0.0, input_dir.y)
	if direction_local.length() > 0.001:
		$Node3D.rotation.y = -input_dir.angle() + -1.57079637050629
		direction_local = direction_local.normalized()
		vevo.x = move_toward(vevo.x,direction_local.x * SPEED, SPEED * delta * 2.0)
		vevo.z = move_toward(vevo.z,direction_local.z * SPEED, SPEED * delta * 2.0)
	else:
		# smoothly decelerate (tweak the third argument if you want faster/slower stop)
		vevo.x = move_toward(vevo.x, 0.0, SPEED * delta * 1.5)
		vevo.z = move_toward(vevo.z, 0.0, SPEED * delta * 1.5)

func Idle(delta: float):
	#Slope Collision
	if(abs(angle()) > 45) && (Vector3(vevo.x,0,vevo.z).length() < 2):
		vevo.y = 10
	vevo += Vector3((surface_normal.cross(Vector3.UP) * -0.65).z,0,(surface_normal.cross(Vector3.UP) * -0.65).x)
	# Get valid collision normals from raycasts, only if they hit.
	var n_sum = Vector3.ZERO
	var count = 0
	if $Node3D/RayCast3D.is_colliding():
		n_sum += $Node3D/RayCast3D.get_collision_normal()
		count += 1
	if $Node3D/RayCast3D2.is_colliding():
		n_sum += $Node3D/RayCast3D2.get_collision_normal()
		count += 1
	
#Ground Collision
	var n = Vector3.UP
	$RayCast3D.target_position = Vector3(0,-0.4,0)
	
	if count > 0:
		n = (n_sum / float(count)).normalized()
	surface_normal = n
	# Align to slope when on floor
	if $RayCast3D.is_colliding():
		vevo.y = 0
		up_direction = n
		global_transform = lerp(global_transform,align_with_y(global_transform, n),delta * 20)
	else:
		up_direction = Vector3.UP
		state = states.Air
	# Jump
	if Input.is_action_pressed("ui_accept") and $RayCast3D.is_colliding():
		vevo.y = JUMP_VEVO

	# Input-based movement in *local* X/Z (X = right, Z = forward)

	# If falling, snap to floor (optional)
	if vevo.y < 0 && $RayCast3D.is_colliding():
		apply_floor_snap()

	# **Important:** convert local `vevo` into global velocity correctly
	# Use basis.xform to transform a local vector into global space
	
	if(abs(angle()) > 90):
		savevevo = Vector3((global_transform.basis * vevo).x,(global_transform.basis * vevo).z,(global_transform.basis * vevo).y)
	else:
		savevevo = global_transform.basis * vevo
	horizontal(delta)
	converting()
	print(global_transform.basis * vevo)
	# Move; pass the slope normal as the up direction so CharacterBody3D handles slopes correctly
	move_and_slide()

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.