Add spine and dialogic #1
276
.agents/skills/spine-godot/SKILL.md
Normal file
276
.agents/skills/spine-godot/SKILL.md
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
---
|
||||||
|
name: spine-godot
|
||||||
|
description: spine-godot runtime for Godot 4 — Spine skeleton display, animation playback, bone/slot manipulation, skin mixing, 2D lighting, and runtime asset loading. Use when working with Spine skeletons, animations, SpineSprite nodes, SpineBoneNode, SpineSlotNode, or SpineAnimationTrack in this project.
|
||||||
|
---
|
||||||
|
|
||||||
|
# spine-godot Skill
|
||||||
|
|
||||||
|
The spine-godot runtime provides Godot nodes and APIs to load, display, animate, and manipulate skeletons exported from Spine. It wraps the spine-cpp runtime and exposes it to GDScript and C#.
|
||||||
|
|
||||||
|
> **Official docs:** https://en.esotericsoftware.com/spine-godot
|
||||||
|
> **Spine Runtimes Guide:** https://en.esotericsoftware.com/spine-runtimes-guide
|
||||||
|
> **Source:** https://github.com/EsotericSoftware/spine-runtimes (under `spine-godot/`)
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
spine-godot Runtime
|
||||||
|
├── SpineSprite — displays a skeleton from SkeletonDataResource
|
||||||
|
│ ├── SpineSkeleton — skeleton instance (bones, slots, attachments, skins, constraints)
|
||||||
|
│ ├── SpineAnimationState — animation playback, queuing, mixing via tracks
|
||||||
|
│ └── Signals — animation_started, animation_completed, animation_event, etc.
|
||||||
|
├── SpineBoneNode — follows or drives a bone's transform
|
||||||
|
├── SpineSlotNode — inserts nodes into drawing order or overrides slot materials
|
||||||
|
└── SpineAnimationTrack — animates a SpineSprite via Godot AnimationPlayer (C++ engine module only)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Resource Types
|
||||||
|
|
||||||
|
| Resource | Purpose |
|
||||||
|
|----------|---------|
|
||||||
|
| `SpineSkeletonFileResource` | Imported `.skel` or `.spine-json` skeleton data |
|
||||||
|
| `SpineAtlasResource` | Imported `.atlas` texture atlas data (uses normal map prefix `n_` by default) |
|
||||||
|
| `SpineSkeletonDataResource` | Combines a `SpineSkeletonFileResource` + `SpineAtlasResource` + animation mix times. Shared across SpineSprite instances. |
|
||||||
|
|
||||||
|
## Asset Management
|
||||||
|
|
||||||
|
### Exporting from Spine Editor
|
||||||
|
|
||||||
|
Export these files from the Spine Editor:
|
||||||
|
1. **Skeleton data:** `skeleton-name.spine-json` or `skeleton-name.skel` (binary, smaller/faster — preferred).
|
||||||
|
2. **Texture atlas:** `skeleton-name.atlas`
|
||||||
|
3. **Atlas pages:** One or more `.png` image files.
|
||||||
|
|
||||||
|
> Prefer `.skel` binary exports. If using JSON, use `.spine-json` extension (not `.json`).
|
||||||
|
> Pre-multiplied alpha atlases are **not supported** — Godot's default bleed handles artifacts.
|
||||||
|
|
||||||
|
### Importing into Godot
|
||||||
|
|
||||||
|
1. Drag `.skel`/`.spine-json`, `.atlas`, and `.png` files into a Godot project folder.
|
||||||
|
2. The importer creates a `SpineSkeletonFileResource`, a `SpineAtlasResource`, and Godot texture resources.
|
||||||
|
3. Create a `SpineSkeletonDataResource`: Right-click folder → `New Resource...` → `SpineSkeletonDataResource`.
|
||||||
|
4. Assign the atlas resource and skeleton file resource in the inspector. Set animation mix times here.
|
||||||
|
|
||||||
|
> A `SpineSkeletonDataResource` should be **shared** by all `SpineSprite` instances displaying the same skeleton. Do not use inline resources.
|
||||||
|
|
||||||
|
### Updating Assets During Development
|
||||||
|
|
||||||
|
Overwrite the source files re-exported from Spine. Godot auto-detects changes and re-imports. If it doesn't, force re-import in the import settings panel.
|
||||||
|
|
||||||
|
### Runtime Loading from Disk (modding)
|
||||||
|
|
||||||
|
Load skeleton and atlas files from arbitrary paths at runtime:
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
# Load skeleton file
|
||||||
|
var skeleton_file_res = SpineSkeletonFileResource.new()
|
||||||
|
skeleton_file_res.load_from_file("/path/to/skeleton.skel")
|
||||||
|
|
||||||
|
# Load atlas file
|
||||||
|
var atlas_res = SpineAtlasResource.new()
|
||||||
|
atlas_res.load_from_atlas_file("/path/to/skeleton.atlas")
|
||||||
|
|
||||||
|
# Create shared skeleton data resource
|
||||||
|
var skeleton_data_res = SpineSkeletonDataResource.new()
|
||||||
|
skeleton_data_res.skeleton_file_res = skeleton_file_res
|
||||||
|
skeleton_data_res.atlas_res = atlas_res
|
||||||
|
|
||||||
|
# Create sprite from data
|
||||||
|
var sprite = SpineSprite.new()
|
||||||
|
sprite.skeleton_data_res = skeleton_data_res
|
||||||
|
sprite.position = Vector2(200, 200)
|
||||||
|
sprite.get_animation_state().set_animation("animation", true, 0)
|
||||||
|
add_child(sprite)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Nodes
|
||||||
|
|
||||||
|
### SpineSprite Node
|
||||||
|
|
||||||
|
Displays skeleton data from a `SkeletonDataResource`. Provides access to the animation state and skeleton instance.
|
||||||
|
|
||||||
|
#### Creating a SpineSprite
|
||||||
|
|
||||||
|
1. Add a `SpineSprite` node via the `+` button in the scene panel.
|
||||||
|
2. Assign a `SkeletonDataResource` in the inspector.
|
||||||
|
3. Transform the node freely in the editor viewport.
|
||||||
|
|
||||||
|
#### Editor Features
|
||||||
|
|
||||||
|
- **Debug view:** Check bones, slots, attachments in the `Debug` property section. Hover over parts in the viewport to see names.
|
||||||
|
- **Animation preview:** Use the `Preview` section to set an animation and scrub through time with the slider.
|
||||||
|
- **Custom materials:** Set per-blend-mode materials in the `Materials` section (applied to all slots). Use `SpineSlotNode` to override per-slot.
|
||||||
|
- **Update Mode:** `Process` (default, every frame), `Physics` (fixed interval, 60fps), `Manual` (call `update_skeleton()` yourself in `_process`/`_physics_process`).
|
||||||
|
|
||||||
|
#### Animating a SpineSprite
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
extends SpineSprite
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
var animation_state = get_animation_state()
|
||||||
|
|
||||||
|
# Set an animation on track 0, looping
|
||||||
|
animation_state.set_animation("walk", true, 0)
|
||||||
|
|
||||||
|
# Queue an animation after a delay (mix duration implied by mix times)
|
||||||
|
animation_state.add_animation("idle", 0.5, true, 0)
|
||||||
|
|
||||||
|
# Set a reversed animation
|
||||||
|
var track_entry = animation_state.set_animation("walk", true, 0)
|
||||||
|
track_entry.set_reverse(true)
|
||||||
|
|
||||||
|
# Mix to an empty animation (reset to setup pose)
|
||||||
|
animation_state.set_empty_animation(0, 0.5)
|
||||||
|
animation_state.add_empty_animation(0, 0.5, 0.5)
|
||||||
|
|
||||||
|
# Clear tracks
|
||||||
|
animation_state.clear_track(0) # clear one track
|
||||||
|
animation_state.clear_tracks() # clear all tracks
|
||||||
|
|
||||||
|
# Reset skeleton poses
|
||||||
|
get_skeleton().set_to_setup_pose() # bones + slots
|
||||||
|
get_skeleton().set_slots_to_setup_pose() # slots only
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Animation State API
|
||||||
|
|
||||||
|
| Method | Purpose |
|
||||||
|
|--------|---------|
|
||||||
|
| `set_animation(name, loop, track)` | Play animation. Returns `SpineTrackEntry`. |
|
||||||
|
| `add_animation(name, delay, loop, track)` | Queue animation after delay. Returns `SpineTrackEntry`. |
|
||||||
|
| `set_empty_animation(track, mix_duration)` | Mix out to empty pose immediately. |
|
||||||
|
| `add_empty_animation(track, mix_duration, delay)` | Queue empty animation mix-out. |
|
||||||
|
| `clear_track(track)` | Immediately clear one track. |
|
||||||
|
| `clear_tracks()` | Immediately clear all tracks. |
|
||||||
|
|
||||||
|
> **Important:** Do not hold `SpineTrackEntry` references across frames. They are reused internally and become invalid once the animation completes.
|
||||||
|
|
||||||
|
#### SpineTrackEntry Properties
|
||||||
|
|
||||||
|
| Method | Purpose |
|
||||||
|
|--------|---------|
|
||||||
|
| `set_reverse(true)` | Play animation in reverse. |
|
||||||
|
| `set_mix_duration(seconds)` | Override mix duration. |
|
||||||
|
| `set_alpha(value)` | Mix alpha for blending (0-1). |
|
||||||
|
| `set_time_scale(scale)` | Animation speed multiplier. |
|
||||||
|
| `set_track_time(time)` | Jump to specific time in seconds. |
|
||||||
|
|
||||||
|
#### SpineSprite Signals
|
||||||
|
|
||||||
|
**Animation state signals:**
|
||||||
|
- `animation_started(track_entry)` — animation started playing.
|
||||||
|
- `animation_interrupted(track_entry)` — track cleared or new animation set.
|
||||||
|
- `animation_completed(track_entry)` — animation finished one full loop.
|
||||||
|
- `animation_ended(track_entry)` — animation will never be applied again.
|
||||||
|
- `animation_disposed(track_entry)` — track entry disposed.
|
||||||
|
- `animation_event(track_entry, event)` — user-defined Spine event triggered.
|
||||||
|
|
||||||
|
**Lifecycle signals:**
|
||||||
|
- `before_animation_state_update` — before animation state is updated with delta.
|
||||||
|
- `before_animation_state_apply` — before animation state is applied to skeleton pose.
|
||||||
|
- `before_world_transforms_change` — before bone world transforms update. Use to set bone transforms manually.
|
||||||
|
- `world_transforms_changed` — after bone world transforms update.
|
||||||
|
|
||||||
|
#### Mix-and-Match Skins
|
||||||
|
|
||||||
|
Combine multiple skins to create custom avatars:
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
var custom_skin = new_skin("custom-skin")
|
||||||
|
var data = get_skeleton().get_data()
|
||||||
|
|
||||||
|
custom_skin.add_skin(data.find_skin("skin-base"))
|
||||||
|
custom_skin.add_skin(data.find_skin("nose/short"))
|
||||||
|
custom_skin.add_skin(data.find_skin("eyes/violet"))
|
||||||
|
custom_skin.add_skin(data.find_skin("hair/brown"))
|
||||||
|
custom_skin.add_skin(data.find_skin("clothes/hoodie-orange"))
|
||||||
|
|
||||||
|
get_skeleton().set_skin(custom_skin)
|
||||||
|
get_skeleton().set_slots_to_setup_pose()
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Getting and Setting Bone Transforms
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
# Get a bone's global transform in Godot canvas space
|
||||||
|
var transform = get_global_bone_transform("bone_name")
|
||||||
|
|
||||||
|
# Set a bone's transform (must be called before world transforms update)
|
||||||
|
set_global_bone_transform("bone_name", Transform2D(0, Vector2(100, 200)))
|
||||||
|
```
|
||||||
|
|
||||||
|
When manually setting bone transforms, connect to `before_world_transforms_change` signal. Alternatively, use `SpineBoneNode` for simpler bone manipulation.
|
||||||
|
|
||||||
|
### SpineBoneNode
|
||||||
|
|
||||||
|
A child node of `SpineSprite` that either **follows** or **drives** a bone.
|
||||||
|
|
||||||
|
> Must be a **direct child** of a `SpineSprite`.
|
||||||
|
|
||||||
|
**Inspector properties:**
|
||||||
|
- `Bone Name` — dropdown of all available bones.
|
||||||
|
- `Bone Mode` — `Follow` (node moves with bone) or `Drive` (node controls bone position).
|
||||||
|
- `Enabled` — toggle following/driving on/off.
|
||||||
|
|
||||||
|
**Use cases:**
|
||||||
|
- **Drive mode:** Control a bone via mouse position or other input (e.g., aiming).
|
||||||
|
- **Follow mode:** Attach other nodes (CollisionShape, Marker2D) to follow a bone.
|
||||||
|
|
||||||
|
See examples: `example/05-mouse-following` (drive), `example/06-bone-following` (follow).
|
||||||
|
|
||||||
|
### SpineSlotNode
|
||||||
|
|
||||||
|
A child node of `SpineSprite` that inserts children into the skeleton's drawing order or overrides a slot's materials.
|
||||||
|
|
||||||
|
> Must be a **direct child** of a `SpineSprite`.
|
||||||
|
|
||||||
|
**Inspector properties:**
|
||||||
|
- `Slot Name` — dropdown of all available slots.
|
||||||
|
- `Materials` — custom materials that override the slot's default materials.
|
||||||
|
|
||||||
|
**Use cases:**
|
||||||
|
- Attach particle systems, custom sprites, or other `SpineSprite` nodes on top of a slot.
|
||||||
|
- Override materials per-slot (overrides `SpineSprite`'s global materials).
|
||||||
|
|
||||||
|
See examples: `example/07-slot-node` (drawing order), `example/09-custom-material` (material override).
|
||||||
|
|
||||||
|
### SpineAnimationTrack (C++ Engine Module Only)
|
||||||
|
|
||||||
|
Animates a `SpineSprite` via Godot's `AnimationPlayer` and animation editor. Ideal for cutscenes.
|
||||||
|
|
||||||
|
> **Experimental.** Not available in the GDExtension.
|
||||||
|
|
||||||
|
> Must be a **direct child** of a `SpineSprite`.
|
||||||
|
|
||||||
|
- Creates a child `AnimationPlayer` automatically.
|
||||||
|
- Key animations on the child `AnimationPlayer`; key animation properties (loop, reverse, etc.) on the `SpineAnimationTrack`.
|
||||||
|
- Multiple `SpineAnimationTrack` nodes on one `SpineSprite` enable multi-track layering.
|
||||||
|
- Each track gets a unique **track index** — higher tracks override lower tracks' skeleton properties.
|
||||||
|
|
||||||
|
> Mix times from `SpineSkeletonDataResource` cannot be previewed in the editor.
|
||||||
|
|
||||||
|
See example: `example/08-animation-player`.
|
||||||
|
|
||||||
|
## 2D Lighting
|
||||||
|
|
||||||
|
spine-godot integrates with Godot's 2D lighting system.
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
1. Provide normal map `.png` images alongside atlas page images with prefix `n_` (default): e.g., `raptor.png` → `n_raptor.png`.
|
||||||
|
2. The normal map prefix is configurable in the atlas import settings.
|
||||||
|
3. After importing, apply Godot 2D lights to `SpineSprite` nodes.
|
||||||
|
|
||||||
|
See example: `example/10-2d-lighting`.
|
||||||
|
|
||||||
|
## Spine Runtimes API Access
|
||||||
|
|
||||||
|
Almost all spine-cpp API is mapped 1:1 to GDScript. Objects from `SpineSprite` (via `get_skeleton()`, `get_animation_state()`) mirror the C++ API.
|
||||||
|
|
||||||
|
### GDScript Limitations
|
||||||
|
|
||||||
|
- Returned arrays/maps are **copies** — mutations don't affect internals.
|
||||||
|
- Cannot set per-track-entry listeners — use `SpineSprite` signals instead.
|
||||||
|
- Cannot create/add/remove bones, slots, or other Spine objects directly.
|
||||||
|
- C++ class hierarchies of attachments and timelines are not exposed.
|
||||||
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