extends CharacterBody3D class_name PlayerCharacter #movement variables var move_speed : float var move_accel : float var move_deccel : float var move_dir : Vector2 var target_angle : float var last_input_dir : Vector2 var last_frame_position : Vector3 var last_frame_velocity : Vector3 var was_on_floor : bool = false enum Locomotion { WALK, RUN } var locomotion : Locomotion = Locomotion.WALK #keep in memory if play char was walking or running before being in the air var walk_or_run : String: #ANCIEN NOM, lecture seule : a supprimer des que plus aucun script ne l'utilise (chercher "walk_or_run" dans le projet) get: return get_locomotion_state() var is_dodging : bool = false #true while DodgeState is active ; hook this up to a hurtbox/i-frame check later var is_crouching : bool = false #posture accroupie (voir GroundedState.toggle_crouch) ; hook this up to a collision-shape resize later var is_mounted : bool = false #true tant que MountRider a le joueur sur un pokemon var _base_ground_stats : Dictionary = {} @export_group("Walk variables") @export var walk_speed : float = 5.0 @export var walk_accel : float = 8.0 @export var walk_deccel : float = 7.5 @export_group("Run variables") @export var run_speed : float = 9.0 @export var run_accel : float = 3.5 @export var run_deccel : float = 6.5 @export var continious_run : bool = false #if true, doesn't need to keep run button on to run @export_group("Dodge variables") @export var dodge_speed : float = 10.0 @export var dodge_duration : float = 0.4 @export var dodge_cooldown : float = 0.6 var dodge_cooldown_ref : float @export_group("Crouch variables") @export var crouch_speed : float = 2.5 @export var crouch_accel : float = 7.0 @export var crouch_deccel : float = 7.0 @export_group("Jump variables") @export var jump_height : float = 3.0 @export var jump_time_to_peak : float = 0.35 @export var jump_time_to_descent : float = 0.29 @onready var jump_velocity : float = ((2.0 * jump_height) / jump_time_to_peak) * -1.0 var has_cut_jump : bool = false @export var jump_cut_multiplier : float = 0.5 @export var jump_cooldown : float = 0.2 var jump_cooldown_ref : float @export var nb_jumps_in_air_allowed : int = 1 var nb_jumps_in_air_allowed_ref : int var jump_buff_on : bool = false var buffered_jump : bool = false @export var coyote_jump_cooldown : float var coyote_jump_cooldown_ref : float var coyote_jump_on : bool = false @export var auto_jump : bool = false @export_group("In air variables") @export var in_air_move_speed : Array[Curve] #one curve for if was walking before jumping/falling, one curve for if was running before jumping/falling @export var in_air_accel : Array[Curve] #one curve for if was walking before jumping/falling, one curve for if was running before jumping/falling @export var hit_wall_cut_velocity : bool = false #gravity variables @onready var jump_gravity : float = ((-2.0 * jump_height) / (jump_time_to_peak * jump_time_to_peak)) * -1.0 @onready var fall_gravity : float = ((-2.0 * jump_height) / (jump_time_to_descent * jump_time_to_descent)) * -1.0 @export_group("Model variables") @export var model_rot_speed : float = 6.5 @export var follow_cam_pos_when_aimed : bool = false #references variables : rien n'est cherche par son nom #- nodes de la scene : exports a assigner dans l'inspecteur (ils suivent un renommage ou un deplacement du node) @export_group("References") @export var visual_root : Node3D #@onready var godot_plush_skin : GodotPlushSkin = %GodotPlushSkin #@onready var particles_manager : ParticlesManager = %ParticlesManager @export var cam_holder : CameraHolder @export var state_machine : StateMachine #@onready var foot_step_audio = %FootStepAudio #@onready var impact_audio = %ImpactAudio #@onready var wave_audio = %WaveAudio @export var collision_shape_3d : CollisionShape3D #@onready var floor_check : RayCast3D = %FloorRaycast #- composants : ils s'enregistrent eux-memes (voir PlayerComponent), le joueur ne connait pas leur nom de node var player_input : InputManagementComponent var aim_targeting : AimTargeting #visee : mode superpose aux etats + ciblage (voir AimTargeting) var throw_resolver : ThrowResolver #trajectoire de la balle, atterrissage, envoi du pokemon, combat var state_hints : StateHints #affiche les hints de l'etat courant dans le HUD var can_crouch : bool = true var is_aiming : bool: #visee en cours (mode superpose aux etats, voir AimTargeting) get: return aim_targeting != null and aim_targeting.active #particles variables #@onready var movement_dust = %MovementDust #@onready var jump_particles = preload("../../PlayerCharacter/VFX/jump_particles_scene.tscn") #@onready var land_particles = preload("../../PlayerCharacter/VFX/land_particles_scene.tscn") var aim_target : AimTarget = null func _ready(): _validate_wiring() #set move variables, and value references move_speed = walk_speed move_accel = walk_accel move_deccel = walk_deccel jump_cooldown_ref = jump_cooldown nb_jumps_in_air_allowed_ref = nb_jumps_in_air_allowed coyote_jump_cooldown_ref = coyote_jump_cooldown dodge_cooldown_ref = dodge_cooldown #connect_footstep_audio_effect() func _validate_wiring() -> void: #un seul endroit qui dit ce qui manque, au lieu d'une erreur obscure sur un null plus tard var missing : Array[String] = [] if visual_root == null: missing.append("visual_root (export)") if cam_holder == null: missing.append("cam_holder (export)") if state_machine == null: missing.append("state_machine (export)") if collision_shape_3d == null: missing.append("collision_shape_3d (export)") if player_input == null: missing.append("un InputManagementComponent sous le joueur") if aim_targeting == null: missing.append("un AimTargeting sous le joueur") if throw_resolver == null: missing.append("un ThrowResolver sous le joueur") if state_hints == null: missing.append("un StateHints sous le joueur") if !missing.is_empty(): push_error("PlayerCharacter : dependances manquantes : " + ", ".join(PackedStringArray(missing))) func _process(delta: float): modify_model_orientation(delta) func _physics_process(delta : float): update_run_toggle() modify_physics_properties() tick_dodge_cooldown(delta) move_and_slide() func modify_model_orientation(delta : float): #manage the model rotation depending on the camera mode + char parameters var dir_target_angle : float #lock-on : le modele fait face a la cible if is_instance_valid(aim_target): var to_target : Vector3 = aim_target.global_position - global_position dir_target_angle = -Vector2(to_target.x, to_target.z).orthogonal().angle() visual_root.rotation.y = rotate_toward(visual_root.rotation.y, dir_target_angle, model_rot_speed * delta) return #follow mode (model must follow the camera rotation) #if the cam is in angled/aim mode if cam_holder.cam_aimed and follow_cam_pos_when_aimed: #get cam rotation on the y axis (+ PI to invert half circle, and be sure that the model is correctly oriented) dir_target_angle = (cam_holder.cam.global_rotation.y) + PI #rotate the model on the y axis visual_root.rotation.y = rotate_toward(visual_root.rotation.y, dir_target_angle, model_rot_speed * delta) #free mode (the model orientation is independant to the camera one) if (!cam_holder.cam_aimed or !follow_cam_pos_when_aimed) and move_dir != Vector2.ZERO: #get char move direction dir_target_angle = -move_dir.orthogonal().angle() #rotate the model on the y axis visual_root.rotation.y = rotate_toward(visual_root.rotation.y, dir_target_angle, model_rot_speed * delta) func modify_physics_properties(): last_frame_position = position #get play char position every frame last_frame_velocity = velocity #get play char velocity every frame was_on_floor = !is_on_floor() #get if play char is on floor or not if move_dir != Vector2.ZERO: last_input_dir = move_dir #memorize the last non-zero input direction, used as a dodge fallback func tick_dodge_cooldown(delta : float) -> void: if dodge_cooldown > 0.0: dodge_cooldown -= delta func gravity_apply(delta : float): #if play char goes up, apply jump gravity #otherwise, apply fall gravity if !is_on_floor(): if velocity.y >= 0.0: velocity.y -= jump_gravity * delta elif velocity.y < 0.0: velocity.y -= fall_gravity * delta func apply_jump_height(height : float) -> void: #source unique des formules de saut (aussi utilisee pour les montures) jump_velocity = ((2.0 * height) / jump_time_to_peak) * -1.0 jump_gravity = (2.0 * height) / (jump_time_to_peak * jump_time_to_peak) fall_gravity = (2.0 * height) / (jump_time_to_descent * jump_time_to_descent) func enter_mount_stats(speed : float, accel : float, deccel : float, mount_jump_height : float) -> void: #mount_jump_height <= 0 : on garde le saut du joueur if _base_ground_stats.is_empty(): _base_ground_stats = { "walk_speed": walk_speed, "walk_accel": walk_accel, "walk_deccel": walk_deccel, "run_speed": run_speed, "run_accel": run_accel, "run_deccel": run_deccel, } walk_speed = speed run_speed = speed walk_accel = accel run_accel = accel walk_deccel = deccel run_deccel = deccel if mount_jump_height > 0.0: apply_jump_height(mount_jump_height) #l'etat courant lit move_* sans repasser par enter() move_speed = speed move_accel = accel move_deccel = deccel func exit_mount_stats() -> void: if _base_ground_stats.is_empty(): return walk_speed = _base_ground_stats["walk_speed"] walk_accel = _base_ground_stats["walk_accel"] walk_deccel = _base_ground_stats["walk_deccel"] run_speed = _base_ground_stats["run_speed"] run_accel = _base_ground_stats["run_accel"] run_deccel = _base_ground_stats["run_deccel"] _base_ground_stats.clear() apply_jump_height(jump_height) #jump_height n'est jamais modifie : on revient aux valeurs du joueur move_speed = walk_speed move_accel = walk_accel move_deccel = walk_deccel func squash_and_strech(value : float, timing : float): #create a tween that simulate a compression of the model (squash and strech ones) #maily used to accentuate game feel/juice #call the squash_and_strech function of the model (it's this function that actually squash and strech the model) var sasTween : Tween = create_tween() sasTween.set_ease(Tween.EASE_OUT) #sasTween.tween_property(godot_plush_skin, "squash_and_stretch", value, timing) #sasTween.tween_property(godot_plush_skin, "squash_and_stretch", 1.0, timing * 1.8) #func connect_footstep_audio_effect() -> void: #set play char model audios effects #godot_plush_skin.footstep.connect(func(intensity : float = 1.0): #foot_step_audio.volume_db = linear_to_db(intensity) #foot_step_audio.play() #) func update_run_toggle() -> void: if continious_run: if player_input.run_just_pressed(): locomotion = Locomotion.WALK if is_running() else Locomotion.RUN else: locomotion = Locomotion.RUN if player_input.run_pressed() else Locomotion.WALK func is_running() -> bool: return locomotion == Locomotion.RUN func get_locomotion_state() -> String: #etat au sol correspondant a la derniere allure (voir State.request_locomotion) return States.RUN if is_running() else States.WALK