Files
Princesse-Lactose-Godot/princesse.gd
Thomas Lavocat fb4599d197 princess/marche+kick: réinitialisation correct des l'état
Certaines variables d'état nécessaire au bon déroulement des
décélérations et de la marche n'étaient pas remises au bon état
correctement en fin de marche. Ce commit essaye d'addresser ce soucis.
2023-04-30 12:09:50 +02:00

387 lines
14 KiB
GDScript

extends CharacterBody2D
## The player listens for input actions appended with this suffix.[br]
## Used to separate controls for multiple players in splitscreen.
@export var action_suffix := ""
var gravity: int = ProjectSettings.get("physics/2d/default_gravity")
@onready var wall_detect_left := $wall_detect_left as RayCast2D
@onready var wall_detect_right := $wall_detect_right as RayCast2D
@onready var ground_far_detect := $ground_far_detect as RayCast2D
@onready var animation := $AnimatedSprite2D as AnimatedSprite2D
@onready var camera := $Camera2D as Camera2D
################################################################################
#
# Constantes de déplacement à pimper dans l'inspecteur
#
################################################################################
@export var WALKING_SPEED = 400
@export var FALLING_SPEED = 400
@export var JUMPING_SPEED = 400
@export var X_SPEED_TABLE = [0, 0.1, 0.15, 0.2, 0.3, 0.6, 0.9, 1]
@export var X_SPEED_DECEL = [0, 0.1, 0.6, 1]
@export var FALL_SPEED_TABLE = [0, 0.1, 0.15, 0.2, 0.3, 0.6, 0.9, 1]
@export var JUMP_SPEED_TABLE = [0, 0.1, 0.2, 0.5, 0.6, 0.7, 0.8, 0.9, 1]
@export var KICK_SPEED_TABLE = [0.6, 0.7, 0.8, 0.9, 1, 1, 1, 1, 1, 1]
# Nombre d'incrément à rajouter lorsque la touche de saut est maintenue
@export var JUMPING_COUNTER_REFILL = 2
# Tous les combien d'incréments rajouter un refill sur le compteur
@export var JUMPING_KEY_COUNTER_THRESHOLD = 3
# Nombre de frames coyote durant lesquelle le joueur peut encore sauter
# sans encore être au sol
@export var COYOTE_LENGTH := 15
# Différence de vitesse de ralentissement entre le sol et l'air
@export var WALK_INCR_GROUND : int = 1
@export var WALK_INCR_AIR : int = 3
################################################################################
#
# Etat de la princesse
#
################################################################################
# Direction dans laquelle est positioné le personnage.
var direction : int = 1
var walking : bool = false # si le joueur veut marche ou non
var walking_step: int = -1 # où la princesse en est dans son tableau d'accel
var walk_incr_reserve : int = 0 # utilisé pour la différence ralentissement air/sol
var init_decel: bool = true # utilisé pour initialiser la décélération
var init_direction_change = true # utilisé pour initialiser le changement de direction
# variables d'état relatives au saut
var jumping : bool = false # Princesse est-elle en train de sauter ?
var need_jump : bool = false # Le joueur veut-il sauter et peut il ?
var jumping_step : int = -1 # Où en est la princesse dans son tableau d'accel
var jump_key_counter : int = 0 # Où en est-on du refil ?
# variables d'état relatives au kick mural
var kicking : bool = false
var kick_step : int = -1
var kick_direction : int = 1
# Coyote time
var coyote_ground = []
# variable d'état relative à la gravité
var falling_step : int = -1 # Où en est la princess dans son acceleration de chute ?
# Variables d'état relative à l'accroche au mur
var pressing_wall = false # Princesse est elle en contact avec un mur ?
var grab_wall = false # Je joueur veut-il et peut-il s'accrocher au mur ?
################################################################################
#
# Gestion d'avec quoi Princesse collisionne
#
################################################################################
const PLATFORM_LAYER = 1 << (5 - 1) # collision layer 5 -> plateformes
const PICS_BLOCK_LAYER = 1 << (6 - 1) # collision layer 6 -> pics
const CHEESE_LAYER = 1 << (6 - 1) # collision layer 7 -> fromages
var layer_of_collision = null
func init_walk_state():
walk_incr_reserve = 0
walking_step = -1
init_decel = false
func walk(direction:int) -> float:
# Fait marcher le personnage.
if kicking:
init_walk_state()
return velocity.x
if velocity.x == 0 and not walking:
init_walk_state()
# Si le personnage est dans l'air, il aura une friction plus faible lors de
# la décélération.
var threshold = WALK_INCR_AIR
if is_on_floor():
threshold = WALK_INCR_GROUND
# Un changement de direction implique de perdre la vélocité dans la direction
# précédente avant de repartir dans la direction que l'on veut.
# ça ne change rien pour le nombre de frames nécéssaires pour accélérer dans
# tous les cas. Mais le feeling est meilleur.
var direction_change: bool = (direction > 0 and velocity.x < 0) or (direction < 0 and velocity.x > 0)
if direction_change:
if init_direction_change:
init_direction_change = false
walking_step = -1
else:
init_direction_change = true
# Si le joueur décide de marcher alors, le compteur de pas doit s'incrémenter
# Si le joueur ne veut plus marcher, alors, le compteur de pas décrémente
# La vitese choisie est le numéro d'étape dans le tableau correspondant
# X_SPEED_TABLE pour l'accélération
# X_SPEED_DECEL pour la décélération
var table = X_SPEED_TABLE
if not walking:
table = X_SPEED_DECEL
# dans le cas où le personnage est propulsé par une force extérieure et
# qu'il n'y a pas d'input du joueur. Lorsque le joueur va vouloir reprendre
# la main, il faut démarrer à la bonne vélocité
var abs_v_x = abs(velocity.x)
if abs_v_x != 0:
if walking_step == -1:
# trouver l'indice le plus proche de la vitesse courante
for index in range(0, table.size()-1):
var speed_i = table[walking_step] * WALKING_SPEED
var speed_i1 = table[walking_step+1] * WALKING_SPEED
# lorsque l'on a trouvé l'indice le bon endroit dans le tableau, alors
# on renvoie l'indice trouvé
if abs_v_x == speed_i:
walking_step = index
if abs_v_x == speed_i1:
walking_step = index+1
if abs_v_x > speed_i and abs_v_x < speed_i1:
walking_step = index+1
# si rien n'est trouvé, alors on initialise l'indice au maximum possible
if walking_step == -1:
walking_step = table.size() - 1
if walking:
walking_step = min(walking_step+1, X_SPEED_TABLE.size() -1)
init_decel = true
else:
# Lors de la première frame de la décélération, initialiser la valeur
# du compteur d'incrément en haut du tableau de décélération
if init_decel:
walking_step = min(walking_step, X_SPEED_DECEL.size() - 1)
init_decel = false
# Si le compteur d'incrément est supérieur ou égal à zéro, c'est qu'il
# faut bouger, donc il est nécéssaire en premier temps de récupérer la vitesse
# à l'indice courant. Puis si le joueur ne veut plus accélérer, alors, appliquer
# la décélération
if walking_step >= 0:
var speed = table[walking_step] * WALKING_SPEED
if not walking:
walk_incr_reserve += 1
# appliquer le nombre de frames nécéssaire pour décrémenter, dépend
# de où est le personnage (air vs sol)
if walk_incr_reserve >= threshold:
walk_incr_reserve = 0
walking_step-=1
if direction_change and walking:
# dans le cas du changement de direction,
return speed * direction + velocity.x
else:
return speed * direction
else:
init_walk_state
return velocity.x
func fall() -> float:
# fait tomber princesse
if jumping or kicking:
falling_step = -1
return velocity.y
if is_on_floor_only():
if get_floor_normal()[0] < 0: # pente à gauche
if direction >= 0:# on va à droite, désactive la gravité
falling_step = -1
return velocity.y
else: # on va à gauche, gravité à fond
falling_step = FALL_SPEED_TABLE.size()-1
else: # pente à droite
if direction > 0:# on va à droite, active la gravité à fond
falling_step = FALL_SPEED_TABLE.size()-1
else: # on va à gauche, désactive la gravité
falling_step = -1
return velocity.y
else:
if grab_wall:
falling_step = max(falling_step-1, 1)
else:
falling_step = min(falling_step+1, FALL_SPEED_TABLE.size()-1)
return FALL_SPEED_TABLE[falling_step] * FALLING_SPEED
func jump() -> float:
# fait sauter princesse
if not jumping or kicking:
return velocity.y
if not is_on_ceiling() and jump_key_counter > 0 and jump_key_counter % JUMPING_KEY_COUNTER_THRESHOLD == 0:
jumping_step = min(
jumping_step + JUMPING_COUNTER_REFILL,
JUMP_SPEED_TABLE.size() -1
)
if jumping_step > 0:
jumping_step -= 1
return JUMP_SPEED_TABLE[jumping_step] * JUMPING_SPEED * -1
else:
end_jump()
return velocity.y
func kick() -> void:
# fait sauter princesse
if not kicking:
return
if kick_step > 0:
kick_step -= 1
velocity.y = KICK_SPEED_TABLE[kick_step] * JUMPING_SPEED * -1 * 0.8
velocity.x = KICK_SPEED_TABLE[kick_step] * WALKING_SPEED * kick_direction
else:
kick_step = -1
kicking=false
func end_jump():
# termine le saut de Princesse
jumping = false
jumping_step = -1
jump_key_counter = 0
func choose_animation_orientation() -> void:
# Oriente l'animation correctement en fonction de laquelle on joue et de
# la direction du personnage
if not is_zero_approx(velocity.x):
if velocity.x > 0.0:
animation.scale.x = 1.0
else:
animation.scale.x = -1.0
if grab_wall:
if wall_detect_left.is_colliding():
animation.scale.x = 1
elif wall_detect_right.is_colliding():
animation.scale.x = -1
func play_animation() -> void:
# Joue l'animation de Princesse et dans le bon sens
choose_animation_orientation()
var anim := get_new_animation()
if anim != animation.animation:
animation.animation = anim
animation.play()
func move_and_handle_collisions() -> void:
# Bouge Princesse et réagis aux éléments avec lesquels elle rentre en collision
var collision = move_and_slide()
if collision:
var collider = get_last_slide_collision().get_collider()
if collider is TileMap:
var tile_rid = get_last_slide_collision().get_collider_rid()
layer_of_collision = PhysicsServer2D.body_get_collision_layer(tile_rid)
if layer_of_collision == PICS_BLOCK_LAYER:
get_tree(). reload_current_scene()
func get_coyote(table: Array):
var result = false
for i in table:
result = result or i
return result
func read_input() -> void:
# Lis les commandes du joueur pour piloter Princesse
# Le joueur veut-il et peut-il s'accrocher au mur ?
grab_wall = pressing_wall and Input.is_action_pressed("grab" + action_suffix)
# Le joueur veut-il sauter ou kicker ?
if Input.is_action_just_pressed("jump" + action_suffix):
# Peut-il sauter ?
if ground_far_detect.is_colliding() or (is_on_floor() or get_coyote(coyote_ground)):
need_jump=true
# Peut-il kicker ?
if grab_wall:
if not kicking:
kicking=true
kick_step = KICK_SPEED_TABLE.size()-1
if wall_detect_left.is_colliding():
kick_direction = 1
else:
kick_direction = -1
# Dès qu'il peut et veut sauter, déclencher le saut
if need_jump and (is_on_floor() or get_coyote(coyote_ground)):
need_jump = false
if not jumping:
jumping = true
jumping_step = JUMP_SPEED_TABLE.size()-1
jump_key_counter = 0
# Si il continue d'appuyer, lui rallonger son saut
if Input.is_action_pressed("jump" + action_suffix):
if jumping:
jump_key_counter += 1
else:
jump_key_counter = 0
# Le joueur veut-il marcher ?
walking = (
Input.is_action_pressed("move_left" + action_suffix) or
Input.is_action_pressed("move_right" + action_suffix)
)
# Calculer dans quelle direction il veut marcher
if walking:
var axis = Input.get_axis(
"move_left" + action_suffix,
"move_right" + action_suffix
)
if not is_zero_approx(axis):
if axis < 0:
direction = -1
else:
direction = 1
else:
if kicking:
direction = kick_direction
func compute_state() -> void:
# Met à jour une partie de l'état de la princesse
# gestion du coyote time sur le contact au sol
coyote_ground.append(is_on_floor())
if coyote_ground.size() > COYOTE_LENGTH:
coyote_ground.remove_at(0)
# gestion de l'état de la pression au mur
pressing_wall = not is_on_floor() and (wall_detect_left.is_colliding() or wall_detect_right.is_colliding())
func _physics_process(delta: float) -> void:
compute_state()
read_input()
velocity.y = jump()
velocity.y = fall()
velocity.x = walk(direction)
kick()
move_and_handle_collisions()
play_animation()
func get_new_animation() -> String:
# Renvoie la bonne annimation en fonction de l'état de la princesse
var animation_new: String
if is_on_floor():
if walking_step > 0:
animation_new = "walk"
else:
animation_new = "idle"
else:
if velocity.y > 0.0:
if grab_wall:
animation_new = "wall_stick"
else:
if walking_step > 0:
animation_new = "falling_diagonals"
else:
animation_new = "falling_straight"
else:
animation_new = "jumping"
return animation_new