Player bounces off slopes, keeps horizontal velocity when walking into walls and can't step up sloped stairs
Closed this issue · 1 comments
RisingThumb commented
Hi, really like what's been made here. Would like to point out 3 issues with this player controller. Feel free to put this up as 3 issues on Github if you want.
A video demonstrating these 3 issues.
- If the stairs are sloped differently, it doesn't step at all(haven't looked into how it behaves if the stairs are just sloped but the same angle).
- If you walk up a slope that's too steep, you just bounce up and down it.
- If you move in a particular direction then jump, the stair stepping triggers, but you keep all your horizontal velocity from walking into the wall, meaning you get adjusted up by the stair stepping and move off the platform uncontrollably due to the horizontal velocity.
mrezai commented
Hi
This implementation should be considered as a proof of concept to start learning about its subject and isn't complete or bug free in any means.
- You can modify STEP_MAX_SLOPE_DEGREE in Player's code to solve this problem but it may cause other problems!
- This is default behaviour of move_and_slide, I have some ideas to fix it that I'll implement and test them later.
- I think this issue is related more to how someone want character movement feels for example in fast shooter game, current implementation would be desirable but you can clamp horizontal velocity by keeping some state in the code like this(is_jumping added to code, also STEP_MAX_SLOPE_DEGREE is set to 30 for issue one) :
extends KinematicBody
const ACCEL_DEFAULT: float = 7.0
const ACCEL_AIR: float = 1.0
var accel: float = ACCEL_DEFAULT
const SPEED_DEFAULT: float = 7.0
const SPEED_ON_STAIRS: float = 5.0
var speed: float = SPEED_DEFAULT
var gravity: float = 9.8
var jump: float = 5.0
const stairs_feeling_coefficient: float = 2.5
var mouse_sense: float = 0.1
var snap: Vector3 = Vector3.ZERO
var direction: Vector3 = Vector3.ZERO
var velocity: Vector3 = Vector3.ZERO
var gravity_vec: Vector3 = Vector3.ZERO
var movement: Vector3 = Vector3.ZERO
onready var body = $Body
onready var head = $Body/Head
onready var camera = $Body/Head/Camera
onready var head_position: Vector3 = head.translation
onready var body_euler_y = body.global_transform.basis.get_euler().y
var head_offset: Vector3 = Vector3.ZERO
var is_step: bool = false
const WALL_MARGIN: float = 0.001
const STEP_HEIGHT_DEFAULT: Vector3 = Vector3(0, 0.6, 0)
const STEP_MAX_SLOPE_DEGREE: float = 30.0
const STEP_CHECK_COUNT: int = 2
var step_check_height: Vector3 = STEP_HEIGHT_DEFAULT / STEP_CHECK_COUNT
var camera_target_position : Vector3 = Vector3()
var camera_coefficient: float = 1.0
var time_in_air: float = 0.0
var is_jumping = false
func _ready():
#hides the cursor
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
camera_target_position = camera.global_transform.origin
camera.set_as_toplevel(true)
camera.physics_interpolation_mode = Node.PHYSICS_INTERPOLATION_MODE_OFF
func _process(delta: float) -> void:
# Find the current interpolated transform of the target
var tr : Transform = head.get_global_transform_interpolated()
# Provide some delayed smoothed lerping towards the target position
camera_target_position = lerp(camera_target_position, tr.origin, delta * speed * stairs_feeling_coefficient * camera_coefficient)
#camera.translation = camera_target_position
camera.translation.x = tr.origin.x
if is_on_floor():
time_in_air = 0.0
camera_coefficient = 1.0
camera.translation.y = camera_target_position.y
else:
time_in_air += delta
if time_in_air > 1.0:
camera_coefficient += delta
camera_coefficient = clamp(camera_coefficient, 2.0, 4.0)
else:
camera_coefficient = 2.0
camera.translation.y = camera_target_position.y
camera.translation.z = tr.origin.z
camera.rotation.x = head.rotation.x
camera.rotation.y = body.rotation.y + body_euler_y
func _input(event):
#get mouse input for camera rotation
if event is InputEventMouseMotion:
body.rotate_y(deg2rad(-event.relative.x * mouse_sense))
head.rotate_x(deg2rad(-event.relative.y * mouse_sense))
head.rotation.x = clamp(head.rotation.x, deg2rad(-89), deg2rad(89))
func _physics_process(delta):
if Input.is_action_just_pressed("ui_cancel"):
if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
else:
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
is_step = false
#get keyboard input
direction = Vector3.ZERO
var h_rot: float = body.global_transform.basis.get_euler().y
var f_input: float = Input.get_action_strength("move_backward") - Input.get_action_strength("move_forward")
var h_input: float = Input.get_action_strength("move_right") - Input.get_action_strength("move_left")
direction = Vector3(h_input, 0, f_input).rotated(Vector3.UP, h_rot).normalized()
#jumping and gravity
if is_on_floor():
snap = -get_floor_normal()
accel = ACCEL_DEFAULT
gravity_vec = Vector3.ZERO
is_jumping = false
else:
snap = Vector3.DOWN
accel = ACCEL_AIR
gravity_vec += Vector3.DOWN * gravity * delta
if Input.is_action_just_pressed("jump") and is_on_floor():
snap = Vector3.ZERO
gravity_vec = Vector3.UP * jump
is_jumping = true
#make it move
velocity = velocity.linear_interpolate(direction * speed, accel * delta)
if gravity_vec.y >= 0:
for i in range(STEP_CHECK_COUNT):
var test_motion_result: PhysicsTestMotionResult = PhysicsTestMotionResult.new()
var step_height: Vector3 = STEP_HEIGHT_DEFAULT - i * step_check_height
var transform3d: Transform = global_transform
var motion: Vector3 = step_height
var is_player_collided: bool = PhysicsServer.body_test_motion(self.get_rid(), transform3d, motion, false, test_motion_result)
if test_motion_result.collision_normal.y < 0:
continue
if not is_player_collided:
transform3d.origin += step_height
motion = velocity * delta
is_player_collided = PhysicsServer.body_test_motion(self.get_rid(), transform3d, motion, false, test_motion_result)
if not is_player_collided:
transform3d.origin += motion
motion = -step_height
is_player_collided = PhysicsServer.body_test_motion(self.get_rid(), transform3d, motion, false, test_motion_result)
if is_player_collided:
if test_motion_result.collision_normal.angle_to(Vector3.UP) <= deg2rad(STEP_MAX_SLOPE_DEGREE):
head_offset = -test_motion_result.motion_remainder
is_step = true
global_transform.origin += -test_motion_result.motion_remainder
break
else:
var wall_collision_normal: Vector3 = test_motion_result.collision_normal
transform3d.origin += test_motion_result.collision_normal * WALL_MARGIN
motion = (velocity * delta).slide(wall_collision_normal)
is_player_collided = PhysicsServer.body_test_motion(self.get_rid(), transform3d, motion, false, test_motion_result)
if not is_player_collided:
transform3d.origin += motion
motion = -step_height
is_player_collided = PhysicsServer.body_test_motion(self.get_rid(), transform3d, motion, false, test_motion_result)
if is_player_collided:
if test_motion_result.collision_normal.angle_to(Vector3.UP) <= deg2rad(STEP_MAX_SLOPE_DEGREE):
head_offset = -test_motion_result.motion_remainder
is_step = true
global_transform.origin += -test_motion_result.motion_remainder
break
else:
var wall_collision_normal: Vector3 = test_motion_result.collision_normal
transform3d.origin += test_motion_result.collision_normal * WALL_MARGIN
motion = step_height
is_player_collided = PhysicsServer.body_test_motion(self.get_rid(), transform3d, motion, false, test_motion_result)
if not is_player_collided:
transform3d.origin += step_height
motion = (velocity * delta).slide(wall_collision_normal)
is_player_collided = PhysicsServer.body_test_motion(self.get_rid(), transform3d, motion, false, test_motion_result)
if not is_player_collided:
transform3d.origin += motion
motion = -step_height
is_player_collided = PhysicsServer.body_test_motion(self.get_rid(), transform3d, motion, false, test_motion_result)
if is_player_collided:
if test_motion_result.collision_normal.angle_to(Vector3.UP) <= deg2rad(STEP_MAX_SLOPE_DEGREE):
head_offset = -test_motion_result.motion_remainder
is_step = true
global_transform.origin += -test_motion_result.motion_remainder
break
var is_falling: bool = false
if not is_step and is_on_floor():
var test_motion_result: PhysicsTestMotionResult = PhysicsTestMotionResult.new()
var step_height: Vector3 = STEP_HEIGHT_DEFAULT
var transform3d: Transform = global_transform
var motion: Vector3 = velocity * delta
var is_player_collided: bool = PhysicsServer.body_test_motion(self.get_rid(), transform3d, motion, false, test_motion_result)
if not is_player_collided:
transform3d.origin += motion
motion = -step_height
is_player_collided = PhysicsServer.body_test_motion(self.get_rid(), transform3d, motion, false, test_motion_result)
if is_player_collided:
if test_motion_result.collision_normal.angle_to(Vector3.UP) <= deg2rad(STEP_MAX_SLOPE_DEGREE):
head_offset = test_motion_result.motion
is_step = true
global_transform.origin += test_motion_result.motion
else:
is_falling = true
else:
if test_motion_result.collision_normal.y == 0:
var wall_collision_normal: Vector3 = test_motion_result.collision_normal
transform3d.origin += test_motion_result.collision_normal * WALL_MARGIN
motion = (velocity * delta).slide(wall_collision_normal)
is_player_collided = PhysicsServer.body_test_motion(self.get_rid(), transform3d, motion, false, test_motion_result)
if not is_player_collided:
transform3d.origin += motion
motion = -step_height
is_player_collided = PhysicsServer.body_test_motion(self.get_rid(), transform3d, motion, false, test_motion_result)
if is_player_collided:
if test_motion_result.collision_normal.angle_to(Vector3.UP) <= deg2rad(STEP_MAX_SLOPE_DEGREE):
head_offset = test_motion_result.motion
is_step = true
global_transform.origin += test_motion_result.motion
else:
is_falling = true
if is_step:
speed = SPEED_ON_STAIRS
if is_jumping:
velocity.x *= 0.1
velocity.z *= 0.1
else:
head_offset = head_offset.linear_interpolate(Vector3.ZERO, delta * speed * stairs_feeling_coefficient)
if abs(head_offset.y) <= 0.01:
speed = SPEED_DEFAULT
movement = velocity + gravity_vec
if is_falling:
snap = Vector3.ZERO
# warning-ignore:return_value_discarded
move_and_slide_with_snap(movement, snap, Vector3.UP, false, 4, deg2rad(46), false)