Tales-of-PKMN/game/player/aim/aim_targeting.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

180 lines
7.3 KiB
GDScript

extends PlayerComponent
class_name AimTargeting
#la visee est un MODE superpose aux etats (comme la posture accroupie), plus un etat a part :
# - elle demarre quand un etat au sol voit l'appui sur la visee (begin_aim) ou quand la visee est maintenue a la fin d'une roulade / d'un lancer
# - elle dure tant que le bouton est maintenu : fin des qu'il est relache, ou en l'air. Pendant un lancer, elle attend la fin du lancer
# - les etats continuent leur travail (marcher, rouler, lancer) : GroundedState ralentit le deplacement et bloque le saut
#le composant gere aussi le ciblage : choix de la cible (auto-lock, changement, deverrouillage), camera de visee, reticule et hints.
#GroundedState et ThrowState appellent tick() chaque frame tant que la visee est active.
#composant a ajouter sous le PlayerCharacter (le nom du node est libre, voir PlayerComponent).
@export var throw_range : float = 20.0 #portee du lancer : sert aussi a ThrowResolver pour le point vise sans cible
@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 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
@export_group("Movement")
@export var aim_move_speed : float = 3.0 #vitesse de deplacement pendant la visee (remplace celle de la marche / course / crouch)
@export var aim_move_accel : float = 8.0
@export_group("Input")
@export var unlock_action : StringName = &"player_aim_unlock"
@export_group("Hints")
@export var switch_hint : Hint #switch_target.tres
const TARGET_GROUP : String = "aim_targets" #groupe des AimTarget (aussi utilise par ThrowResolver)
var active : bool = false #true tant que la visee est en cours
var auto_lock_blocked : bool = false #true apres un deverrouillage manuel, tant que la cible est encore au centre
var switch_target_action : StringName: #l'action vient du Hint : une seule definition
get: return switch_hint.action
func register_with_player() -> void:
player.aim_targeting = self
func _ready() -> void:
process_physics_priority = -5 #avant la StateMachine : les etats voient une visee deja a jour (fin de visee, en l'air...)
func _exit_tree() -> void:
Hints.clear_hints(self)
#---------- mode visee ----------
func _physics_process(_delta : float) -> void:
if !active: return
var s : State = player.state_machine.curr_state
if s is AirborneState:
end_aim() #pas de visee en l'air : il faut reappuyer apres l'atterrissage
elif !player.player_input.aim_held() and !(s is ThrowState):
end_aim() #bouton relache (pendant un lancer, on attend la fin du lancer : la cible verrouillee compte)
func begin_aim() -> void:
if active: return
active = true
auto_lock_blocked = false
player.cam_holder.cam_aimed = true
var list : Array[Hint] = []
if switch_hint: list.append(switch_hint)
Hints.show_hints(self, list)
func end_aim() -> void:
if !active: return
active = false
release_target()
player.cam_holder.cam_aimed = false
AimReticle.hide_instance()
Hints.clear_hints(self)
func tick() -> void:
#appele chaque frame par les etats qui acceptent la visee (GroundedState, ThrowState pendant la charge)
if Input.is_action_just_pressed(unlock_action):
unlock() #deverrouillage manuel
elif switch_hint and Input.is_action_just_pressed(switch_target_action):
switch_target()
update_target()
update_reticle()
func unlock() -> void:
#deverrouillage manuel : pas de re-verrouillage automatique tant que la cible reste au centre
auto_lock_blocked = true
release_target()
func update_reticle() -> void:
#reticule libre au centre de l'ecran, seulement quand aucune cible n'est verrouillee (appele par tick(), et par ThrowState quand on charge un lancer sans viser)
if is_instance_valid(player.aim_target):
AimReticle.hide_instance() #cible verrouillee : son propre indicateur suffit
else:
AimReticle.show_instance(self)
#---------- ciblage ----------
func update_target() -> void:
var current : AimTarget = player.aim_target
#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, false): return
release_target()
#visee libre : auto-lock sur la meilleure cible proche du centre de la camera
var best : AimTarget = null
var best_angle : float = auto_lock_angle
for t in get_candidates():
var angle : float = get_angle_to_camera(t) - t.priority
if angle < best_angle:
best_angle = angle
best = t
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(TARGET_GROUP):
var t := node as AimTarget
if t and is_target_valid(t, 1.0, true): result.append(t)
return result
func is_target_valid(t : AimTarget, range_mult : float, check_angle : bool) -> bool:
if !t.enabled: return false
if player.global_position.distance_to(t.global_position) > throw_range * 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
func get_angle_to_camera(t : AimTarget) -> float:
var cam : Camera3D = player.cam_holder.cam
var to_t : Vector3 = t.global_position - cam.global_position
return rad_to_deg((-cam.global_basis.z).angle_to(to_t))
func has_line_of_sight(t : AimTarget) -> bool:
var query := PhysicsRayQueryParameters3D.create(
player.cam_holder.cam.global_position, t.global_position, line_of_sight_mask, [player.get_rid()])
var hit : Dictionary = player.get_world_3d().direct_space_state.intersect_ray(query)
#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 switch_target() -> void:
#passe a la cible suivante, triee de gauche a droite par rapport a la camera (boucle)
var cam : Camera3D = player.cam_holder.cam
var forward : Vector3 = -cam.global_basis.z
var list : Array[AimTarget] = get_candidates()
#la cible actuelle reste dans la liste meme si la camera est decalee par rapport a elle
var current : AimTarget = player.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(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:
var to_t : Vector3 = t.global_position - cam.global_position
return Vector2(forward.x, forward.z).angle_to(Vector2(to_t.x, to_t.z))
func set_target(t : AimTarget) -> void:
if player.aim_target == t: return
release_target()
player.aim_target = t
player.cam_holder.set_lock_target(t)
t.set_locked(true)
func release_target() -> void:
if !player: return
if is_instance_valid(player.aim_target): player.aim_target.set_locked(false)
player.aim_target = null
player.cam_holder.set_lock_target(null)