# ============================================================ # Gym 09 — Minigame 1: Animation Speed Challenge # ============================================================ # A SpineSprite plays an animation at Normal, Fast, or Hard # speed. The player must click the button that matches the # *current* speed. Correct answers advance a progress bar; # reach the threshold to win. Run out of time and you lose. # The speed changes to a new random value after *every* press. # ============================================================ extends Node2D # ---------- Configurable multipliers ---------- @export var normal_speed: float = 1.0 @export var fast_speed: float = 2.0 @export var hard_speed: float = 3.0 # ---------- Timing & progression ---------- @export var time_limit: float = 30.0 ## total seconds before auto-loss @export var progress_per_click: float = 20.0 ## progress added on correct answer @export var win_threshold: float = 100.0 ## progress value that triggers a win @export var penalty_per_miss: float = 10.0 ## progress lost on wrong press # ---------- Animation ---------- @export var animation_name: String = "roar" ## which animation to play on the SpineSprite enum Speed { NORMAL, FAST, HARD } var _current_speed: Speed = Speed.NORMAL var _progress: float = 0.0 var _time_remaining: float = 0.0 var _game_active: bool = false var _result_pending: bool = false var _track_entry = null ## SpineTrackEntry — stored reference for time-scale changes @onready var _spine_sprite: SpineSprite = %SpineSprite @onready var _progress_bar: ProgressBar = %ProgressBar @onready var _current_speed_label: Label = %CurrentSpeedLabel @onready var _timer_label: Label = %TimerLabel @onready var _status_label: Label = %StatusLabel @onready var _normal_button: Button = %NormalButton @onready var _fast_button: Button = %FastButton @onready var _hard_button: Button = %HardButton # ============================================================ # Lifecycle # ============================================================ func _ready() -> void: print("[Gym09 Minigame 1] Ready! Match the animation speed — click the correct button.") _start_game() func _process(delta: float) -> void: if not _game_active: return _time_remaining -= delta if _time_remaining <= 0.0: _time_remaining = 0.0 _on_time_up() _update_ui() # ============================================================ # Game state # ============================================================ func _start_game() -> void: _progress = 0.0 _time_remaining = time_limit _game_active = true _result_pending = false _pick_random_speed() _update_ui() func _pick_random_speed() -> void: _current_speed = randi() % 3 as Speed _apply_speed() func _apply_speed() -> void: _current_speed_label.text = "Press: " + Speed.keys()[_current_speed].capitalize() if _track_entry == null: return match _current_speed: Speed.NORMAL: _track_entry.set_time_scale(normal_speed) Speed.FAST: _track_entry.set_time_scale(fast_speed) Speed.HARD: _track_entry.set_time_scale(hard_speed) # ============================================================ # Animation # ============================================================ func _play_animation() -> void: _track_entry = _spine_sprite.get_animation_state().set_animation(animation_name, false, 0) # ============================================================ # Button handlers # ============================================================ func _on_normal_pressed() -> void: _evaluate_answer(Speed.NORMAL) func _on_fast_pressed() -> void: _evaluate_answer(Speed.FAST) func _on_hard_pressed() -> void: _evaluate_answer(Speed.HARD) func _evaluate_answer(chosen: Speed) -> void: if not _game_active or _result_pending: return # Restart animation on each press _play_animation() _apply_speed() if chosen == _current_speed: _progress += progress_per_click print("[Gym09 Minigame 1] Correct! Progress: %.0f / %.0f" % [_progress, win_threshold]) if _progress >= win_threshold: _on_win() return else: _progress = maxf(0.0, _progress - penalty_per_miss) print("[Gym09 Minigame 1] Wrong button! Current speed is ", Speed.keys()[_current_speed]) # Speed changes after every button press _pick_random_speed() _update_ui() # ============================================================ # End conditions # ============================================================ func _on_win() -> void: _game_active = false _result_pending = true print("[Gym09 Minigame 1] WIN!") _status_label.text = "YOU WIN!" _disable_buttons() Dialogic.VAR.Gym.minigame_done = true await get_tree().create_timer(1.5).timeout _return_to_main() func _on_time_up() -> void: _game_active = false _result_pending = true print("[Gym09 Minigame 1] TIME'S UP — You lose!") _status_label.text = "TIME'S UP!" _disable_buttons() # Do NOT set minigame_done — the controller will re-show the intro await get_tree().create_timer(1.5).timeout _return_to_main() func _return_to_main() -> void: var return_path := "res://docs/gyms/gym09_scene_switching/gym09_main.tscn" if Dialogic.VAR.Gym.minigame_return == "hub": return_path = "res://docs/gyms/cross-timeline-interactions/gym_controller.tscn" print("[Gym09 Minigame 1] Returning to: ", return_path) get_tree().change_scene_to_file(return_path) # ============================================================ # UI helpers # ============================================================ func _disable_buttons() -> void: _normal_button.disabled = true _fast_button.disabled = true _hard_button.disabled = true func _update_ui() -> void: _progress_bar.max_value = win_threshold _progress_bar.value = clampf(_progress, 0.0, win_threshold) _timer_label.text = "Time: %.1f" % _time_remaining