feat(progression): succès/achievements + toasts notification
Nouvel autoload AchievementManager avec 12 succès (Premier éclat, Mineur confirmé, Chasseur de perles, Fouilleur d'épaves, Abysses, Légende des océans, etc.). Chaque déblocage donne un bonus XP et joue un son. HUD: toasts dorés avec icône + titre + desc + fade-in/out, empilés en haut centre. Son bulle au déblocage. Long-terme: cible des milestones cumulatifs qui donnent un sentiment de progression durable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,9 @@ var _quest_list: VBoxContainer = null
|
||||
var _quest_complete_banner: Label = null
|
||||
var _quest_banner_timer: float = 0.0
|
||||
|
||||
var _toast_container: VBoxContainer = null
|
||||
var _toasts: Array = [] # each: {"node": PanelContainer, "timer": float}
|
||||
|
||||
var _biome_cache_pos: Vector3 = Vector3(99999, 99999, 99999)
|
||||
var _biome_cached_name: String = ""
|
||||
|
||||
@@ -48,6 +51,10 @@ func _ready() -> void:
|
||||
qm.quests_updated.connect(_on_quests_updated)
|
||||
qm.quest_completed.connect(_on_quest_completed)
|
||||
_on_quests_updated()
|
||||
_build_toast_container()
|
||||
var am: Node = get_node_or_null("/root/AchievementManager")
|
||||
if am != null:
|
||||
am.achievement_unlocked.connect(_on_achievement_unlocked)
|
||||
|
||||
|
||||
func _build_info_panel() -> void:
|
||||
@@ -232,6 +239,91 @@ func _on_quest_completed(q: Dictionary) -> void:
|
||||
am.call("play_bubble_sfx", _dolphin.global_position)
|
||||
|
||||
|
||||
func _build_toast_container() -> void:
|
||||
_toast_container = VBoxContainer.new()
|
||||
_toast_container.anchor_left = 0.5
|
||||
_toast_container.anchor_right = 0.5
|
||||
_toast_container.anchor_top = 0.0
|
||||
_toast_container.anchor_bottom = 0.0
|
||||
_toast_container.offset_left = -220.0
|
||||
_toast_container.offset_right = 220.0
|
||||
_toast_container.offset_top = 16.0
|
||||
_toast_container.offset_bottom = 16.0
|
||||
_toast_container.add_theme_constant_override("separation", 6)
|
||||
add_child(_toast_container)
|
||||
|
||||
|
||||
func _on_achievement_unlocked(ach: Dictionary) -> void:
|
||||
if _toast_container == null:
|
||||
return
|
||||
var panel := PanelContainer.new()
|
||||
var style := StyleBoxFlat.new()
|
||||
style.bg_color = Color(0.18, 0.14, 0.05, 0.92)
|
||||
style.border_color = Color(1.0, 0.82, 0.2)
|
||||
style.set_border_width_all(2)
|
||||
style.set_corner_radius_all(8)
|
||||
panel.add_theme_stylebox_override("panel", style)
|
||||
|
||||
var hbox := HBoxContainer.new()
|
||||
hbox.add_theme_constant_override("separation", 10)
|
||||
panel.add_child(hbox)
|
||||
|
||||
var icon := Label.new()
|
||||
icon.text = ach.get("icon", "★")
|
||||
icon.add_theme_font_size_override("font_size", 28)
|
||||
icon.add_theme_color_override("font_color", Color(1.0, 0.92, 0.4))
|
||||
hbox.add_child(icon)
|
||||
|
||||
var vbox := VBoxContainer.new()
|
||||
vbox.add_theme_constant_override("separation", 0)
|
||||
hbox.add_child(vbox)
|
||||
|
||||
var title := Label.new()
|
||||
title.text = "SUCCÈS DÉBLOQUÉ : %s" % ach["name"]
|
||||
title.add_theme_font_size_override("font_size", 13)
|
||||
title.add_theme_color_override("font_color", Color(1.0, 0.95, 0.6))
|
||||
vbox.add_child(title)
|
||||
|
||||
var desc := Label.new()
|
||||
desc.text = "%s (+%d XP)" % [ach["desc"], ach["xp"]]
|
||||
desc.add_theme_font_size_override("font_size", 11)
|
||||
desc.add_theme_color_override("font_color", Color(0.88, 0.88, 0.88))
|
||||
vbox.add_child(desc)
|
||||
|
||||
_toast_container.add_child(panel)
|
||||
panel.modulate.a = 0.0
|
||||
_toasts.append({"node": panel, "timer": 4.0, "age": 0.0})
|
||||
|
||||
var audio: Node = get_node_or_null("/root/AudioManager")
|
||||
if audio != null and is_instance_valid(_dolphin) and audio.has_method("play_bubble_sfx"):
|
||||
audio.call("play_bubble_sfx", _dolphin.global_position)
|
||||
|
||||
|
||||
func _update_toasts(delta: float) -> void:
|
||||
if _toasts.is_empty():
|
||||
return
|
||||
var i: int = _toasts.size() - 1
|
||||
while i >= 0:
|
||||
var t: Dictionary = _toasts[i]
|
||||
t["age"] += delta
|
||||
t["timer"] -= delta
|
||||
var node: PanelContainer = t["node"]
|
||||
if not is_instance_valid(node):
|
||||
_toasts.remove_at(i)
|
||||
i -= 1
|
||||
continue
|
||||
if t["age"] < 0.35:
|
||||
node.modulate.a = t["age"] / 0.35
|
||||
elif t["timer"] < 0.5:
|
||||
node.modulate.a = maxf(t["timer"] / 0.5, 0.0)
|
||||
else:
|
||||
node.modulate.a = 1.0
|
||||
if t["timer"] <= 0.0:
|
||||
node.queue_free()
|
||||
_toasts.remove_at(i)
|
||||
i -= 1
|
||||
|
||||
|
||||
func _build_level_banner() -> void:
|
||||
_level_banner = Label.new()
|
||||
_level_banner.anchor_left = 0.5
|
||||
@@ -320,6 +412,8 @@ func _process(delta: float) -> void:
|
||||
if _quest_complete_banner != null:
|
||||
_quest_complete_banner.modulate.a = clampf(_quest_banner_timer / 0.6, 0.0, 1.0)
|
||||
|
||||
_update_toasts(delta)
|
||||
|
||||
if not is_instance_valid(_dolphin):
|
||||
return
|
||||
|
||||
@@ -334,6 +428,9 @@ func _process(delta: float) -> void:
|
||||
var qm: Node = get_node_or_null("/root/QuestManager")
|
||||
if qm != null and qm.has_method("note_depth"):
|
||||
qm.call("note_depth", depth_m)
|
||||
var am2: Node = get_node_or_null("/root/AchievementManager")
|
||||
if am2 != null and am2.has_method("note_depth"):
|
||||
am2.call("note_depth", depth_m)
|
||||
|
||||
if _compass_label != null:
|
||||
_compass_label.text = _compass_text(_dolphin.rotation.y)
|
||||
|
||||
Reference in New Issue
Block a user