From 3f8c100f1796b542add1d0ba55a074af71137d0e Mon Sep 17 00:00:00 2001 From: TheThomaas Date: Wed, 23 Sep 2026 17:56:57 +0200 Subject: [PATCH] Update Camera and add RunState --- game/player/Camera/CameraHolder.gd | 5 +- game/player/PlayerCharacter.tscn | 3 +- game/player/StateMachine/AimState.gd | 55 +++++++++++------- game/player/StateMachine/IdleState.gd | 9 ++- game/player/StateMachine/JumpState.gd | 20 +++---- game/player/StateMachine/RunState.gd | 74 ++++++++++++++++++++++++ game/player/StateMachine/RunState.gd.uid | 1 + game/player/StateMachine/WalkState.gd | 3 +- game/player/actions/AimTarget.gd | 49 +++++++++++++++- project.godot | 15 ++--- 10 files changed, 184 insertions(+), 50 deletions(-) create mode 100644 game/player/StateMachine/RunState.gd create mode 100644 game/player/StateMachine/RunState.gd.uid diff --git a/game/player/Camera/CameraHolder.gd b/game/player/Camera/CameraHolder.gd index 9ece542..2ff41af 100644 --- a/game/player/Camera/CameraHolder.gd +++ b/game/player/Camera/CameraHolder.gd @@ -61,7 +61,7 @@ func _input(event: InputEvent) -> void: #change cam mode (default, aim) #if event.is_action_pressed(aim_cam_action): #cam_aimed = !cam_aimed - + if event is InputEventMouseMotion: if has_lock_target(): return var viewport_transform: Transform2D = get_tree().root.get_final_transform() @@ -129,3 +129,6 @@ func lock_on_handling(delta : float) -> void: var weight : float = clampf(lock_on_speed * delta, 0.0, 1.0) rotation.y = lerp_angle(rotation.y, target_yaw, weight) rotation.x = lerp_angle(rotation.x, target_pitch, weight) + +func set_lock_target(t : Node3D) -> void: + lock_target = t diff --git a/game/player/PlayerCharacter.tscn b/game/player/PlayerCharacter.tscn index 1c2f513..d24f1b6 100644 --- a/game/player/PlayerCharacter.tscn +++ b/game/player/PlayerCharacter.tscn @@ -58,7 +58,6 @@ mesh = SubResource("CapsuleMesh_3agn1") unique_name_in_owner = true transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, -0.009075999, 1.6803148, 0.01983509) fov_val = 30.0 -aim_cam_pos = Vector3(1.4, 0.9, 0) [node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1800756522] unique_name_in_owner = true @@ -82,5 +81,7 @@ script = ExtResource("6_lwgph") [node name="AimState" type="Node" parent="StateMachine" unique_id=296560188] script = ExtResource("7_kxr4l") +[node name="RunState" type="Node" parent="StateMachine" unique_id=1345421775] + [node name="InputManagementComponent" type="Node" parent="." unique_id=1981951371] script = ExtResource("2_r5dqo") diff --git a/game/player/StateMachine/AimState.gd b/game/player/StateMachine/AimState.gd index 8248c50..b0a4bc1 100644 --- a/game/player/StateMachine/AimState.gd +++ b/game/player/StateMachine/AimState.gd @@ -7,15 +7,14 @@ var state_name : String = "Aim" var play_char : CharacterBody3D @export var aim_action : StringName = &"player_aim_camera" -@export var next_target_action : StringName = &"player_aim_next_target" -@export var prev_target_action : StringName = &"player_aim_prev_target" +@export var switch_target_action : StringName = &"player_aim_switch_target" @export var unlock_action : StringName = &"player_aim_unlock" @export_group("Targeting") @export var throw_range : float = 20.0 -@export var max_aim_angle : float = 35.0 #angle max (deg) entre l'axe camera et la cible pour etre candidate +@export var max_aim_angle : float = 35.0 #angle max (deg) entre l'axe camera et une cible pour qu'elle soit candidate @export var auto_lock_angle : float = 12.0 #si une cible passe sous cet angle, on se verrouille dessus -@export var lose_range_margin : float = 1.25 #marge avant de perdre une cible deja verrouillee +@export var lose_range_margin : float = 1.25 #marge de distance avant de perdre une cible deja verrouillee @export var check_line_of_sight : bool = true @export_flags_3d_physics var line_of_sight_mask : int = 1 @@ -23,13 +22,16 @@ var play_char : CharacterBody3D @export var aim_move_speed : float = 3.0 @export var aim_move_accel : float = 8.0 +var auto_lock_blocked : bool = false #true apres un deverrouillage manuel, tant que la cible est encore au centre + func enter(play_char_ref : CharacterBody3D) -> void: play_char = play_char_ref + auto_lock_blocked = false verifications() func verifications() -> void: play_char.floor_snap_length = 1.0 - play_char.cam_holder.cam_aimed = true #adapte si ton CameraHolder gere ca autrement + play_char.cam_holder.cam_aimed = true func exit() -> void: release_target() @@ -54,11 +56,10 @@ func input_management() -> bool: return true if Input.is_action_just_pressed(unlock_action): + auto_lock_blocked = true release_target() - elif Input.is_action_just_pressed(next_target_action): - cycle_target(1) - elif Input.is_action_just_pressed(prev_target_action): - cycle_target(-1) + elif Input.is_action_just_pressed(switch_target_action): + switch_target() return false func move(delta : float) -> void: @@ -76,9 +77,10 @@ func move(delta : float) -> void: func update_target() -> void: var current : AimTarget = play_char.aim_target - #verrouillage existant : on le garde tant qu'il est valide + #verrouillage existant : on le garde tant que distance et ligne de vue sont valides + #(l'angle avec la camera ne compte pas, le joueur peut deplacer la camera librement) if is_instance_valid(current): - if is_target_valid(current, lose_range_margin): return + if is_target_valid(current, lose_range_margin, false): return release_target() #visee libre : auto-lock sur la meilleure cible proche du centre de la camera @@ -89,19 +91,23 @@ func update_target() -> void: if angle < best_angle: best_angle = angle best = t - if best: set_target(best) + + if best == null: + auto_lock_blocked = false #le joueur a ecarte la camera : l'auto-lock est de nouveau permis + elif !auto_lock_blocked: + set_target(best) func get_candidates() -> Array[AimTarget]: var result : Array[AimTarget] = [] for node in get_tree().get_nodes_in_group("aim_targets"): var t := node as AimTarget - if t and is_target_valid(t, 1.0): result.append(t) + if t and is_target_valid(t, 1.0, true): result.append(t) return result -func is_target_valid(t : AimTarget, range_mult : float) -> bool: +func is_target_valid(t : AimTarget, range_mult : float, check_angle : bool) -> bool: if !t.enabled: return false if play_char.global_position.distance_to(t.global_position) > throw_range * range_mult: return false - if get_angle_to_camera(t) > max_aim_angle * range_mult: return false + if check_angle and get_angle_to_camera(t) > max_aim_angle: return false if check_line_of_sight and !has_line_of_sight(t): return false return true @@ -117,17 +123,22 @@ func has_line_of_sight(t : AimTarget) -> bool: #pas d'obstacle, ou l'obstacle est le proprietaire de la cible lui-meme return hit.is_empty() or hit["collider"].is_ancestor_of(t) -func cycle_target(direction : int) -> void: - #trie les cibles de gauche a droite (par rapport a la camera) et passe a la suivante/precedente +func switch_target() -> void: + #passe a la cible suivante, triee de gauche a droite par rapport a la camera (boucle) var cam : Camera3D = play_char.cam_holder.cam var forward : Vector3 = -cam.global_basis.z var list : Array[AimTarget] = get_candidates() - if list.is_empty(): return + + #la cible actuelle reste dans la liste meme si la camera est decalee par rapport a elle + var current : AimTarget = play_char.aim_target + if is_instance_valid(current) and !list.has(current): list.append(current) + if list.size() < 2: return + list.sort_custom(func(a : AimTarget, b : AimTarget) -> bool: return signed_angle(forward, a, cam) < signed_angle(forward, b, cam)) - var idx : int = list.find(play_char.aim_target) - idx = 0 if idx == -1 else posmod(idx + direction, list.size()) + var idx : int = list.find(current) + idx = 0 if idx == -1 else (idx + 1) % list.size() set_target(list[idx]) func signed_angle(forward : Vector3, t : AimTarget, cam : Camera3D) -> float: @@ -138,11 +149,11 @@ func set_target(t : AimTarget) -> void: if play_char.aim_target == t: return release_target() play_char.aim_target = t - play_char.cam_holder.lock_target = t + play_char.cam_holder.set_lock_target(t) t.set_locked(true) func release_target() -> void: if !play_char: return if is_instance_valid(play_char.aim_target): play_char.aim_target.set_locked(false) play_char.aim_target = null - play_char.cam_holder.lock_target = null + play_char.cam_holder.set_lock_target(null) diff --git a/game/player/StateMachine/IdleState.gd b/game/player/StateMachine/IdleState.gd index 35d30f4..42a2bae 100644 --- a/game/player/StateMachine/IdleState.gd +++ b/game/player/StateMachine/IdleState.gd @@ -51,9 +51,12 @@ func input_management() -> void: if Input.is_action_just_pressed(play_char.aim_cam_action): transitioned.emit(self, "AimState") - if Input.is_action_just_pressed(play_char.run_action): - if play_char.walk_or_run == "WalkState": play_char.walk_or_run = "RunState" - elif play_char.walk_or_run == "RunState": play_char.walk_or_run = "WalkState" + if play_char.continious_run: + if Input.is_action_just_pressed(play_char.run_action): + if play_char.walk_or_run == "WalkState": play_char.walk_or_run = "RunState" + elif play_char.walk_or_run == "RunState": play_char.walk_or_run = "WalkState" + else: + play_char.walk_or_run = "RunState" if Input.is_action_pressed(play_char.run_action) else "WalkState" func move(delta : float) -> void: #manage the character movement diff --git a/game/player/StateMachine/JumpState.gd b/game/player/StateMachine/JumpState.gd index 240ebbf..4375a73 100644 --- a/game/player/StateMachine/JumpState.gd +++ b/game/player/StateMachine/JumpState.gd @@ -79,16 +79,16 @@ func jump() -> void: var can_jump : bool = false #jump condition #in air jump - #if !play_char.is_on_floor(): - #if play_char.coyote_jump_on: - #play_char.jump_cooldown = play_char.jump_cooldown_ref - #play_char.coyote_jump_cooldown = -1.0 #so that the character cannot immediately make another coyote jump - #play_char.coyote_jump_on = false - #can_jump = true - #elif play_char.nb_jumps_in_air_allowed > 0: - #play_char.nb_jumps_in_air_allowed -= 1 - #play_char.jump_cooldown = play_char.jump_cooldown_ref - #can_jump = true + if !play_char.is_on_floor(): + if play_char.coyote_jump_on: + play_char.jump_cooldown = play_char.jump_cooldown_ref + play_char.coyote_jump_cooldown = -1.0 #so that the character cannot immediately make another coyote jump + play_char.coyote_jump_on = false + can_jump = true + elif play_char.nb_jumps_in_air_allowed > 0: + play_char.nb_jumps_in_air_allowed -= 1 + play_char.jump_cooldown = play_char.jump_cooldown_ref + can_jump = true #on floor jump if play_char.is_on_floor(): diff --git a/game/player/StateMachine/RunState.gd b/game/player/StateMachine/RunState.gd new file mode 100644 index 0000000..27acf2b --- /dev/null +++ b/game/player/StateMachine/RunState.gd @@ -0,0 +1,74 @@ +extends State + +class_name RunState + +var state_name : String = "Run" + +var play_char : CharacterBody3D + +func enter(play_char_ref : CharacterBody3D) -> void: + play_char = play_char_ref + + verifications() + +func verifications() -> void: + #play_char.godot_plush_skin.set_state("run") + play_char.walk_or_run = "RunState" #utilise par JumpState/InairState pour choisir les courbes en l'air + play_char.move_speed = play_char.run_speed + play_char.move_accel = play_char.run_accel + play_char.move_deccel = play_char.run_deccel + + play_char.floor_snap_length = 1.0 + if play_char.jump_cooldown > 0.0: play_char.jump_cooldown = -1.0 + if play_char.nb_jumps_in_air_allowed < play_char.nb_jumps_in_air_allowed_ref: play_char.nb_jumps_in_air_allowed = play_char.nb_jumps_in_air_allowed_ref + if play_char.coyote_jump_cooldown < play_char.coyote_jump_cooldown_ref: play_char.coyote_jump_cooldown = play_char.coyote_jump_cooldown_ref + if play_char.has_cut_jump: play_char.has_cut_jump = false + +func physics_update(delta : float) -> void: + applies() + + play_char.gravity_apply(delta) + + input_management() + + move(delta) + +func applies() -> void: + if !play_char.is_on_floor() and !play_char.is_on_wall(): + if play_char.velocity.y < 0.0: + transitioned.emit(self, "InairState") + + if play_char.is_on_floor(): + if play_char.jump_buff_on: + #apply jump buffering + play_char.buffered_jump = true + play_char.jump_buff_on = false + transitioned.emit(self, "JumpState") + +func input_management() -> void: + if Input.is_action_pressed(play_char.jump_action) if play_char.auto_jump else Input.is_action_just_pressed(play_char.jump_action): + transitioned.emit(self, "JumpState") + + if play_char.continious_run: + #course continue : un nouvel appui sur la touche repasse a la marche + if Input.is_action_just_pressed(play_char.run_action): + stop_running() + else: + #course maintenue : on court tant que la touche est enfoncee + if !Input.is_action_pressed(play_char.run_action): + stop_running() + +func stop_running() -> void: + play_char.walk_or_run = "WalkState" + transitioned.emit(self, "WalkState") + +func move(delta : float) -> void: + play_char.move_dir = Input.get_vector(play_char.move_left_action, play_char.move_right_action, play_char.move_forward_action, play_char.move_backward_action).rotated(-play_char.cam_holder.global_rotation.y) + + if play_char.move_dir and play_char.is_on_floor(): + #apply smooth move + play_char.velocity.x = lerp(play_char.velocity.x, play_char.move_dir.x * play_char.move_speed, play_char.move_accel * delta) + play_char.velocity.z = lerp(play_char.velocity.z, play_char.move_dir.y * play_char.move_speed, play_char.move_accel * delta) + else: + if !play_char.continious_run: play_char.walk_or_run = "WalkState" + transitioned.emit(self, "IdleState") diff --git a/game/player/StateMachine/RunState.gd.uid b/game/player/StateMachine/RunState.gd.uid new file mode 100644 index 0000000..e5237ef --- /dev/null +++ b/game/player/StateMachine/RunState.gd.uid @@ -0,0 +1 @@ +uid://dpcfffs3vt5co diff --git a/game/player/StateMachine/WalkState.gd b/game/player/StateMachine/WalkState.gd index 014df71..eaac5f3 100644 --- a/game/player/StateMachine/WalkState.gd +++ b/game/player/StateMachine/WalkState.gd @@ -52,7 +52,8 @@ func input_management() -> void: if Input.is_action_just_pressed(play_char.aim_cam_action): transitioned.emit(self, "AimState") - if Input.is_action_just_pressed(play_char.run_action): + var wants_run : bool = Input.is_action_just_pressed(play_char.run_action) if play_char.continious_run else Input.is_action_pressed(play_char.run_action) + if wants_run: play_char.walk_or_run = "RunState" transitioned.emit(self, "RunState") diff --git a/game/player/actions/AimTarget.gd b/game/player/actions/AimTarget.gd index 5bfaa77..74f5175 100644 --- a/game/player/actions/AimTarget.gd +++ b/game/player/actions/AimTarget.gd @@ -5,15 +5,60 @@ class_name AimTarget signal locked_changed(is_locked : bool) @export var enabled : bool = true -@export var priority : float = 0.0 #plus c'est haut, plus la cible est favorisée -@export var indicator : Node3D #optionnel : reticule/sprite affiché quand la cible est verrouillée +@export var priority : float = 0.0 #plus c'est haut, plus la cible est favorisee + +@export_group("Indicator") +@export var indicator : Node3D #optionnel : ton propre visuel (remplace le losange par defaut) +@export var show_default_diamond : bool = true +@export var diamond_size : float = 0.3 +@export var diamond_color : Color = Color(1.0, 0.85, 0.2, 0.95) +@export var diamond_offset : Vector3 = Vector3.ZERO #decalage par rapport a la node (ex. (0, 0.5, 0) pour le mettre au-dessus) var is_locked : bool = false func _ready() -> void: add_to_group("aim_targets") + + if indicator == null and show_default_diamond: + indicator = create_diamond() + add_child(indicator) + if indicator: indicator.visible = false +func create_diamond() -> MeshInstance3D: + #carre incline a 45 degres, oriente vers la camera + #(la rotation est appliquee directement aux sommets, car le billboard ignore la rotation de la node) + var half : float = diamond_size * 0.3 + var corners : Array[Vector3] = [ + Vector3(-half, -half, 0.0), + Vector3(half, -half, 0.0), + Vector3(half, half, 0.0), + Vector3(-half, half, 0.0), + ] + for i in corners.size(): + corners[i] = corners[i].rotated(Vector3.BACK, deg_to_rad(45.0)) + + var st := SurfaceTool.new() + st.begin(Mesh.PRIMITIVE_TRIANGLES) + for i in [0, 1, 2, 0, 2, 3]: + st.add_vertex(corners[i]) + + var mat := StandardMaterial3D.new() + mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED + mat.albedo_color = diamond_color + mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA + mat.billboard_mode = BaseMaterial3D.BILLBOARD_ENABLED + mat.cull_mode = BaseMaterial3D.CULL_DISABLED + mat.no_depth_test = true #reste visible meme a travers le modele de la cible + mat.render_priority = 100 + + var mesh_instance := MeshInstance3D.new() + mesh_instance.mesh = st.commit() + mesh_instance.material_override = mat + mesh_instance.position = diamond_offset + mesh_instance.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF + return mesh_instance + func set_locked(value : bool) -> void: if is_locked == value: return is_locked = value diff --git a/project.godot b/project.godot index 70a11c6..74cf082 100644 --- a/project.godot +++ b/project.godot @@ -144,21 +144,16 @@ player_crouch={ "events": [Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":7,"pressure":0.0,"pressed":true,"script":null) ] } -player_aim_next_target={ -"deadzone": 0.2, -"events": [Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":14,"pressure":0.0,"pressed":true,"script":null) -] -} -player_aim_prev_target={ -"deadzone": 0.2, -"events": [Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":13,"pressure":0.0,"pressed":true,"script":null) -] -} player_aim_unlock={ "deadzone": 0.2, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":81,"key_label":0,"unicode":113,"location":0,"echo":false,"script":null) ] } +player_aim_switch_target={ +"deadzone": 0.2, +"events": [Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":13,"pressure":0.0,"pressed":true,"script":null) +] +} [physics]