Compare commits

...

3 commits

Author SHA1 Message Date
TheThomaas fc5afc8ca5 Update hints system 2026-10-13 09:21:01 +02:00
TheThomaas 65ccb1905c Allow Null hints 2026-10-13 09:03:55 +02:00
TheThomaas e115200194 Add ui hints 2026-10-13 08:55:12 +02:00
14 changed files with 152 additions and 66 deletions

View file

@ -20,6 +20,10 @@ signal candidate_changed(sent : SentPokemon) #pokemon montable a portee (null =
@export var swallow_jump_on_mount : bool = true #si A sert aussi a sauter : l'appui qui monte ne declenche pas de saut
@export var camera_follow_speed : float = 8.0 #vitesse a laquelle la camera monte a hauteur de selle
@export_group("Hints HUD")
@export var mount_hint_text : String = "Grimper"
@export var hint_priority : int = 10
const GROUNDED_STATES : Array[String] = ["Idle", "Walk", "Run"] #monter / descendre n'est possible que depuis ces etats
@onready var player : PlayerCharacter = get_parent()
@ -51,6 +55,9 @@ func _ready() -> void:
key.physical_keycode = KEY_F
InputMap.action_add_event(dismount_action, key))
func _exit_tree() -> void:
Hints.clear_context(self)
func _ensure_action(action : StringName, add_events : Callable) -> void:
if InputMap.has_action(action): return
InputMap.add_action(action)
@ -61,7 +68,10 @@ func is_mounted() -> bool:
func _physics_process(_delta : float) -> void:
if mount != null:
if Input.is_action_just_pressed(dismount_action) and _can_switch_now():
var can_dismount : bool = _can_switch_now()
if can_dismount: Hints.set_context(self, dismount_action, "", hint_priority)
else: Hints.clear_context(self)
if Input.is_action_just_pressed(dismount_action) and can_dismount:
dismount()
return
@ -123,6 +133,8 @@ func _set_candidate(s : SentPokemon) -> void:
if s == candidate: return
candidate = s
candidate_changed.emit(s)
if s != null: Hints.set_context(self, mount_action, mount_hint_text, hint_priority)
else: Hints.clear_context(self)
#---------- monter / descendre ----------
@ -133,6 +145,7 @@ func mount_up(s : SentPokemon) -> void:
s.rider = player
s.recalled.connect(_on_mount_recalled)
_set_candidate(null)
Hints.set_context(self, dismount_action, "", hint_priority)
player.is_mounted = true
player.enter_mount_stats(profile.mount_speed, profile.mount_accel, profile.mount_deccel,
@ -151,6 +164,7 @@ func dismount() -> void:
player.is_mounted = false
player.exit_mount_stats()
Hints.clear_context(self)
_release_blocked_states_next_frame()
visual_root.position -= _seat_applied

View file

@ -81,12 +81,15 @@ initial_state = NodePath("IdleState")
[node name="IdleState" type="Node" parent="StateMachine" unique_id=1417869633]
script = ExtResource("4_38lxl")
hints = Array[StringName]([&"player_jump"])
[node name="WalkState" type="Node" parent="StateMachine" unique_id=1524475809]
script = ExtResource("4_lg056")
hints = Array[StringName]([&"player_jump"])
[node name="JumpState" type="Node" parent="StateMachine" unique_id=711160985]
script = ExtResource("6_lwgph")
hints = Array[StringName]([&"player_jump"])
[node name="AimState" type="Node" parent="StateMachine" unique_id=296560188]
script = ExtResource("7_kxr4l")
@ -96,6 +99,7 @@ script = ExtResource("8_k0r6x")
[node name="RunState" type="Node" parent="StateMachine" unique_id=1345421775]
script = ExtResource("8_j4mf1")
hints = Array[StringName]([&"player_jump"])
[node name="DodgeState" type="Node" parent="StateMachine" unique_id=232179400]
script = ExtResource("9_6abew")

View file

@ -28,7 +28,11 @@ var auto_lock_blocked : bool = false #true apres un deverrouillage manuel, tant
var dodging_from_aim : bool = false #true quand on quitte Aim pour rouler, pour ne pas relacher la cible verrouillee
var came_from_crouch : bool = false #true si Aim a ete declenche depuis CrouchState, pour y retourner en sortant
var keep_aim_on_exit : bool = false #true quand on part vers ThrowState : camera et verrouillage restent en place
var charging_throw : bool = false #true quand on arrive depuis ThrowState en pleine charge : RT est encore maintenue
var charging_throw : bool = false: #true quand on arrive depuis ThrowState en pleine charge : RT est encore maintenue
set(value):
if charging_throw == value: return
charging_throw = value
hints_changed.emit()
func _ready() -> void:
if InputMap.has_action(throw_action): return
@ -234,3 +238,8 @@ func release_target() -> void:
if is_instance_valid(play_char.aim_target): play_char.aim_target.set_locked(false)
play_char.aim_target = null
play_char.cam_holder.set_lock_target(null)
func get_hints() -> Array:
var list : Array[StringName] = [switch_target_action]
if charging_throw: list.append(cancel_action)
return list

View file

@ -110,4 +110,3 @@ func jump() -> void:
play_char.squash_and_strech(1.12, 0.1)
#play_char.particles_manager.display_particles(play_char.jump_particles, play_char)

View file

@ -14,3 +14,6 @@ func enter(play_char_ref : CharacterBody3D) -> void:
play_char.move_deccel = profile.mount_deccel
# instancier profile.mount_scene_override ou profile.species.model_scene,
# le positionner, y attacher play_char selon seat_offset...
func get_hints() -> Array:
return [{"action": &"player_dismount", "text": "Descendre"}] # remplace par le nom réel de ton action

View file

@ -3,6 +3,9 @@ extends Node
class_name State
signal transitioned #it's normal if this signal isn't used in this script, because it's purpose is to be used in the scripts that extends from the State class
signal hints_changed #à émettre quand get_hints() change pendant que l'état est actif
@export var hints : Array[StringName] = [] #hints simples, réglables dans l'inspecteur
func enter(_char_reference : CharacterBody3D) -> void:
#enter state
@ -19,3 +22,6 @@ func update(_delta : float) -> void:
func physics_update(_delta : float) -> void:
#physics_process update
pass
func get_hints() -> Array:
return hints #à surcharger seulement si les hints dépendent d'une condition

View file

@ -20,6 +20,7 @@ func _ready() -> void:
if child is State:
states[child.name.to_lower()] = child
child.transitioned.connect(on_state_child_transition)
child.hints_changed.connect(_on_hints_changed.bind(child))
#if initial state, transition to it
if initial_state:
@ -27,6 +28,7 @@ func _ready() -> void:
initial_state.enter(char_ref)
curr_state = initial_state
curr_state_name = curr_state.state_name
publish_hints()
func _process(delta : float) -> void:
if curr_state: curr_state.update(delta)
@ -73,7 +75,17 @@ func on_state_child_transition(state : State, new_state_name : String) -> void:
curr_state = new_state
curr_state_name = curr_state.state_name
publish_hints()
func set_state_blocked(state_name : String, blocked : bool) -> void:
if blocked: blocked_states[state_name.to_lower()] = true
else: blocked_states.erase(state_name.to_lower())
func _on_hints_changed(state : State) -> void:
if state == curr_state: publish_hints()
func publish_hints() -> void:
if !curr_state: return
var actions = curr_state.get_hints()
if actions != null: Hints.set_state_hints(actions) #null = on garde les hints précédents (si tu as gardé cette option pour Dodge)

View file

@ -294,3 +294,8 @@ func _release_aim() -> void:
play_char.aim_target = null
play_char.cam_holder.set_lock_target(null)
play_char.cam_holder.cam_aimed = false
func get_hints() -> Array:
var list : Array[StringName] = []
if !_thrown: list.append(cancel_action) #plus d'annulation possible une fois la balle partie
return list

58
game/ui/hints/hints.gd Normal file
View file

@ -0,0 +1,58 @@
extends Node
signal changed
#catalogue unique : action -> libellé affiché
const LABELS : Dictionary = {
&"player_jump": "Sauter",
&"player_dismount": "Descendre",
&"player_aim_switch_target": "Changer de cible",
&"player_aim_unlock": "Libérer la cible",
&"player_throw_cancel": "Annuler le lancer",
}
var _state_actions : Array = [] # [action, ...]
var _context_hints : Dictionary = {} # clé (objet ou StringName) -> {action, text, priority}
func label_for(action : StringName) -> String:
return LABELS.get(action, String(action)) #sans entrée : le nom de l'action, bien visible
#---------- hints d'état (appelés par la StateMachine) ----------
func set_state_hints(actions : Array) -> void:
_state_actions = actions
changed.emit()
#---------- hints de contexte (monture, PNJ, etc.) ----------
#text vide : le libellé vient du catalogue. À renseigner quand le sens dépend du contexte (Grimper / Parler sur la même action)
func set_context(source : Variant, action : StringName, text : String = "", priority : int = 0) -> void:
var h : Dictionary = {"action": action, "text": text if text != "" else label_for(action), "priority": priority}
if _context_hints.get(source) == h: return
_context_hints[source] = h
changed.emit()
func clear_context(source : Variant) -> void:
if _context_hints.erase(source): changed.emit()
#---------- lecture (par le HUD) ----------
func get_visible() -> Array:
for key in _context_hints.keys():
if typeof(key) == TYPE_OBJECT and !is_instance_valid(key): _context_hints.erase(key)
var ctx : Array = _context_hints.values()
ctx.sort_custom(func(a, b): return a.priority > b.priority)
#le contexte passe avant l'état ; une même action n'apparaît qu'une fois
var result : Array = []
var used : Dictionary = {}
for h in ctx:
if used.has(h.action): continue
used[h.action] = true
result.append(h)
for action in _state_actions:
if used.has(action): continue
used[action] = true
result.append({"action": action, "text": label_for(action)})
return result

View file

@ -0,0 +1 @@
uid://bqapxkrub0fcs

View file

@ -0,0 +1,33 @@
extends MarginContainer
@onready var rows : HBoxContainer = $HBoxContainer
func _ready() -> void:
Hints.changed.connect(refresh)
refresh()
func refresh() -> void:
for c in rows.get_children():
rows.remove_child(c)
c.queue_free()
for h in Hints.get_visible():
rows.add_child(make_row(h.action, h.text))
func make_row(action : StringName, text : String) -> HBoxContainer:
var row := HBoxContainer.new()
var tex := ControllerIconTexture.new()
tex.path = String(action) #même chose que le champ "path" dans ta scène
var icon := TextureRect.new()
icon.texture = tex
icon.custom_minimum_size = Vector2(40, 40)
icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
row.add_child(icon)
var label := Label.new()
label.text = text
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
row.add_child(label)
return row

View file

@ -0,0 +1 @@
uid://cj73boctbuqvj

View file

@ -1,30 +1,10 @@
[gd_scene format=3 uid="uid://di22cs2qposdm"]
[ext_resource type="Script" uid="uid://7hglx6nfu063" path="res://addons/controller_icons/objects/ControllerIconTexture.gd" id="1_4xhdt"]
[ext_resource type="Script" uid="uid://rgm6aitwyxdv" path="res://game/ui/hud/save_hud.gd" id="1_euh0j"]
[ext_resource type="Script" uid="uid://cj73boctbuqvj" path="res://game/ui/hints/hints_hud.gd" id="2_8ujol"]
[ext_resource type="Script" uid="uid://b3ddqgiawrh5c" path="res://game/ui/hud/team_hud.gd" id="2_lkjh2"]
[ext_resource type="Script" uid="uid://c3b263k0gt0ck" path="res://game/ui/hide_during_battle.gd" id="3_8ujol"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_uyiaf"]
[sub_resource type="Texture2D" id="Texture2D_3agn1"]
resource_local_to_scene = false
resource_name = ""
script = ExtResource("1_4xhdt")
path = "player_jump"
metadata/_custom_type_script = "uid://7hglx6nfu063"
[sub_resource type="LabelSettings" id="LabelSettings_3agn1"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_0fvdv"]
[sub_resource type="Texture2D" id="Texture2D_4xhdt"]
resource_local_to_scene = false
resource_name = ""
script = ExtResource("1_4xhdt")
path = "ui_accept"
metadata/_custom_type_script = "uid://7hglx6nfu063"
[node name="Ui" type="CanvasLayer" unique_id=1492671226]
[node name="SaveHUD" type="CanvasLayer" parent="." unique_id=319200051]
@ -49,53 +29,13 @@ grow_horizontal = 0
grow_vertical = 0
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 10
script = ExtResource("2_8ujol")
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer" unique_id=1235437041]
layout_mode = 2
[node name="PanelContainer2" type="PanelContainer" parent="MarginContainer/HBoxContainer" unique_id=429339024]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxEmpty_uyiaf")
[node name="HFlowContainer3" type="HFlowContainer" parent="MarginContainer/HBoxContainer/PanelContainer2" unique_id=165290387]
custom_minimum_size = Vector2(130, -1)
layout_mode = 2
theme_override_constants/separation = 24
alignment = 2
[node name="TextureRect" type="TextureRect" parent="MarginContainer/HBoxContainer/PanelContainer2/HFlowContainer3" unique_id=895869050]
custom_minimum_size = Vector2(40, 40)
custom_maximum_size = Vector2(40, 40)
layout_mode = 2
size_flags_vertical = 3
texture = SubResource("Texture2D_3agn1")
expand_mode = 1
[node name="Label" type="Label" parent="MarginContainer/HBoxContainer/PanelContainer2/HFlowContainer3" unique_id=1494282096]
layout_mode = 2
text = "Sauter"
label_settings = SubResource("LabelSettings_3agn1")
[node name="PanelContainer" type="PanelContainer" parent="MarginContainer/HBoxContainer" unique_id=525836706]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxEmpty_0fvdv")
[node name="HFlowContainer2" type="HFlowContainer" parent="MarginContainer/HBoxContainer/PanelContainer" unique_id=534508482]
custom_minimum_size = Vector2(130, -1)
layout_mode = 2
alignment = 2
[node name="TextureRect" type="TextureRect" parent="MarginContainer/HBoxContainer/PanelContainer/HFlowContainer2" unique_id=729252705]
custom_minimum_size = Vector2(40, 40)
layout_mode = 2
size_flags_vertical = 3
texture = SubResource("Texture2D_4xhdt")
expand_mode = 1
[node name="Label" type="Label" parent="MarginContainer/HBoxContainer/PanelContainer/HFlowContainer2" unique_id=701166371]
layout_mode = 2
text = "Accepter"
label_settings = SubResource("LabelSettings_3agn1")
[node name="HideDuringBattle" type="Node" parent="MarginContainer" unique_id=1229951221]
script = ExtResource("3_8ujol")

View file

@ -27,6 +27,7 @@ ControllerIcons="*uid://b7ho1rjcpht1d"
GameData="*uid://d4beiel88cwc7"
UiAudio="*uid://7e7fe2xk3aue"
LoadingScreen="*uid://ckyjxhrfhaqlc"
Hints="*uid://bqapxkrub0fcs"
Pokedex="*uid://dijnkrewbwgmx"
MountRegistry="*uid://u4gskwbwm0b0"
DebugPokemonPanel="*uid://b3058rssf6ud5"