Add animation speed minigame
This commit is contained in:
202
docs/gyms/gym09_scene_switching/gym09_minigame_1.gd
Normal file
202
docs/gyms/gym09_scene_switching/gym09_minigame_1.gd
Normal file
@@ -0,0 +1,202 @@
|
||||
# ============================================================
|
||||
# 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
|
||||
1
docs/gyms/gym09_scene_switching/gym09_minigame_1.gd.uid
Normal file
1
docs/gyms/gym09_scene_switching/gym09_minigame_1.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dhdwyav6b2ur3
|
||||
101
docs/gyms/gym09_scene_switching/gym09_minigame_1.tscn
Normal file
101
docs/gyms/gym09_scene_switching/gym09_minigame_1.tscn
Normal file
@@ -0,0 +1,101 @@
|
||||
[gd_scene format=3 uid="uid://6uporiru4ny1"]
|
||||
|
||||
[ext_resource type="SpineSkeletonDataResource" uid="uid://c53t4xfil8g8g" path="res://docs/museums/spine/assets/raptor/raptor-data.tres" id="1_irao8"]
|
||||
[ext_resource type="Script" uid="uid://dhdwyav6b2ur3" path="res://docs/gyms/gym09_scene_switching/gym09_minigame_1.gd" id="2_script"]
|
||||
|
||||
[node name="Gym09Minigame1" type="Node2D" unique_id=26511728]
|
||||
script = ExtResource("2_script")
|
||||
progress_per_click = 5.0
|
||||
|
||||
[node name="SpineSprite" type="SpineSprite" parent="." unique_id=1663671115]
|
||||
skeleton_data_res = ExtResource("1_irao8")
|
||||
preview_skin = "Default"
|
||||
preview_animation = "roar"
|
||||
preview_frame = false
|
||||
preview_time = 0.0
|
||||
unique_name_in_owner = true
|
||||
position = Vector2(578, 390)
|
||||
scale = Vector2(0.3, 0.3)
|
||||
|
||||
[node name="UILayer" type="Control" parent="." unique_id=-522947854]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 578.0
|
||||
offset_top = 566.0
|
||||
offset_right = 578.0
|
||||
offset_bottom = 566.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="UILayer" unique_id=-1400444195]
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -200.0
|
||||
offset_top = -120.0
|
||||
offset_right = 200.0
|
||||
offset_bottom = 120.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_constants/separation = 12
|
||||
|
||||
[node name="TitleLabel" type="Label" parent="UILayer/VBoxContainer" unique_id=1058329447]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 18
|
||||
text = "ANIMATION SPEED CHALLENGE"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="ProgressBar" type="ProgressBar" parent="UILayer/VBoxContainer" unique_id=-1376495673]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="CurrentSpeedLabel" type="Label" parent="UILayer/VBoxContainer" unique_id=-1544803351]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 22
|
||||
text = "Press: ???"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="TimerLabel" type="Label" parent="UILayer/VBoxContainer" unique_id=1334428778]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Time: 30.0"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="UILayer/VBoxContainer" unique_id=-1138046855]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 8
|
||||
|
||||
[node name="NormalButton" type="Button" parent="UILayer/VBoxContainer/HBoxContainer" unique_id=-2107521662]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Normal"
|
||||
|
||||
[node name="FastButton" type="Button" parent="UILayer/VBoxContainer/HBoxContainer" unique_id=929410871]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Fast"
|
||||
|
||||
[node name="HardButton" type="Button" parent="UILayer/VBoxContainer/HBoxContainer" unique_id=-727767392]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Hard"
|
||||
|
||||
[node name="StatusLabel" type="Label" parent="UILayer/VBoxContainer" unique_id=-246134786]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 20
|
||||
horizontal_alignment = 1
|
||||
|
||||
[connection signal="pressed" from="UILayer/VBoxContainer/HBoxContainer/NormalButton" to="." method="_on_normal_pressed"]
|
||||
[connection signal="pressed" from="UILayer/VBoxContainer/HBoxContainer/FastButton" to="." method="_on_fast_pressed"]
|
||||
[connection signal="pressed" from="UILayer/VBoxContainer/HBoxContainer/HardButton" to="." method="_on_hard_pressed"]
|
||||
Reference in New Issue
Block a user