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!