diff --git a/scenes/game_elements/props/eternal_loom/components/eternal_loom.gd b/scenes/game_elements/props/eternal_loom/components/eternal_loom.gd index b0d76d876..e65ebb9d6 100644 --- a/scenes/game_elements/props/eternal_loom/components/eternal_loom.gd +++ b/scenes/game_elements/props/eternal_loom/components/eternal_loom.gd @@ -18,7 +18,7 @@ var elders: Array[Elder] @onready var interact_area: InteractArea = %InteractArea @onready var talk_behavior: TalkBehavior = %TalkBehavior -@onready var loom_offering_animation_player: AnimationPlayer = %LoomOfferingAnimationPlayer +@onready var loom_offering_animation: LoomOfferingAnimation = %LoomOfferingAnimation func _find_elder(quest: Quest) -> Elder: @@ -90,8 +90,8 @@ func on_offering_succeeded() -> void: if load_error != OK: push_error("Failed to start loading %s: %s" % [cutscene_path, error_string(load_error)]) - loom_offering_animation_player.play(&"loom_offering") - await loom_offering_animation_player.animation_finished + loom_offering_animation.play_loom_animation() + await loom_offering_animation.animation_finished GameState.quest.inventory.clear_inventory() if load_error != OK: diff --git a/scenes/game_elements/props/eternal_loom/components/loom_offering_animation.gd b/scenes/game_elements/props/eternal_loom/components/loom_offering_animation.gd new file mode 100644 index 000000000..ce8ab183d --- /dev/null +++ b/scenes/game_elements/props/eternal_loom/components/loom_offering_animation.gd @@ -0,0 +1,102 @@ +# SPDX-FileCopyrightText: The Threadbare Authors +# SPDX-License-Identifier: MPL-2.0 +@tool +class_name LoomOfferingAnimation +extends Node2D + +signal animation_finished + +const WORLD_IMAGINATION = preload("uid://6bf8rum68wq3") +const WORLD_MEMORY = preload("uid://5wscjc8yqqts") +const WORLD_SPIRIT = preload("uid://cepg1o3ihp055") + +## The time in seconds it takes for the animation to finish, determines the speed of threads +@export var animation_time: float = 5.0 +## Number of threads in the animation for the in-editor preview +@export var debug_thread_count: int = 6 + +@export_tool_button("Play") var play_animation: Callable = play_loom_animation_debug + +@onready var animation_path: Path2D = %AnimationPath +@onready var loom_offering_sound: AudioStreamPlayer2D = $LoomOfferingSound + + +func play_loom_animation() -> void: + _loom_animation_play(GameState.quest.inventory.items) + + +## Only used for the in-editor preview +func play_loom_animation_debug() -> void: + var debug_items: Array[InventoryItem] + # TODO: InventoryItem needs a utility function to obtain all types except NONE. + var item_types := InventoryItem.COLORS_PER_TYPE.keys() + for i in range(debug_thread_count): + var item := InventoryItem.new() + item.type = item_types.pick_random() + debug_items.append(item) + _loom_animation_play(debug_items) + + +func _loom_animation_play(thread_list: Array[InventoryItem]) -> void: + if thread_list.is_empty(): + animation_finished.emit() + return + + var separation := 1.0 / thread_list.size() + + # Time it takes for all threads to loop + var loop_time := animation_time * 0.7 + + loom_offering_sound.play() + + var counter: int = 0 + for thread in thread_list: + var path_follow := PathFollow2D.new() + path_follow.rotates = false + var sprite := Sprite2D.new() + + sprite.texture = thread.get_world_texture() + path_follow.add_child(sprite) + + animation_path.add_child(path_follow) + + # Starting point for each thread + path_follow.progress_ratio = counter * separation + + # Time variation between threads + var time_variation := loop_time / (2.0 * thread_list.size()) + + var tween := path_follow.create_tween() + + # The first thread is the last to end + if counter == 0: + tween.finished.connect(_on_animation_finished) + + # Animation Start + tween.tween_property(sprite, "scale", Vector2(0, 0), 0) + + tween.tween_property(sprite, "scale", Vector2(1, 1), 0.5) + # Loop around + tween.parallel().tween_property( + path_follow, "progress_ratio", 2.0, loop_time - time_variation * counter + ) + + # Animation end + ( + tween + . tween_property(sprite, "global_position", animation_path.global_position, 0.25) + . set_ease(Tween.EASE_OUT) + ) + tween.parallel().tween_property(sprite, "modulate", Color(0, 0, 0, 0), 0.5) + tween.parallel().tween_property(sprite, "scale", Vector2(3, 3), 0.5) + tween.tween_callback(_thread_free.bind(sprite)) + counter += 1 + + +# Remove thread once animation finishes +func _thread_free(thread: Sprite2D) -> void: + thread.queue_free() + + +func _on_animation_finished() -> void: + animation_finished.emit() diff --git a/scenes/game_elements/props/eternal_loom/components/loom_offering_animation.gd.uid b/scenes/game_elements/props/eternal_loom/components/loom_offering_animation.gd.uid new file mode 100644 index 000000000..13e76f3e0 --- /dev/null +++ b/scenes/game_elements/props/eternal_loom/components/loom_offering_animation.gd.uid @@ -0,0 +1 @@ +uid://byu17bpk0v5n7 diff --git a/scenes/game_elements/props/eternal_loom/eternal_loom.tscn b/scenes/game_elements/props/eternal_loom/eternal_loom.tscn index f0dba9095..41c5107c3 100644 --- a/scenes/game_elements/props/eternal_loom/eternal_loom.tscn +++ b/scenes/game_elements/props/eternal_loom/eternal_loom.tscn @@ -3,11 +3,9 @@ [ext_resource type="Script" uid="uid://dj5shb75ckk2a" path="res://scenes/game_elements/props/eternal_loom/components/eternal_loom.gd" id="1_0f752"] [ext_resource type="Texture2D" uid="uid://cj62bpasgf643" path="res://scenes/game_elements/props/eternal_loom/components/eternal_loom_blue.png" id="2_dj2yp"] [ext_resource type="Script" uid="uid://du8wfijr35r35" path="res://scenes/game_elements/props/interact_area/interact_area.gd" id="3_mjlja"] -[ext_resource type="Texture2D" uid="uid://5wscjc8yqqts" path="res://assets/first_party/collectibles/world_memory.png" id="4_12bvc"] [ext_resource type="Script" uid="uid://edcifob4jc4s" path="res://scenes/game_logic/talk_behavior.gd" id="4_eq7jh"] -[ext_resource type="Texture2D" uid="uid://6bf8rum68wq3" path="res://assets/first_party/collectibles/world_imagination.png" id="5_eq7jh"] [ext_resource type="Resource" uid="uid://yafw7bf362gh" path="res://scenes/game_elements/props/eternal_loom/components/eternal_loom_interaction.dialogue" id="5_k2bt4"] -[ext_resource type="Texture2D" uid="uid://cepg1o3ihp055" path="res://assets/first_party/collectibles/world_spirit.png" id="6_hfqem"] +[ext_resource type="Script" uid="uid://byu17bpk0v5n7" path="res://scenes/game_elements/props/eternal_loom/components/loom_offering_animation.gd" id="6_rle0d"] [ext_resource type="AudioStream" uid="uid://bg8u4en3hlo6w" path="res://assets/third_party/sounds/eternal_loom/EternalLoomShort.ogg" id="7_12bvc"] [ext_resource type="Texture2D" uid="uid://b5ooaiyxdrp6a" path="res://scenes/game_elements/components/light_texture_256x256.tres" id="9_mjlja"] [ext_resource type="Script" uid="uid://bk52qjv58locq" path="res://scenes/game_logic/light2d_behaviors/artificial_light_behavior.gd" id="10_k2bt4"] @@ -148,378 +146,11 @@ size = Vector2(393, 260) [sub_resource type="RectangleShape2D" id="RectangleShape2D_uhynt"] size = Vector2(50, 50) -[sub_resource type="Animation" id="Animation_x4prg"] -length = 0.001 -tracks/0/type = "value" -tracks/0/imported = false -tracks/0/enabled = true -tracks/0/path = NodePath("LoomOfferingAnimation/Imagination:scale") -tracks/0/interp = 1 -tracks/0/loop_wrap = true -tracks/0/keys = { -"times": PackedFloat32Array(0), -"transitions": PackedFloat32Array(1), -"update": 0, -"values": [Vector2(1, 1)] -} -tracks/1/type = "value" -tracks/1/imported = false -tracks/1/enabled = true -tracks/1/path = NodePath("LoomOfferingAnimation/Spirit:scale") -tracks/1/interp = 1 -tracks/1/loop_wrap = true -tracks/1/keys = { -"times": PackedFloat32Array(0), -"transitions": PackedFloat32Array(1), -"update": 0, -"values": [Vector2(1, 1)] -} -tracks/2/type = "value" -tracks/2/imported = false -tracks/2/enabled = true -tracks/2/path = NodePath("LoomOfferingAnimation/Memory:scale") -tracks/2/interp = 1 -tracks/2/loop_wrap = true -tracks/2/keys = { -"times": PackedFloat32Array(0), -"transitions": PackedFloat32Array(1), -"update": 0, -"values": [Vector2(1, 1)] -} -tracks/3/type = "value" -tracks/3/imported = false -tracks/3/enabled = true -tracks/3/path = NodePath("LoomOfferingAnimation/Imagination:visible") -tracks/3/interp = 1 -tracks/3/loop_wrap = true -tracks/3/keys = { -"times": PackedFloat32Array(0), -"transitions": PackedFloat32Array(1), -"update": 1, -"values": [false] -} -tracks/4/type = "value" -tracks/4/imported = false -tracks/4/enabled = true -tracks/4/path = NodePath("LoomOfferingAnimation/Spirit:visible") -tracks/4/interp = 1 -tracks/4/loop_wrap = true -tracks/4/keys = { -"times": PackedFloat32Array(0), -"transitions": PackedFloat32Array(1), -"update": 1, -"values": [false] -} -tracks/5/type = "value" -tracks/5/imported = false -tracks/5/enabled = true -tracks/5/path = NodePath("LoomOfferingAnimation/Memory:visible") -tracks/5/interp = 1 -tracks/5/loop_wrap = true -tracks/5/keys = { -"times": PackedFloat32Array(0), -"transitions": PackedFloat32Array(1), -"update": 1, -"values": [false] -} -tracks/6/type = "value" -tracks/6/imported = false -tracks/6/enabled = true -tracks/6/path = NodePath("LoomOfferingAnimation/Spirit:position") -tracks/6/interp = 1 -tracks/6/loop_wrap = true -tracks/6/keys = { -"times": PackedFloat32Array(0), -"transitions": PackedFloat32Array(1), -"update": 0, -"values": [Vector2(-48, 33)] -} -tracks/7/type = "value" -tracks/7/imported = false -tracks/7/enabled = true -tracks/7/path = NodePath("LoomOfferingAnimation/Memory:position") -tracks/7/interp = 1 -tracks/7/loop_wrap = true -tracks/7/keys = { -"times": PackedFloat32Array(0), -"transitions": PackedFloat32Array(1), -"update": 0, -"values": [Vector2(1, -34)] -} -tracks/8/type = "value" -tracks/8/imported = false -tracks/8/enabled = true -tracks/8/path = NodePath("LoomOfferingAnimation/Imagination:position") -tracks/8/interp = 1 -tracks/8/loop_wrap = true -tracks/8/keys = { -"times": PackedFloat32Array(0), -"transitions": PackedFloat32Array(1), -"update": 0, -"values": [Vector2(50, 33)] -} -tracks/9/type = "value" -tracks/9/imported = false -tracks/9/enabled = true -tracks/9/path = NodePath("LoomOfferingAnimation/Spirit:modulate") -tracks/9/interp = 1 -tracks/9/loop_wrap = true -tracks/9/keys = { -"times": PackedFloat32Array(0), -"transitions": PackedFloat32Array(1), -"update": 0, -"values": [Color(1, 1, 1, 1)] -} -tracks/10/type = "value" -tracks/10/imported = false -tracks/10/enabled = true -tracks/10/path = NodePath("LoomOfferingAnimation/Memory:modulate") -tracks/10/interp = 1 -tracks/10/loop_wrap = true -tracks/10/keys = { -"times": PackedFloat32Array(0), -"transitions": PackedFloat32Array(1), -"update": 0, -"values": [Color(1, 1, 1, 1)] -} -tracks/11/type = "value" -tracks/11/imported = false -tracks/11/enabled = true -tracks/11/path = NodePath("LoomOfferingAnimation/Imagination:modulate") -tracks/11/interp = 1 -tracks/11/loop_wrap = true -tracks/11/keys = { -"times": PackedFloat32Array(0), -"transitions": PackedFloat32Array(1), -"update": 0, -"values": [Color(1, 1, 1, 1)] -} -tracks/12/type = "value" -tracks/12/imported = false -tracks/12/enabled = true -tracks/12/path = NodePath("AnimatedSprite2D:modulate") -tracks/12/interp = 1 -tracks/12/loop_wrap = true -tracks/12/keys = { -"times": PackedFloat32Array(0), -"transitions": PackedFloat32Array(1), -"update": 0, -"values": [Color(1, 1, 1, 1)] -} -tracks/13/type = "value" -tracks/13/imported = false -tracks/13/enabled = true -tracks/13/path = NodePath("LoomOfferingAnimation/LoomOfferingSound:playing") -tracks/13/interp = 1 -tracks/13/loop_wrap = true -tracks/13/keys = { -"times": PackedFloat32Array(0), -"transitions": PackedFloat32Array(1), -"update": 1, -"values": [false] -} -tracks/14/type = "value" -tracks/14/imported = false -tracks/14/enabled = true -tracks/14/path = NodePath("LoomOfferingAnimation/LoomOfferingSound:volume_db") -tracks/14/interp = 1 -tracks/14/loop_wrap = true -tracks/14/keys = { -"times": PackedFloat32Array(0), -"transitions": PackedFloat32Array(1), -"update": 0, -"values": [0.0] -} - -[sub_resource type="Animation" id="Animation_ytadt"] -resource_name = "loom_offering" -length = 5.0 -tracks/0/type = "value" -tracks/0/imported = false -tracks/0/enabled = true -tracks/0/path = NodePath("LoomOfferingAnimation/Imagination:scale") -tracks/0/interp = 1 -tracks/0/loop_wrap = true -tracks/0/keys = { -"times": PackedFloat32Array(0, 0.8, 3.16667, 3.6), -"transitions": PackedFloat32Array(1, 1, 1, 1), -"update": 0, -"values": [Vector2(0.001, 0.001), Vector2(1, 1), Vector2(1, 1), Vector2(3, 3)] -} -tracks/1/type = "value" -tracks/1/imported = false -tracks/1/enabled = true -tracks/1/path = NodePath("LoomOfferingAnimation/Spirit:scale") -tracks/1/interp = 1 -tracks/1/loop_wrap = true -tracks/1/keys = { -"times": PackedFloat32Array(0, 0.766667, 4.16667, 4.6), -"transitions": PackedFloat32Array(1, 1, 1, 1), -"update": 0, -"values": [Vector2(0.001, 0.001), Vector2(1, 1), Vector2(1, 1), Vector2(3, 3)] -} -tracks/2/type = "value" -tracks/2/imported = false -tracks/2/enabled = true -tracks/2/path = NodePath("LoomOfferingAnimation/Memory:scale") -tracks/2/interp = 1 -tracks/2/loop_wrap = true -tracks/2/keys = { -"times": PackedFloat32Array(0, 0.766667, 2.33333, 2.8), -"transitions": PackedFloat32Array(1, 1, 1, 1), -"update": 0, -"values": [Vector2(0.001, 0.001), Vector2(1, 1), Vector2(1, 1), Vector2(3, 3)] -} -tracks/3/type = "value" -tracks/3/imported = false -tracks/3/enabled = true -tracks/3/path = NodePath("LoomOfferingAnimation/Imagination:visible") -tracks/3/interp = 1 -tracks/3/loop_wrap = true -tracks/3/keys = { -"times": PackedFloat32Array(0), -"transitions": PackedFloat32Array(1), -"update": 1, -"values": [true] -} -tracks/4/type = "value" -tracks/4/imported = false -tracks/4/enabled = true -tracks/4/path = NodePath("LoomOfferingAnimation/Spirit:visible") -tracks/4/interp = 1 -tracks/4/loop_wrap = true -tracks/4/keys = { -"times": PackedFloat32Array(0), -"transitions": PackedFloat32Array(1), -"update": 1, -"values": [true] -} -tracks/5/type = "value" -tracks/5/imported = false -tracks/5/enabled = true -tracks/5/path = NodePath("LoomOfferingAnimation/Memory:visible") -tracks/5/interp = 1 -tracks/5/loop_wrap = true -tracks/5/keys = { -"times": PackedFloat32Array(0), -"transitions": PackedFloat32Array(1), -"update": 1, -"values": [true] -} -tracks/6/type = "value" -tracks/6/imported = false -tracks/6/enabled = true -tracks/6/path = NodePath("LoomOfferingAnimation/Spirit:position") -tracks/6/interp = 2 -tracks/6/loop_wrap = true -tracks/6/keys = { -"times": PackedFloat32Array(0, 0.77, 1.5, 2.35, 3.18333, 4.15), -"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1), -"update": 0, -"values": [Vector2(-48, 33), Vector2(50, 33), Vector2(1, -34), Vector2(-48, 33), Vector2(50, 33), Vector2(1, -34)] -} -tracks/7/type = "value" -tracks/7/imported = false -tracks/7/enabled = true -tracks/7/path = NodePath("LoomOfferingAnimation/Memory:position") -tracks/7/interp = 2 -tracks/7/loop_wrap = true -tracks/7/keys = { -"times": PackedFloat32Array(0, 0.77, 1.49, 2.35), -"transitions": PackedFloat32Array(1, 1, 1, 1), -"update": 0, -"values": [Vector2(1, -34), Vector2(-48, 33), Vector2(50, 33), Vector2(1, -34)] -} -tracks/8/type = "value" -tracks/8/imported = false -tracks/8/enabled = true -tracks/8/path = NodePath("LoomOfferingAnimation/Imagination:position") -tracks/8/interp = 2 -tracks/8/loop_wrap = true -tracks/8/keys = { -"times": PackedFloat32Array(0, 0.77, 1.5, 2.33333, 3.16667), -"transitions": PackedFloat32Array(1, 1, 1, 1, 1), -"update": 0, -"values": [Vector2(50, 33), Vector2(1, -34), Vector2(-48, 33), Vector2(50, 33), Vector2(1, -34)] -} -tracks/9/type = "value" -tracks/9/imported = false -tracks/9/enabled = true -tracks/9/path = NodePath("LoomOfferingAnimation/Spirit:modulate") -tracks/9/interp = 1 -tracks/9/loop_wrap = true -tracks/9/keys = { -"times": PackedFloat32Array(0, 4.16667, 4.60667), -"transitions": PackedFloat32Array(1, 1, 1), -"update": 0, -"values": [Color(1, 1, 1, 1), Color(1, 1, 1, 1), Color(1, 1, 1, 0)] -} -tracks/10/type = "value" -tracks/10/imported = false -tracks/10/enabled = true -tracks/10/path = NodePath("LoomOfferingAnimation/Memory:modulate") -tracks/10/interp = 1 -tracks/10/loop_wrap = true -tracks/10/keys = { -"times": PackedFloat32Array(0, 2.34333, 2.78333), -"transitions": PackedFloat32Array(1, 1, 1), -"update": 0, -"values": [Color(1, 1, 1, 1), Color(1, 1, 1, 1), Color(1, 1, 1, 0)] -} -tracks/11/type = "value" -tracks/11/imported = false -tracks/11/enabled = true -tracks/11/path = NodePath("LoomOfferingAnimation/Imagination:modulate") -tracks/11/interp = 1 -tracks/11/loop_wrap = true -tracks/11/keys = { -"times": PackedFloat32Array(0, 3.16667, 3.63333), -"transitions": PackedFloat32Array(1, 1, 1), -"update": 0, -"values": [Color(1, 1, 1, 1), Color(1, 1, 1, 1), Color(1, 1, 1, 0)] -} -tracks/12/type = "value" -tracks/12/imported = false -tracks/12/enabled = true -tracks/12/path = NodePath("AnimatedSprite2D:modulate") -tracks/12/interp = 1 -tracks/12/loop_wrap = true -tracks/12/keys = { -"times": PackedFloat32Array(0, 1.7, 2.46667, 3.03333, 3.33333, 3.66667, 4.03333, 4.46667, 4.86667), -"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1), -"update": 0, -"values": [Color(1, 1, 1, 1), Color(1, 1, 1, 1), Color(1, 1, 1, 1), Color(0, 0.972549, 0, 1), Color(0, 0.972549, 0, 1), Color(1, 1, 0, 1), Color(1, 1, 0, 1), Color(1, 0, 0, 1), Color(1, 1, 1, 1)] -} -tracks/13/type = "value" -tracks/13/imported = false -tracks/13/enabled = true -tracks/13/path = NodePath("LoomOfferingAnimation/LoomOfferingSound:playing") -tracks/13/interp = 1 -tracks/13/loop_wrap = true -tracks/13/keys = { -"times": PackedFloat32Array(0), -"transitions": PackedFloat32Array(1), -"update": 1, -"values": [true] -} -tracks/14/type = "value" -tracks/14/imported = false -tracks/14/enabled = true -tracks/14/path = NodePath("LoomOfferingAnimation/LoomOfferingSound:volume_db") -tracks/14/interp = 1 -tracks/14/loop_wrap = true -tracks/14/keys = { -"times": PackedFloat32Array(0, 4.33333, 5), -"transitions": PackedFloat32Array(1, 1, 1), -"update": 0, -"values": [0.0, 0.0, -60.0] -} - -[sub_resource type="AnimationLibrary" id="AnimationLibrary_x4prg"] +[sub_resource type="Curve2D" id="Curve2D_f3ofd"] _data = { -&"RESET": SubResource("Animation_x4prg"), -&"loom_offering": SubResource("Animation_ytadt") +"points": PackedVector2Array(0, 0, 0, 0, 0, -80, 0, -80, 0, 80, -80, 0, 0, 0, 0, 0, 0, 80, 0, 80, 0, -80, 80, 0, 0, 0, 0, 0, 0, -80) } +point_count = 5 [node name="EternalLoom" type="Node2D" unique_id=630179356] script = ExtResource("1_0f752") @@ -556,7 +187,7 @@ metadata/_custom_type_script = "uid://du8wfijr35r35" [node name="CollisionShape2D" type="CollisionShape2D" parent="InteractArea" unique_id=1537085446] position = Vector2(0, 120) shape = SubResource("RectangleShape2D_uhynt") -debug_color = Color(0.600391, 0.54335, 0, 0.42) +debug_color = Color(0.6, 0.545, 0, 0.42) [node name="Marker" type="Marker2D" parent="InteractArea" unique_id=556753843] position = Vector2(0, -152) @@ -569,28 +200,14 @@ interact_area = NodePath("../InteractArea") metadata/_custom_type_script = "uid://edcifob4jc4s" [node name="LoomOfferingAnimation" type="Node2D" parent="." unique_id=515569025] +unique_name_in_owner = true z_index = 1 position = Vector2(0, 135) +script = ExtResource("6_rle0d") -[node name="LoomOfferingAnimationPlayer" type="AnimationPlayer" parent="LoomOfferingAnimation" unique_id=1836718462] +[node name="AnimationPath" type="Path2D" parent="LoomOfferingAnimation" unique_id=1718786189] unique_name_in_owner = true -root_node = NodePath("../..") -libraries/ = SubResource("AnimationLibrary_x4prg") - -[node name="Memory" type="Sprite2D" parent="LoomOfferingAnimation" unique_id=480436827] -visible = false -position = Vector2(1, -34) -texture = ExtResource("4_12bvc") - -[node name="Imagination" type="Sprite2D" parent="LoomOfferingAnimation" unique_id=1315423320] -visible = false -position = Vector2(50, 33) -texture = ExtResource("5_eq7jh") - -[node name="Spirit" type="Sprite2D" parent="LoomOfferingAnimation" unique_id=1417706099] -visible = false -position = Vector2(-48, 33) -texture = ExtResource("6_hfqem") +curve = SubResource("Curve2D_f3ofd") [node name="LoomOfferingSound" type="AudioStreamPlayer2D" parent="LoomOfferingAnimation" unique_id=1851096463] stream = ExtResource("7_12bvc")