Tales-of-PKMN/game/player/player_character.gd
TheThomaas 250976d170 Squashed commit of the following:
commit 8e7da96bfcd52b230feaf40fbc6d4a42b80c5723
Author: TheThomaas <thethomaas@protonmail.ch>
Date:   Sat Oct 17 18:55:14 2026 +0200

    Make state machine more reusable

commit 37da7248b8c33ce95cff7ef9332c5ccf6e3cd418
Author: TheThomaas <thethomaas@protonmail.ch>
Date:   Sat Oct 17 18:44:34 2026 +0200

    Change aim state into flag

commit fbb3a84a12e2cabff9b36390da7483b8de7e9379
Author: TheThomaas <thethomaas@protonmail.ch>
Date:   Sat Oct 17 18:33:08 2026 +0200

    Replace fixed references with ui references

commit 913955b13e9980780ac88761845d9e1f165f0d0e
Author: TheThomaas <thethomaas@protonmail.ch>
Date:   Fri Oct 16 13:32:21 2026 +0200

    Update camera action handling

commit 4ce0ac13ffea473cf62c2fefd1571e9df1fbbcaa
Author: TheThomaas <thethomaas@protonmail.ch>
Date:   Fri Oct 16 13:04:39 2026 +0200

    Update mounting system

commit e410eca38760955b2866eecd6ac0281e2041e76d
Author: TheThomaas <thethomaas@protonmail.ch>
Date:   Fri Oct 16 12:57:25 2026 +0200

    Update aiming system

commit 4c788df485ccbff5f91fd51d9f480e495f11da5f
Author: TheThomaas <thethomaas@protonmail.ch>
Date:   Thu Oct 15 21:50:26 2026 +0200

    Centralize states names into constants

commit 6059479c87d749e986a48adb520ef33121104da5
Author: TheThomaas <thethomaas@protonmail.ch>
Date:   Thu Oct 15 21:25:32 2026 +0200

    Refator Crouch state into flag

commit 08faec70e770042d36eed6f173a63f3aac1f67c6
Author: TheThomaas <thethomaas@protonmail.ch>
Date:   Wed Oct 14 10:19:35 2026 +0200

    Update player input management and state machine

commit 11a0aecc58c50c5862a03b4fd0b55e8db024031f
Author: TheThomaas <thethomaas@protonmail.ch>
Date:   Wed Oct 14 10:17:06 2026 +0200

    Delete charizard.tres

commit 399eac2b6c64f242a1c2e12360d811127f87681c
Author: TheThomaas <thethomaas@protonmail.ch>
Date:   Tue Oct 13 18:20:45 2026 +0200

    Remove unused MountedState

commit 42a3668c52fe96a7509a74290a94fc5a8084b713
Author: TheThomaas <thethomaas@protonmail.ch>
Date:   Tue Oct 13 18:18:08 2026 +0200

    Group grounded states under common class
2026-10-17 18:56:13 +02:00

258 lines
11 KiB
GDScript

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