Hi!
I use move_and_slide for my game, but my character is supposed to stick to a platform when it collide with it for 2 seconds, and then fall.
For that I coded a very simple script where it detect if there's a collision with the get_last_slide_collision( ) function, and if the player is not on_the_floor() then a timer attached to it starts, its velocity becomes Vector.ZERO and when the timer is done, the player stops sticking and fall.

It works 99% of the time, but for some reasons I can't understand, sometimes the game does detect collision but the player does not stick, it just "bump" and fall.
What's weird is that the collision IS detected, and the player is NOT on_the_floor(), but still it fall.

I have a video if you want to picture what I'm talking about : the player hits the corner of the platform and falls immediately instead of sticking. Later in the video you can see what's happening when it works and the player sticks for 2 seconds:

Do you guys know what's happening? I know it's easier if you have the script, but the game is fully programmed so there are lots of stuff going on in the player script, but here is the script if you want to take a look at it (commentaries in French sorry):

extends KinematicBody2D

# Position à laquelle tombe également le dev ghost actuel
var spawn_position = Vector2(0, -1000)
var end_teleport_position = Vector2(1170, -58250)
var before_options_position = Vector2.ZERO
export(NodePath) var player_spawn_platform = null
const GRAVITY = 900
var current_gravity = GRAVITY
var velocity = Vector2.ZERO
var start_rotation_degrees = rotation_degrees
const JUMP_FORCE_X = 300*1.5
const JUMP_FORCE_Y = 600*1.5
const JUMP_MIN_X = (JUMP_FORCE_X/4)
const JUMP_MIN_Y = (JUMP_FORCE_Y/8)
const FALL_SPEED_MAX = 2500
var is_sticking = false
var infinite_sticking = false
var collision = null
var jump_angle = 0
var jump_remember = 0
var jump_remember_time = 0.1
var fall_time = 0
var fall_high_altitude = 110
var collide_platform = null
var was_colliding = false
const UP_DIRECTION = Vector2.UP
var vanish_platform_list = []
var is_insect = false
var show_arrow_to_light = false
var mouse_position = Vector2.ZERO

# Ghost
onready var devghost_frames = load_dev_ghost_data()
var playerghost_frames = load_player_ghost_data()
#var devghost_frames = null
var play_ghost = false
onready var ghost = get_node("/root/World/Ghost")
onready var ghost_animation_player = get_node("/root/World/Ghost/AnimationPlayer")
onready var camera = $Camera2D

#var collision_system = "move_and_collide"
var collision_system = "move_and_slide"

onready var platform_timer = $PlatformTimer
onready var sprite = $Sprite
onready var aim = $Aim
onready var animation_player = $AnimationPlayer
onready var reset_position = global_position
onready var new_record_message_animation_player = get_node("/root/World/CanvasLayer/NewRecordMessage/AnimationPlayer")
onready var arrow_to_light = get_node("/root/World/ArrowToLight")
onready var secret_end_area = get_node("/root/World/Level/Areas/ZoneFinal/EndTriggerArea/SecretEndTriggerArea")
onready var end_target = get_node("/root/World/Level/EndTarget")
onready var snd_end_target = get_node("/root/World/Level/EndTarget/SndEndTarget")
onready var snd_teleport_flag = $SndTeleportFlag
onready var snd_failed_flag = $SndFailedFlag

func _ready():
	modulate = Color("fffeea")
	# Gère la position de départ du player
	if Global.save.is_new_game() and !Global.debug_version:
		global_position = spawn_position
	# Gère le ghost
	if !Global.debug_version:
		if Global.new_game_plus:
			play_ghost = true
		else:
			play_ghost = false
	else:
		play_ghost = true

# Called when the node is created
func _enter_tree():
	Global.player = self

# Called when the node is destructed
func _exit_tree():
	Global.player = null

func reload_data():
	playerghost_frames = load_player_ghost_data()
	global_position = spawn_position
	animation_player.play("jump")
	velocity = Vector2.ZERO
	is_insect = false
	Global.music_manager.snd_fly.stop()
	show_arrow_to_light = false
	# Gère le ghost
	if !Global.debug_version:
		if Global.new_game_plus:
			play_ghost = true
		else:
			play_ghost = false
	else:
		play_ghost = true

func _process(delta):
	# Enregistre la position du joueur à chaque frame et l'ajoute au tableau ghost_frames
	if !Global.player_ghost_too_long or !Global.game_is_completed:
		save_player_ghost()
	play_ghost()
	arrow_to_light.global_position = Vector2(global_position.x, global_position.y - 150)
	if !Global.controller_is_connected:
		mouse_position = get_global_mouse_position()
	update_altitude()

# Update physic du joueur
func _physics_process(delta):
	# Empêche le joueur de jouer s'il a atteint la secret ending
	if Global.secret_ending_is_running:
		Global.music_manager.snd_fly.stop()
		return
		
	Global.altitude = floor(-global_position.y)
	# Téléporte le player au flag s'il existe
	if Input.is_action_just_pressed("teleport_to_flag"):
		teleport_to_flag()
	# Gère les variables utiles
	var jumping = Input.is_action_just_pressed("jump")
	
	# Gère jump buffering
	jump_remember -= delta
	if jumping:
		jump_remember = jump_remember_time
	
	# Applique la gravité
	current_gravity = GRAVITY
	
	# Calcule la vélocité Y
	velocity.y += (current_gravity * delta)
	
	# Aim
	if Global.controller_is_connected: # Gameplay avec joystick
		aim_to_joystick()
	else: # Gameplay avec souris
		aim_to_mouse()
	
	# Le joueur colle aux plateformes (et l'empêche de slide trop)
	if !is_on_air():
		if is_sticking:
			velocity = Vector2.ZERO
	else:
		fall_time += 1
			
	# Gère le jump
	if !is_on_air() and !is_insect:
		# Gère forme insecte
		# Jump normal
		if Input.is_action_just_pressed("jump") and is_sticking:
			process_jump()
			# Animation jump
			animation_player.play("prepare_jump")
			# Joue son
			Global.music_manager.snd_jump.play()
		# Jump buffering
		elif jump_remember > 0:
			jump_remember = 0
			process_jump()
			# Animation prepare_jump
			animation_player.play("prepare_jump")
			# Joue son
			Global.music_manager.snd_jump_buffering.play()
	else:
		if Input.is_action_just_released("jump") and !is_insect: 
			if velocity.y < -JUMP_MIN_Y:
				velocity.y = -JUMP_MIN_Y

	# Gère vol avec la forme insecte
	if is_insect and !Global.cinematic_is_running:
		if Input.is_action_pressed("fly"):
			process_fly()
			# Animation insect fly
			animation_player.play("insect_fly")
			# Joue son
			if !Global.music_manager.snd_fly.playing:
				Global.music_manager.snd_fly.play()
		else:
			# Animation insect idle
			animation_player.play("insect_idle")
			# Stoppe son
			if Global.music_manager.snd_fly.playing:
				Global.music_manager.snd_fly.stop()
		
#	# Système move_and_collide()
#	collision = move_and_collide(velocity * delta)
	# Système move_and_slide()
	velocity = move_and_slide(velocity, UP_DIRECTION)
	
	# Gère is_sticking
	if is_colliding():
		set_collide_platform()
		if !was_colliding:
			var platform = get_last_slide_collision().get_collider()
			# Reset vanishing plateformes
			if !platform.disable_vanish:
				for curr_platform in Global.vanish_platform_list:
					curr_platform.vanish_appear()
			if !is_insect:
				Global.achievement.check_fall_time(fall_time)
				if fall_time > 20:
					# Animation hit_ground
					if Global.end_is_triggered and Global.splash_anim:
						play_ghost = false
						animation_player.play("end_splash")
						Global.disable_stick = false
						is_insect = true
						snd_end_target.playing = true
						Global.game_is_completed = true
						Global.music_manager.play_end_music()
						Global.achievement.check_insect()
						Global.achievement.set_leaderboard_speedrun()
						# Utile seulement lorsque j'enregistre le premier devghost
#						var devghost_file = File.new()
#						if not devghost_file.file_exists("res://Files/devghost.save"):
#							save_dev_ghost_data()
#							return
						if Global.new_game_plus:
							if !Global.player_ghost_too_long:
								if Global.dev_ghost_is_beaten and playerghost_frames: # Dev ghost déjà battu
									if Global.game_timer < playerghost_frames.size():
										Global.dev_ghost_is_beaten = true
										Global.achievement.check_player_ghost()
										new_record_message_animation_player.play("anim")
										Global.music_manager.snd_victory.play()
										save_player_ghost_data()
								else: # Dev ghost pas encore battu
									if Global.game_timer < devghost_frames.size():
										Global.dev_ghost_is_beaten = true
										Global.achievement.check_dev_ghost()
										new_record_message_animation_player.play("anim")
										Global.music_manager.snd_victory.play()
										save_player_ghost_data()
						else:
							Global.new_game_plus = true
					else:
						if fall_time > (fall_high_altitude * 2):
							# Anim très grosse chute
							animation_player.play("mega_hit_ground")
							camera.add_trauma(1)
						else:
							# Anim petite chute
							animation_player.play("hit_ground")
					if fall_time > fall_high_altitude:
						# Joue son grosse chute
						Global.music_manager.snd_jump_buffering.play()
					else:
						# Joue son petite chute
						Global.music_manager.snd_impact_small.play()
		fall_time = 0
	else:
		# Reset collide_platform
		collide_platform = null
		# Reset le platform_timer
		platform_timer.stop()
#		is_sticking = true # A ÉTÉ MIS EN COMMENTAIRE CAR PAS CERTAIN QUE CE SOIT UTILE ??
	was_colliding = is_colliding()
	
	# Gère les collisions avec les vanishing plateformes
	if is_colliding_with_vanishing():
		if collision_system == "move_and_collide":
			collision.vanish_disappear()
		elif collision_system == "move_and_slide":
			var platform = get_last_slide_collision().get_collider()
			if !platform.disable_vanish:
				platform.vanish_disappear()
				
	# Applique une limite max à la vélocité Y
	velocity.y = clamp(velocity.y, -FALL_SPEED_MAX, FALL_SPEED_MAX)
	
	# DEBUG
	if get_last_slide_collision() and !is_on_floor():
#		velocity = Vector2.ZERO
		print("bubu") # OUI
#		if is_sticking: # OUI
#			print("byby")
#		if is_on_air(): # NON
#			print("bobo")
#		if is_on_wall(): # ÇA DÉPEND
#			print("bzbz")
#		if is_on_ceiling(): # ÇA DÉPEND
#			print("bcbc")
		print(platform_timer.is_stopped())
		print()
	
	# Empêche le player de se coller à la fin du jeu
	if Global.disable_stick:
		is_sticking = false
		
	# Empêche la rotation du player à la fin du jeu
	if Global.reset_player_rotation:
		rotation_degrees = start_rotation_degrees
		
	# Gère angle du player en mode insecte s'il vole
	if is_insect and is_on_air():
		update_insect_angle()
		
	# Gère l'arrow_to_light une fois en forme insecte
	if show_arrow_to_light:
		rotate_arrow_to_light()
		
	# DEBUG: Permet de gérer des controls de debug (téléport, save, etc)
	if Global.debug_version:
		check_debug_controls()
	
func update_altitude():
	Global.altitude = global_position.y
	Global.achievement.check_altitude()

# Vérifie si le joueur est en train de collisionner
func is_colliding():
	if collision_system == "move_and_collide":
		if collision != null:
			return true
		else:
			return false
	elif collision_system == "move_and_slide":
		return get_slide_count() > 0
		
func set_collide_platform():
	if collision_system == "move_and_collide":
		if collide_platform != collision:
			if !is_on_top():
				platform_timer.start()
				collide_platform = collision
			else:
				is_sticking = true
	elif collision_system == "move_and_slide":
		if collide_platform != get_last_slide_collision().get_collider():
			is_sticking = true
			if !is_on_floor():
				platform_timer.start()
				collide_platform = get_last_slide_collision().get_collider()
#	# DEBUG
#	if collision_system == "move_and_slide":
#		if collide_platform != get_last_slide_collision().get_collider():
#			if !is_on_floor():
#				platform_timer.start()
#				collide_platform = get_last_slide_collision().get_collider()
#			else:
#				is_sticking = true
				
func is_on_top():
	var width_to_add = 0
	if global_position.y < (collision.position.y + width_to_add):
		return true
	else:
		return false
	
# Vérifie si le joueur collisionne avec un objet en particulier
func is_colliding_with(pObject):
	if collision_system == "move_and_collide":
		if pObject and collision:
			return collision.collider == pObject
	elif collision_system == "move_and_slide":
		if get_slide_count() > 0:
			return get_last_slide_collision().get_collider() == pObject
	return false
	
# Vérifie si le joueur collisionne avec un objet is_vanishing
func is_colliding_with_vanishing():
	if collision_system == "move_and_collide":
		if collision != null:
			if collision.collider.is_vanishing:
				return true
	elif collision_system == "move_and_slide":
		if get_slide_count() > 0:
			if get_last_slide_collision().get_collider().is_vanishing:
				return true
	return false
	
# Vérifie si le joueur est dans les airs
func is_on_air():
	return !is_colliding()
	
# Permet de viser dans une direction avec le joystick
func aim_to_joystick():
	# Rotationne le viseur (aim)
	var joystick_vector = Vector2(Input.get_action_strength("move_right") - Input.get_action_strength("move_left"), Input.get_action_strength("move_down") - Input.get_action_strength("move_up"))
	var controller_deadzone = 0.32
	if Vector2.ZERO.distance_to(joystick_vector) > controller_deadzone*sqrt(2.0):
		joystick_vector = joystick_vector.normalized()
		aim.visible = true
	else:
		joystick_vector = Vector2(0, -JUMP_FORCE_Y)
		aim.visible = false
	aim.global_rotation = atan2(joystick_vector.y, joystick_vector.x)

# Permet de viser dans une direction avec la souris
func aim_to_mouse():
	# Rotationne le viseur (aim)
	var mouse_vector = get_global_mouse_position() - aim.global_position
	aim.global_rotation = atan2(mouse_vector.y, mouse_vector.x)

func update_insect_angle():
	if Global.controller_is_connected:
		var joystick_vector = Vector2(Input.get_action_strength("move_right") - Input.get_action_strength("move_left"), Input.get_action_strength("move_down") - Input.get_action_strength("move_up"))
		var controller_deadzone = 0.32
		if Vector2.ZERO.distance_to(joystick_vector) > controller_deadzone*sqrt(2.0):
			global_rotation = atan2(joystick_vector.y, joystick_vector.x) - PI*3.5
		else:
			global_rotation = atan2(-JUMP_FORCE_Y, joystick_vector.x) - PI*3.5
	else:
		var mouse_vector = get_global_mouse_position() - global_position
		global_rotation = atan2(mouse_vector.y, mouse_vector.x) - PI*3.5
	
func show_arrow_to_light():
	arrow_to_light.visible = true
	show_arrow_to_light = true
	
func rotate_arrow_to_light():
	var max_gap = 1000
	var max_angle = 90
	var difference_x = (end_target.global_position.x - arrow_to_light.global_position.x)
	var angle = (difference_x / max_gap) * max_angle
	arrow_to_light.rotation_degrees = clamp(angle, -max_angle, max_angle)
	
# Calcule la vélocité du saut
func process_jump():
	if Global.disable_jump:
		return
	if Global.controller_is_connected:
		var joystick_vector = Vector2(Input.get_action_strength("move_right") - Input.get_action_strength("move_left"), Input.get_action_strength("move_down") - Input.get_action_strength("move_up"))
		var controller_deadzone = 0.32
		if Vector2.ZERO.distance_to(joystick_vector) > controller_deadzone*sqrt(2.0):
			joystick_vector = joystick_vector.normalized()
		else:
			joystick_vector = Vector2(0, -JUMP_FORCE_Y)
			joystick_vector = joystick_vector.normalized()
		velocity.x = joystick_vector.x * JUMP_FORCE_X
		velocity.y = joystick_vector.y * JUMP_FORCE_Y
		# Rotationne le player
		var old_aim_rotation = aim.global_rotation
		global_rotation = atan2(joystick_vector.y, joystick_vector.x) + 30
		# Empêche le aim d'être rotationné pedant une frame
		aim.global_rotation = old_aim_rotation
	else:
		var mouse_vector = get_global_mouse_position() - global_position
		mouse_vector = mouse_vector.normalized()
		velocity.x = mouse_vector.x * JUMP_FORCE_X
		velocity.y = mouse_vector.y * JUMP_FORCE_Y
		# Rotationne le player
		var old_aim_rotation = aim.global_rotation
		global_rotation = atan2(mouse_vector.y, mouse_vector.x) + 30
		# Empêche le aim d'être rotationné pedant une frame
		aim.global_rotation = old_aim_rotation
	# Increments the total of nb jump
	Global.nb_jump_total += 1
	Global.achievement.check_nb_jump()
	
# Calcule la vélocité du vol
func process_fly():
	if Global.controller_is_connected:
		var joystick_vector = Vector2(Input.get_action_strength("move_right") - Input.get_action_strength("move_left"), Input.get_action_strength("move_down") - Input.get_action_strength("move_up"))
		var controller_deadzone = 0.32
		var old_aim_rotation = aim.global_rotation
		if Vector2.ZERO.distance_to(joystick_vector) > controller_deadzone*sqrt(2.0):
			joystick_vector = joystick_vector.normalized()
			# Rotationne le player
			global_rotation = atan2(joystick_vector.y, joystick_vector.x) + 30
		else:
			joystick_vector = Vector2.ZERO
			joystick_vector = joystick_vector.normalized()
		velocity.x = joystick_vector.x * JUMP_FORCE_X
		velocity.y = joystick_vector.y * JUMP_FORCE_Y
		# Empêche le aim d'être rotationné pedant une frame
		aim.global_rotation = old_aim_rotation
	else:
		var mouse_vector = get_global_mouse_position() - global_position
		mouse_vector = mouse_vector.normalized()
		velocity.x = mouse_vector.x * JUMP_FORCE_X
		velocity.y = mouse_vector.y * JUMP_FORCE_Y
		# Rotationne le player
		var old_aim_rotation = aim.global_rotation
		# Empêche le aim d'être rotationné pedant une frame
		aim.global_rotation = old_aim_rotation
		global_rotation = aim.global_rotation
	
# Téléporte au flag (s'il y en a un)
func teleport_to_flag():
	# Empêche de se téléporter au flag si les flags sont disabled (pour al fin)
	if Global.flags_are_disabled:
		return
	if Global.active_flag:
		snd_teleport_flag.play()
		Global.active_flag.teleport()
	else:
		snd_failed_flag.play()
	
func anim_jump():
	if is_on_air():
		animation_player.play("jump")
	else:
		anim_idle()
	
func anim_idle():
	# Annule rotation du player
	global_rotation = 0
	animation_player.play("idle")
	
func anim_insect_idle():
	animation_player.play("insect_idle")
	
func anim_mega_hit_ground():
	animation_player.play("mega_hit_ground")
	
func disable_cinematic():
	Global.cinematic_is_running = false

func _on_PlatformTimer_timeout():
	# Le player se décolle (sauf si sticking infini appliqué)
	if !infinite_sticking:
		is_sticking = false
		# Joue son
		Global.music_manager.snd_unstick.play()
		
func check_debug_controls():
	if Global.debug_version:
		if Input.is_action_just_pressed("debug_reset_player_position"):
			if player_spawn_platform == null:
				global_position = reset_position
			else:
				global_position = Vector2(get_node(player_spawn_platform).global_position.x + 10, get_node(player_spawn_platform).global_position.y - 10)
		if Input.is_action_just_pressed("debug_teleport_end"):
			global_position = end_teleport_position
		if Input.is_action_just_pressed("debug_save_ghost"):
			save_dev_ghost_data()

# Enregistre la position du joueur à chaque frame et l'ajoute au tableau ghost_frames
func save_player_ghost():
	if Global.game_is_completed or Global.player_ghost_too_long:
		return
	if devghost_frames == null:
		# Enregistre la position du joueur et l'ajoute au tableau ghost_frames
		var ghost_pos = [position.x, position.y]
		Global.ghost_frames.append(ghost_pos)
		return
	if Global.dev_ghost_is_beaten and playerghost_frames: # Dev ghost déjà battu
		if Global.ghost_frames.size() < playerghost_frames.size():
			# Enregistre la position du joueur et l'ajoute au tableau ghost_frames
			var ghost_pos = [position.x, position.y]
			Global.ghost_frames.append(ghost_pos)
		else:
			Global.player_ghost_too_long = true
	else:
		if Global.ghost_frames.size() < devghost_frames.size():
			# Enregistre la position du joueur et l'ajoute au tableau ghost_frames
			var ghost_pos = [position.x, position.y]
			Global.ghost_frames.append(ghost_pos)
		else:
			Global.player_ghost_too_long = true
	
func play_ghost():
	if !play_ghost :
		ghost.visible = false
		return
	if Global.dev_ghost_is_beaten and playerghost_frames: # Player ghost
		if Global.game_timer >= playerghost_frames.size():
			play_ghost = false
			return
		else:
			ghost.visible = true
			ghost_animation_player.play("idle")
			ghost.global_position = Vector2(playerghost_frames[Global.game_timer][0], playerghost_frames[Global.game_timer][1])
	else: # Dev ghost
		if devghost_frames == null:
			play_ghost = false
			return
		if Global.game_timer >= devghost_frames.size():
			ghost.visible = false
			play_ghost = false
			return
		else:
			ghost.visible = true
			ghost_animation_player.play("idle")
			ghost.global_position = Vector2(devghost_frames[Global.game_timer][0], devghost_frames[Global.game_timer][1])
		
func save_dev_ghost_data():
	# Méthode 1 : file dans le dossier de save
#	var save_file = File.new()
#	save_file.open("user://devghost.save", File.WRITE)
#	save_file.store_line(to_json(ghost_frames))
#	save_file.close()
	# Méthode 2 : file dans le dossier files
	var save_file = File.new()
	save_file.open("res://Files/devghost.save", File.WRITE)
	save_file.store_line(to_json(Global.ghost_frames))
	save_file.close()
	# Save backup
	var save_backup_file = File.new()
	var backup_path = "res://Files/Backups/devghost_" + str(Global.ghost_frames.size()) + ".save"
	save_backup_file.open(backup_path, File.WRITE)
	save_backup_file.store_line(to_json(Global.ghost_frames))
	save_backup_file.close()
	return
	
func save_player_ghost_data():
	var save_file = File.new()
	save_file.open("user://playerghost.save", File.WRITE)
	save_file.store_line(to_json(Global.ghost_frames))
	save_file.close()
	# Save backup
	var save_backup_file = File.new()
	var backup_path = "res://Files/Backups/playerghost_" + str(Global.ghost_frames.size()) + ".save"
	save_backup_file.open(backup_path, File.WRITE)
	save_backup_file.store_line(to_json(Global.ghost_frames))
	save_backup_file.close()
	
func load_dev_ghost_data():
	# Variables directement intégrée dans le code
	var devghost_frames = Global.ghost.devghost_data
	return devghost_frames
	
func load_player_ghost_data():
	var save_file = File.new()
	if not save_file.file_exists("user://playerghost.save"):
		play_ghost = false
		return
	save_file.open("user://playerghost.save", File.READ)
	var playerghost_frames_tab = []
	while save_file.get_position() < save_file.get_len():
		# Get the saved dictionary from the next line in the save file
		playerghost_frames_tab.append(parse_json(save_file.get_line()))
	save_file.close()
	var playerghost_frames = playerghost_frames_tab[0]
	return playerghost_frames

Thanks for your help!

Move and slide is good for walking around and colliding with the ground or walls. It's not so great to detect precise collisions (especially at edge cases, like the corner of a square). You are probably better of using a RayCast node that is pointing in the direction of the player velocity and use that for the sticking logic.

https://docs.godotengine.org/en/stable/tutorials/physics/ray-casting.html

    cybereality The weird thing is: the collision actually had been detected, for sure. I used console prit to make sure of that. This is why the bug seem so strange to me. Even in the video you can see that the collision has been detected because the platform on the right reset when the player touch the other platform: it's a feature on the game and it's another proof the collision has been detected for sure.

      I discovered the same thing @cybereality pointed out. If move and slide is screwy, use a raycast. The same tactic works in 3D.

      MonsieurBouboule The weird thing is: the collision actually had been detected

      Yes, collision will be detected, but it's not consistent. The collision may happen on the next frame, or 2 frames later, (and then the object is moved) but this usually looks believable. Is on floor also does not always detect every frame (sometimes it's true or false, even if you are on the floor). For it's intended purpose, for walking around, it works perfectly fine. But it's not accurate enough, or consistent enough, to detect precise collision.

        cybereality Ok thanks I'll try raycasting. Butg what I meant is each time the problem occured it detected the collision, and the is_on_floor() worked and I know it for sure because I checked using console prints. So if the moment the character did not stick it actually detected collision and is_on_floor() was working, I assumed it was not the source of the problem. I'm not the greatest coder so I may be wrong about that, I'll try the raycast system anyway and I don't doubt it works better, but I just thought that's not what the problem was.
        Thanks for your help anyway, hope it'll work! 🙂

        Ok I've changed my collision system for a raycast based collision system. I'll keep it since it's apparently handling collisions more precisely, but it does not fix the problem I've been describing in this thread.
        Again, collisions are definitely detected, but something else seems to cause the character sometimes not to stick to platforms like it should.

        Somebody else has an idea where the problem could come from?

        Yeah, it could be something else in your code, but it's a lot of code to look through.