KinematicBody 3D, max slope climb angle

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By DrewS

I want to prevent my character from climbing slopes of greater than 45 degrees, the player has a capsule shaped collision box. The code below shows how I am moving the character, however, the player is able to scale anything less than 90 degrees, only a wall will stop it. If I stop on the slope he falls down accordingly, but he should be prevented from climbing steep enough ones as well.

extends KinematicBody

var max_speed = 30
var gravity = -9.8/2
var jump_height = 50

var motion = Vector3()

func _physics_process(delta):
	run_and_jump(delta)

func run_and_jump(delta):
	var dir = Vector3(0, 0, 0)
	motion.y += gravity
	motion.x = 0
	motion.z = 0
	
	if Input.is_action_pressed("ui_left"):
		dir.x = -1
	elif Input.is_action_pressed("ui_right"):
		dir.x = 1
		
	if Input.is_action_pressed("ui_up"):
		dir.z = -1
	elif Input.is_action_pressed("ui_down"):
		dir.z = 1
		
	dir = dir.normalized() * max_speed
			
	if Input.is_action_just_pressed("action_jump") and is_on_floor():
		motion.y = jump_height
			
	dir += motion
	
	motion = move_and_slide(dir, Vector3(0, 1, 0))
:bust_in_silhouette: Reply From: Fabiofrosaa

I have the exact same problem.

I found this:

Which presents a weird solution using raycast. But that is for 2d and it doesn’t seem to have been ported to 3D as intended. Maybe that is on the way yet.

Maybe if we did this:

But it is also for 2d, I’m afraid in 3d we would get into situations where this can’t be done as we would get too many collisions to read from. Imagine a slight slope next to a steep one where we want to get up on one way but not another.

I really thought the “max slope angle” on the move_and_slide() would prevent this from happening, but that seems to only affect rather the player may slide down from a slope or not.

:bust_in_silhouette: Reply From: jemapelle

I came up with this solution where I compute the dot-product for a 45% slope vector against the y-axis (choosing x or z doesn’t matter). It works sort of ok:

MoveAndSlide(velocity);

// here we define what a steep slope is
var steepSlope = new Vector3(1,1,0)).Normalized();
var _FLOOR_MIN_DOT_PRODUCT = (steepSlope.Dot(Vector3.Up);

// note the testOnly parameter here
var collision = MoveAndCollide(velocity, testOnly: true);

var slopeDotProduct = collision.Normal.Dot(Vector3.Up);

if (slopeDotProduct < _FLOOR_MIN_DOT_PRODUCT)
{
    velocity = Vector3.Zero;
}

This is in C#, so it has to be translated to GDScript first. I usually just look at GDScript documentation and translate method names to CamelCase when I code in C#, so it shouldn’t be hard to reverse this.

Dot-product is -1 if opposite from a vector in 3D and 1 if the same direction. Make sure they are normalized to get a predictable value.

:bust_in_silhouette: Reply From: frapa

One thing that helped in my case, is not applying x-z movements when not floored. This would still need some fine-tuning to work properly, but it mostly solves the issue. For me it makes it also easier to jump while running around.

Here an example. Instead of:

velocity.y -= delta * GRAVITY
velocity.x = direction.x * MAX_SPEED
velocity.z = direction.z * MAX_SPEED

velocity = move_and_slide(velocity, Vector3.UP, true)

I do:

velocity.y -= delta * GRAVITY
if is_on_floor():
	velocity.x = direction.x * MAX_SPEED
	velocity.z = direction.z * MAX_SPEED

velocity = move_and_slide(velocity, Vector3.UP, true)

This avoids applying the x and z movements when you’re up a steep slope, thus stopping the “force” the object is doing against it, which causes the sliding. It also keeps the object better grounded instead of flying around, so that jumping is easier. Remaining problems:

  • The object will still slightly go up a slope, then bounce back.
  • There is some jitter when pushing against steep slopes.