feat(progression): combo de mining + multiplicateur XP
Nouvel autoload ComboTracker: chaque cassage de bloc dans les 1.8s du précédent étend le combo (+15% par palier, cap x3.0). Le multiplicateur s'applique au gain XP de base. HUD: panneau "COMBO xN (+X%)" animé (pop scale) à partir de x2, avec barre de temps restant qui se vide. Combo ≥5 au break → bonus XP final (3 * count) avec popup. Rétention: incite à chaîner les actions, récompense la dextérité et crée des pics d'intensité dans la boucle. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -53,9 +53,14 @@ func _ready() -> void:
|
||||
_on_quests_updated()
|
||||
_build_toast_container()
|
||||
_build_consumable_hint()
|
||||
_build_combo_panel()
|
||||
var am: Node = get_node_or_null("/root/AchievementManager")
|
||||
if am != null:
|
||||
am.achievement_unlocked.connect(_on_achievement_unlocked)
|
||||
var combo: Node = get_node_or_null("/root/ComboTracker")
|
||||
if combo != null:
|
||||
combo.combo_changed.connect(_on_combo_changed)
|
||||
combo.combo_broken.connect(_on_combo_broken)
|
||||
|
||||
|
||||
func _build_info_panel() -> void:
|
||||
@@ -242,6 +247,91 @@ func _on_quest_completed(q: Dictionary) -> void:
|
||||
|
||||
var _consumable_hint: Label = null
|
||||
|
||||
var _combo_panel: PanelContainer = null
|
||||
var _combo_label: Label = null
|
||||
var _combo_bar: ProgressBar = null
|
||||
|
||||
|
||||
func _build_combo_panel() -> void:
|
||||
_combo_panel = PanelContainer.new()
|
||||
_combo_panel.anchor_left = 0.5
|
||||
_combo_panel.anchor_right = 0.5
|
||||
_combo_panel.anchor_top = 0.5
|
||||
_combo_panel.anchor_bottom = 0.5
|
||||
_combo_panel.offset_left = 120.0
|
||||
_combo_panel.offset_right = 320.0
|
||||
_combo_panel.offset_top = 30.0
|
||||
_combo_panel.offset_bottom = 80.0
|
||||
|
||||
var style := StyleBoxFlat.new()
|
||||
style.bg_color = Color(0.2, 0.08, 0.05, 0.85)
|
||||
style.border_color = Color(1.0, 0.55, 0.15)
|
||||
style.set_border_width_all(2)
|
||||
style.set_corner_radius_all(8)
|
||||
_combo_panel.add_theme_stylebox_override("panel", style)
|
||||
|
||||
var vbox := VBoxContainer.new()
|
||||
vbox.add_theme_constant_override("separation", 2)
|
||||
_combo_panel.add_child(vbox)
|
||||
|
||||
_combo_label = Label.new()
|
||||
_combo_label.text = "COMBO x1"
|
||||
_combo_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
_combo_label.add_theme_font_size_override("font_size", 20)
|
||||
_combo_label.add_theme_color_override("font_color", Color(1.0, 0.85, 0.3))
|
||||
_combo_label.add_theme_color_override("font_outline_color", Color(0, 0, 0, 0.9))
|
||||
_combo_label.add_theme_constant_override("outline_size", 4)
|
||||
vbox.add_child(_combo_label)
|
||||
|
||||
_combo_bar = ProgressBar.new()
|
||||
_combo_bar.max_value = 1.0
|
||||
_combo_bar.value = 0.0
|
||||
_combo_bar.custom_minimum_size = Vector2(180, 4)
|
||||
_combo_bar.show_percentage = false
|
||||
var fill := StyleBoxFlat.new()
|
||||
fill.bg_color = Color(1.0, 0.55, 0.15)
|
||||
fill.set_corner_radius_all(2)
|
||||
_combo_bar.add_theme_stylebox_override("fill", fill)
|
||||
var bg := StyleBoxFlat.new()
|
||||
bg.bg_color = Color(0.1, 0.05, 0.03, 0.8)
|
||||
bg.set_corner_radius_all(2)
|
||||
_combo_bar.add_theme_stylebox_override("background", bg)
|
||||
vbox.add_child(_combo_bar)
|
||||
|
||||
_combo_panel.visible = false
|
||||
add_child(_combo_panel)
|
||||
|
||||
|
||||
func _on_combo_changed(cnt: int, mult: float) -> void:
|
||||
if _combo_panel == null:
|
||||
return
|
||||
if cnt < 2:
|
||||
_combo_panel.visible = false
|
||||
return
|
||||
_combo_panel.visible = true
|
||||
_combo_label.text = "COMBO x%d (+%d%%)" % [cnt, int(round((mult - 1.0) * 100.0))]
|
||||
_combo_panel.scale = Vector2(1.15, 1.15)
|
||||
var tween := create_tween()
|
||||
tween.tween_property(_combo_panel, "scale", Vector2.ONE, 0.18).set_trans(Tween.TRANS_SINE)
|
||||
|
||||
|
||||
func _on_combo_broken(final_count: int) -> void:
|
||||
if final_count >= 5:
|
||||
var pp: Node = get_node_or_null("/root/PlayerProgress")
|
||||
if pp != null:
|
||||
var bonus: int = final_count * 3
|
||||
pp.award(bonus, "bonus combo x%d" % final_count, Vector3.ZERO)
|
||||
var main: Node = get_tree().get_first_node_in_group("main")
|
||||
if main != null and is_instance_valid(_dolphin) and main.has_method("_spawn_xp_popup"):
|
||||
main.call("_spawn_xp_popup", bonus, _dolphin.global_position + Vector3(0, 2.0, 0))
|
||||
|
||||
|
||||
func _update_combo_bar() -> void:
|
||||
var combo: Node = get_node_or_null("/root/ComboTracker")
|
||||
if combo == null or _combo_bar == null:
|
||||
return
|
||||
_combo_bar.value = combo.time_remaining_ratio()
|
||||
|
||||
|
||||
func _build_consumable_hint() -> void:
|
||||
_consumable_hint = Label.new()
|
||||
@@ -455,6 +545,7 @@ func _process(delta: float) -> void:
|
||||
|
||||
_update_toasts(delta)
|
||||
_update_consumable_hint()
|
||||
_update_combo_bar()
|
||||
|
||||
if not is_instance_valid(_dolphin):
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user