diff --git a/assets/music&sfx/sfx/achievement_unlock.wav b/assets/music&sfx/sfx/achievement_unlock.wav new file mode 100644 index 0000000..a8c8781 Binary files /dev/null and b/assets/music&sfx/sfx/achievement_unlock.wav differ diff --git a/assets/music&sfx/sfx/achievement_unlock.wav.import b/assets/music&sfx/sfx/achievement_unlock.wav.import new file mode 100644 index 0000000..6c22e63 --- /dev/null +++ b/assets/music&sfx/sfx/achievement_unlock.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://c7x1rtkae66k4" +path="res://.godot/imported/achievement_unlock.wav-9a9f9e013cd33e691b8d6734dd2cf61a.sample" + +[deps] + +source_file="res://assets/music&sfx/sfx/achievement_unlock.wav" +dest_files=["res://.godot/imported/achievement_unlock.wav-9a9f9e013cd33e691b8d6734dd2cf61a.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/scripts/achievement_manager.gd b/scripts/achievement_manager.gd index 42fa77d..2612c38 100644 --- a/scripts/achievement_manager.gd +++ b/scripts/achievement_manager.gd @@ -1,6 +1,7 @@ extends Node const SAVE_PATH = "user://achievements.json" +const _POPUP_SCRIPT = preload("res://scripts/achievement_popup.gd") var achievements: Array = [] func _ready() -> void: @@ -123,15 +124,22 @@ func _define() -> void: ] func check_all() -> void: - var changed = false + var newly_unlocked: Array = [] for ach in achievements: if ach.unlocked: continue if _meets(ach): ach.unlocked = true - changed = true - if changed: + newly_unlocked.append(ach) + if not newly_unlocked.is_empty(): _save() + for ach in newly_unlocked: + _show_popup(ach) + +func _show_popup(ach: Dictionary) -> void: + var popup = _POPUP_SCRIPT.new() + get_tree().root.add_child(popup) + popup.show_unlock(ach) func _meets(ach: Dictionary) -> bool: var d = StatsManager.data diff --git a/scripts/achievement_popup.gd b/scripts/achievement_popup.gd new file mode 100644 index 0000000..c37f2d2 --- /dev/null +++ b/scripts/achievement_popup.gd @@ -0,0 +1,122 @@ +extends CanvasLayer + +const _VP_W := 1280.0 +const _VP_H := 720.0 +const _MARGIN := 16.0 +const _SFX_PATH := "res://assets/music&sfx/sfx/achievement_unlock.wav" + +var _font = preload("res://assets/fonts/slkscre.ttf") +var _panel: PanelContainer + +func _ready() -> void: + layer = 100 + process_mode = Node.PROCESS_MODE_ALWAYS + + _panel = PanelContainer.new() + _panel.custom_minimum_size = Vector2(310.0, 76.0) + _panel.mouse_filter = Control.MOUSE_FILTER_IGNORE + + var style := StyleBoxFlat.new() + style.bg_color = Color(0.04, 0.04, 0.12, 0.96) + style.border_width_left = 2 + style.border_width_top = 2 + style.border_width_right = 2 + style.border_width_bottom = 2 + style.border_color = Color(0.78, 0.62, 0.18, 1.0) + style.corner_radius_top_left = 3 + style.corner_radius_top_right = 3 + style.corner_radius_bottom_right = 3 + style.corner_radius_bottom_left = 3 + style.content_margin_left = 8.0 + style.content_margin_top = 8.0 + style.content_margin_right = 12.0 + style.content_margin_bottom = 8.0 + _panel.add_theme_stylebox_override("panel", style) + + _panel.position = Vector2(_VP_W + 8.0, _VP_H - 76.0 - _MARGIN) + add_child(_panel) + + +func show_unlock(ach: Dictionary) -> void: + var hbox := HBoxContainer.new() + hbox.add_theme_constant_override("separation", 10) + hbox.mouse_filter = Control.MOUSE_FILTER_IGNORE + _panel.add_child(hbox) + + # Icon square + var icon_wrap := Control.new() + icon_wrap.custom_minimum_size = Vector2(52, 52) + icon_wrap.mouse_filter = Control.MOUSE_FILTER_IGNORE + hbox.add_child(icon_wrap) + + var icon_bg := ColorRect.new() + icon_bg.color = ach.color + icon_bg.mouse_filter = Control.MOUSE_FILTER_IGNORE + icon_bg.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + icon_wrap.add_child(icon_bg) + + var icon_lbl := Label.new() + icon_lbl.text = ach.icon_char + icon_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + icon_lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + icon_lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE + icon_lbl.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + icon_lbl.add_theme_font_override("font", _font) + icon_lbl.add_theme_font_size_override("font_size", 22) + icon_lbl.add_theme_color_override("font_color", Color(1.0, 1.0, 0.88)) + icon_lbl.add_theme_color_override("font_outline_color", Color(0.0, 0.0, 0.0, 0.8)) + icon_lbl.add_theme_constant_override("outline_size", 2) + icon_wrap.add_child(icon_lbl) + + # Text block + var vbox := VBoxContainer.new() + vbox.size_flags_vertical = Control.SIZE_SHRINK_CENTER + vbox.mouse_filter = Control.MOUSE_FILTER_IGNORE + hbox.add_child(vbox) + + var header := Label.new() + header.text = "Achievement Unlocked!" + header.mouse_filter = Control.MOUSE_FILTER_IGNORE + header.add_theme_font_override("font", _font) + header.add_theme_font_size_override("font_size", 13) + header.add_theme_color_override("font_color", Color(0.78, 0.62, 0.18, 1.0)) + vbox.add_child(header) + + var title_lbl := Label.new() + title_lbl.text = ach.title + title_lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE + title_lbl.add_theme_font_override("font", _font) + title_lbl.add_theme_font_size_override("font_size", 20) + title_lbl.add_theme_color_override("font_color", Color(1.0, 0.95, 0.8, 1.0)) + vbox.add_child(title_lbl) + + _play_sound() + # Wait one frame so the panel has its final size before animating + await get_tree().process_frame + _animate() + + +func _play_sound() -> void: + if not ResourceLoader.exists(_SFX_PATH): + return + var sfx := AudioStreamPlayer.new() + sfx.stream = load(_SFX_PATH) + sfx.volume_db = -2.0 + sfx.process_mode = Node.PROCESS_MODE_ALWAYS + add_child(sfx) + sfx.play() + sfx.finished.connect(sfx.queue_free) + +func _animate() -> void: + var pw := _panel.size.x + var target_x := _VP_W - pw - _MARGIN + _panel.position.x = _VP_W + 8.0 + + var tween := create_tween() + tween.set_pause_mode(Tween.TWEEN_PAUSE_PROCESS) + tween.tween_property(_panel, "position:x", target_x, 0.4)\ + .set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_QUART) + tween.tween_interval(3.5) + tween.tween_property(_panel, "position:x", _VP_W + 8.0, 0.3)\ + .set_ease(Tween.EASE_IN).set_trans(Tween.TRANS_QUART) + tween.tween_callback(queue_free) diff --git a/scripts/achievement_popup.gd.uid b/scripts/achievement_popup.gd.uid new file mode 100644 index 0000000..07351b9 --- /dev/null +++ b/scripts/achievement_popup.gd.uid @@ -0,0 +1 @@ +uid://dsitid33q7lcx diff --git a/scripts/stats_manager.gd b/scripts/stats_manager.gd index ade85ac..77c4fae 100644 --- a/scripts/stats_manager.gd +++ b/scripts/stats_manager.gd @@ -50,10 +50,12 @@ func end_run(died: bool) -> void: func on_kill(enemy_type: String) -> void: if data["kills"].has(enemy_type): data["kills"][enemy_type] += 1 + AchievementManager.call_deferred("check_all") func on_spell_cast(spell_type: String) -> void: if data["spells_cast"].has(spell_type): data["spells_cast"][spell_type] += 1 + AchievementManager.call_deferred("check_all") func _save() -> void: var file = FileAccess.open(SAVE_PATH, FileAccess.WRITE)