From f50a739b3df5761b99cf50d63b5656bbddc95cee Mon Sep 17 00:00:00 2001 From: Adrien Ufferte Date: Tue, 9 Jun 2026 11:43:23 +0200 Subject: [PATCH] Optimize turtle minigame --- sources/minigames/turtles/turtle.gd | 59 +++++++++++++------ sources/minigames/turtles/turtle.tscn | 36 ----------- sources/minigames/turtles/turtles_minigame.gd | 18 +++++- sources/minigames/turtles/water.gd | 25 ++++++-- sources/minigames/turtles/water.gdshader | 35 +---------- 5 files changed, 79 insertions(+), 94 deletions(-) diff --git a/sources/minigames/turtles/turtle.gd b/sources/minigames/turtles/turtle.gd index a9cbed90..26d19389 100644 --- a/sources/minigames/turtles/turtle.gd +++ b/sources/minigames/turtles/turtle.gd @@ -4,25 +4,22 @@ extends Node2D signal pressed(gp: Dictionary) signal animation_changed(position: Vector2) -enum Colors { - GREEN, - KHAKI, - PURPLE, -} - -const ANIMATIONS: Array[SpriteFrames] = [ - preload("res://sources/minigames/turtles/green_turtle_animations.tres"), - preload("res://sources/minigames/turtles/khaki_turtle_animations.tres"), - preload("res://sources/minigames/turtles/purple_turtle_animations.tres") -] const TURTLE_BACK_RIGHT: CompressedTexture2D = preload("res://assets/minigames/turtles/graphic/turtle_back_right.png") const TURTLE_BACK_WRONG: CompressedTexture2D = preload("res://assets/minigames/turtles/graphic/turtle_back_wrong.png") +# Only used when a turtle lands on the island, so they're instantiated on +# demand instead of sitting idle inside every spawned turtle. +const RIGHT_FX_SCENE: PackedScene = preload("res://sources/utils/fx/right.tscn") +const WRONG_FX_SCENE: PackedScene = preload("res://sources/utils/fx/wrong.tscn") +const RIGHT_STARS_SCENE: PackedScene = preload("res://sources/utils/fx/right_stars.tscn") -@export var color: Colors = Colors.PURPLE: +# Set by the minigame after the random color is picked, so only one of the three +# 6400x4800 turtle spritesheets is ever in memory. +@export var sprite_frames: SpriteFrames: set(value): - color = value - if sprite: - sprite.sprite_frames = ANIMATIONS[color] + sprite_frames = value + if sprite and value: + sprite.sprite_frames = value + sprite.play("swim") var gp: Dictionary = {}: set(value): @@ -37,6 +34,9 @@ var direction: Vector2 = Vector2(0,-1): var is_moving: bool = true var is_changing_direction: bool = false var is_visible_on_screen: bool = false +var right_fx: RightFX +var right_stars: RightStarsFX +var wrong_fx: WrongFX @onready var body: Node2D = $Body @onready var body_back: Sprite2D = $Body/AnimatedSprite2D/Sprite2D_Back @@ -45,9 +45,8 @@ var is_visible_on_screen: bool = false @onready var head_area_collision_shape: CollisionShape2D = $Body/HeadArea/CollisionShape2D @onready var body_area_collision_shape: CollisionShape2D = $Body/BodyArea/CollisionShape2D @onready var highlight_fx: HighlightFX = %HighlightFX -@onready var right_fx: RightFX = %RightFX -@onready var right_stars: RightStarsFX = %Right_Stars -@onready var wrong_fx: WrongFX = %WrongFX +@onready var back_fx: Control = $BackFX +@onready var front_fx: Control = $FrontFX @onready var delete_timer: Timer = $DeleteTimer @onready var audio_stream_player: AudioStreamPlayer2D = $AudioStreamPlayer2D @@ -92,6 +91,28 @@ func highlight(value: bool = true) -> void: highlight_fx.stop() +func _ensure_right_fx() -> void: + if right_fx: + return + right_fx = RIGHT_FX_SCENE.instantiate() + right_fx.set_anchors_and_offsets_preset(Control.PRESET_CENTER) + right_fx.size_flags_horizontal = Control.SIZE_SHRINK_CENTER + right_fx.size_flags_vertical = Control.SIZE_SHRINK_CENTER + back_fx.add_child(right_fx) + right_stars = RIGHT_STARS_SCENE.instantiate() + front_fx.add_child(right_stars) + + +func _ensure_wrong_fx() -> void: + if wrong_fx: + return + wrong_fx = WRONG_FX_SCENE.instantiate() + wrong_fx.set_anchors_and_offsets_preset(Control.PRESET_CENTER) + wrong_fx.size_flags_horizontal = Control.SIZE_SHRINK_CENTER + wrong_fx.size_flags_vertical = Control.SIZE_SHRINK_CENTER + back_fx.add_child(wrong_fx) + + func right() -> void: is_moving = false change_font_color_after_collision() @@ -99,6 +120,7 @@ func right() -> void: sprite.play("victory") await sprite.animation_finished sprite.play_backwards("victory") + _ensure_right_fx() right_fx.play() right_stars.play() await sprite.animation_finished @@ -109,6 +131,7 @@ func wrong() -> void: is_moving = false change_font_color_after_collision() body_back.texture = TURTLE_BACK_WRONG + _ensure_wrong_fx() wrong_fx.play() sprite.play("defeat") await sprite.animation_finished diff --git a/sources/minigames/turtles/turtle.tscn b/sources/minigames/turtles/turtle.tscn index 58a914cb..038b8aba 100644 --- a/sources/minigames/turtles/turtle.tscn +++ b/sources/minigames/turtles/turtle.tscn @@ -1,13 +1,9 @@ [gd_scene format=3 uid="uid://djykrpdu58f3v"] [ext_resource type="Script" uid="uid://doq6myv45i4w6" path="res://sources/minigames/turtles/turtle.gd" id="1_vx1yr"] -[ext_resource type="PackedScene" uid="uid://cn2rw06pltyiu" path="res://sources/utils/fx/right.tscn" id="2_d5x3l"] [ext_resource type="PackedScene" uid="uid://cge0uyn30tcpv" path="res://sources/utils/fx/highlight.tscn" id="2_rllc5"] -[ext_resource type="PackedScene" uid="uid://dlmbxcgiv8tpr" path="res://sources/utils/fx/wrong.tscn" id="4_yhidj"] -[ext_resource type="SpriteFrames" uid="uid://gm2hqn76oaib" path="res://sources/minigames/turtles/purple_turtle_animations.tres" id="5_xbpgw"] [ext_resource type="Texture2D" uid="uid://bhahju7y2n5ke" path="res://assets/minigames/turtles/graphic/turtle_back_neutral.png" id="6_4juk7"] [ext_resource type="LabelSettings" uid="uid://dn32fpff81uu" path="res://resources/themes/minigames_label_settings_turtles.tres" id="6_8k7c6"] -[ext_resource type="PackedScene" uid="uid://cs6g7fhc0bjvh" path="res://sources/utils/fx/right_stars.tscn" id="8_xbpgw"] [ext_resource type="AudioStream" uid="uid://cmsn7q4fxnghr" path="res://assets/sfx/splash.mp3" id="42_p3fqk"] [ext_resource type="PackedScene" uid="uid://cjg6pgc7yfp1p" path="res://sources/utils/swipe_detector.tscn" id="49_0qwwy"] @@ -29,19 +25,6 @@ anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 -[node name="RightFX" parent="BackFX" unique_id=1787877717 instance=ExtResource("2_d5x3l")] -unique_name_in_owner = true -layout_mode = 1 -anchors_preset = 8 -anchor_left = 0.5 -anchor_top = 0.5 -anchor_right = 0.5 -anchor_bottom = 0.5 -grow_horizontal = 2 -grow_vertical = 2 -size_flags_horizontal = 4 -size_flags_vertical = 4 - [node name="HighlightFX" parent="BackFX" unique_id=462139651 instance=ExtResource("2_rllc5")] unique_name_in_owner = true layout_mode = 1 @@ -54,19 +37,6 @@ grow_horizontal = 2 grow_vertical = 2 scale = Vector2(0.6, 0.6) -[node name="WrongFX" parent="BackFX" unique_id=777978196 instance=ExtResource("4_yhidj")] -unique_name_in_owner = true -layout_mode = 1 -anchors_preset = 8 -anchor_left = 0.5 -anchor_top = 0.5 -anchor_right = 0.5 -anchor_bottom = 0.5 -grow_horizontal = 2 -grow_vertical = 2 -size_flags_horizontal = 4 -size_flags_vertical = 4 - [node name="AudioStreamPlayer2D" type="AudioStreamPlayer2D" parent="." unique_id=799402224] stream = ExtResource("42_p3fqk") bus = &"Effects" @@ -77,9 +47,6 @@ bus = &"Effects" unique_name_in_owner = true position = Vector2(0, -16) scale = Vector2(0.8, 0.8) -sprite_frames = ExtResource("5_xbpgw") -animation = &"swim" -autoplay = "swim" [node name="Sprite2D_Back" type="Sprite2D" parent="Body/AnimatedSprite2D" unique_id=1980717066] position = Vector2(0, 19.999998) @@ -139,9 +106,6 @@ anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 -[node name="Right_Stars" parent="FrontFX" unique_id=1474432273 instance=ExtResource("8_xbpgw")] -unique_name_in_owner = true - [node name="DeleteTimer" type="Timer" parent="." unique_id=94467327] wait_time = 3.0 one_shot = true diff --git a/sources/minigames/turtles/turtles_minigame.gd b/sources/minigames/turtles/turtles_minigame.gd index 28578a9e..483c232e 100644 --- a/sources/minigames/turtles/turtles_minigame.gd +++ b/sources/minigames/turtles/turtles_minigame.gd @@ -7,6 +7,12 @@ const TURTLE_SCENE: PackedScene = preload("res://sources/minigames/turtles/turtl const MAX_TURTLE_COUNT: int = 5 # Defines the minimum distance between turtles when spawning them const MIN_DISTANCE: int = 500 +# Each spritesheet is ~123 MB of VRAM; never preload — load only the picked one. +const TURTLE_ANIMATIONS_PATHS: Array[String] = [ + "res://sources/minigames/turtles/green_turtle_animations.tres", + "res://sources/minigames/turtles/khaki_turtle_animations.tres", + "res://sources/minigames/turtles/purple_turtle_animations.tres", +] var difficulty_settings: Array[DifficultySettings] = [ DifficultySettings.new(.75, 200., 4.), @@ -22,7 +28,8 @@ var turtle_count: int = 0: can_spawn_turtle.emit() turtle_count = value var stimulus_spawned: bool = false -var color: Turtle.Colors +# Single shared SpriteFrames assigned to every spawned turtle this round. +var turtle_sprite_frames: SpriteFrames @onready var water: Water = $GameRoot/Water @onready var island: Island = $GameRoot/Island @@ -61,7 +68,12 @@ func _setup_minigame() -> void: func pick_random_color() -> void: - color = randi_range(0, Turtle.Colors.size() - 1) as Turtle.Colors + # Drop the previous color first so its ~123 MB texture can be freed by + # refcount before we load the next one (turtles still fading out also + # hold a ref, so peak overlap is brief). + turtle_sprite_frames = null + var index: int = randi_range(0, TURTLE_ANIMATIONS_PATHS.size() - 1) + turtle_sprite_frames = load(TURTLE_ANIMATIONS_PATHS[index]) func _highlight() -> void: @@ -121,7 +133,7 @@ func _on_spawn_timer_timeout() -> void: ) turtles.add_child(turtle) - turtle.color = color + turtle.sprite_frames = turtle_sprite_frames # Set the direction of the turtle var random_offset: float = deg_to_rad(randf_range(-5, 5)) diff --git a/sources/minigames/turtles/water.gd b/sources/minigames/turtles/water.gd index 84e45383..fb1c5a91 100644 --- a/sources/minigames/turtles/water.gd +++ b/sources/minigames/turtles/water.gd @@ -2,14 +2,31 @@ class_name Water extends TextureRect const WATER_RING_SCENE: PackedScene = preload("res://sources/utils/fx/water_ring.tscn") +# Up to MAX_TURTLE_COUNT (5) turtles each loop the swim animation roughly +# every 0.7s, so ~7-8 rings/second. The pool absorbs the bursts without +# instantiate/queue_free churn on every loop. +const POOL_SIZE: int = 12 @export var ring_color: Color +var _available_rings: Array[WaterRingFX] = [] + + +func _ready() -> void: + for _index: int in POOL_SIZE: + var fx: WaterRingFX = WATER_RING_SCENE.instantiate() + fx.modulate = ring_color + fx.hide() + add_child(fx) + _available_rings.append(fx) + func spawn_water_ring(pos: Vector2) -> void: - var fx: WaterRingFX = WATER_RING_SCENE.instantiate() + if _available_rings.is_empty(): + return + var fx: WaterRingFX = _available_rings.pop_back() fx.position = pos - fx.modulate = ring_color - add_child(fx) + fx.show() await fx.play() - fx.queue_free() + fx.hide() + _available_rings.append(fx) diff --git a/sources/minigames/turtles/water.gdshader b/sources/minigames/turtles/water.gdshader index 53c93f7d..f494a3d6 100644 --- a/sources/minigames/turtles/water.gdshader +++ b/sources/minigames/turtles/water.gdshader @@ -40,6 +40,8 @@ float circ(vec2 pos, vec2 c, float s) float waterlayer(vec2 uv) { + // Reduced from 73 to 42 circles by dropping radii < 0.01 — the smaller + // ones barely contributed visually but each circ() is ~10 GPU ops. uv = mod(uv, 1.0); float ret = 1.0; ret += circ(uv, vec2(0.37378, 0.277169), 0.0268181); @@ -52,71 +54,38 @@ float waterlayer(vec2 uv) ret += circ(uv, vec2(0.310149, 0.686637), 0.0128496); ret += circ(uv, vec2(0.928617, 0.195986), 0.0152041); ret += circ(uv, vec2(0.0438506, 0.868153), 0.0268601); - ret += circ(uv, vec2(0.308619, 0.194937), 0.00806102); - ret += circ(uv, vec2(0.349922, 0.449714), 0.00928667); ret += circ(uv, vec2(0.0449556, 0.953415), 0.023126); ret += circ(uv, vec2(0.117761, 0.503309), 0.0151272); ret += circ(uv, vec2(0.563517, 0.244991), 0.0292322); - ret += circ(uv, vec2(0.566936, 0.954457), 0.00981141); ret += circ(uv, vec2(0.0489944, 0.200931), 0.0178746); ret += circ(uv, vec2(0.569297, 0.624893), 0.0132408); ret += circ(uv, vec2(0.298347, 0.710972), 0.0114426); - ret += circ(uv, vec2(0.878141, 0.771279), 0.00322719); - ret += circ(uv, vec2(0.150995, 0.376221), 0.00216157); ret += circ(uv, vec2(0.119673, 0.541984), 0.0124621); ret += circ(uv, vec2(0.629598, 0.295629), 0.0198736); ret += circ(uv, vec2(0.334357, 0.266278), 0.0187145); ret += circ(uv, vec2(0.918044, 0.968163), 0.0182928); - ret += circ(uv, vec2(0.965445, 0.505026), 0.006348); - ret += circ(uv, vec2(0.514847, 0.865444), 0.00623523); - ret += circ(uv, vec2(0.710575, 0.0415131), 0.00322689); ret += circ(uv, vec2(0.71403, 0.576945), 0.0215641); ret += circ(uv, vec2(0.748873, 0.413325), 0.0110795); ret += circ(uv, vec2(0.0623365, 0.896713), 0.0236203); - ret += circ(uv, vec2(0.980482, 0.473849), 0.00573439); ret += circ(uv, vec2(0.647463, 0.654349), 0.0188713); - ret += circ(uv, vec2(0.651406, 0.981297), 0.00710875); ret += circ(uv, vec2(0.428928, 0.382426), 0.0298806); - ret += circ(uv, vec2(0.811545, 0.62568), 0.00265539); - ret += circ(uv, vec2(0.400787, 0.74162), 0.00486609); - ret += circ(uv, vec2(0.331283, 0.418536), 0.00598028); - ret += circ(uv, vec2(0.894762, 0.0657997), 0.00760375); ret += circ(uv, vec2(0.525104, 0.572233), 0.0141796); ret += circ(uv, vec2(0.431526, 0.911372), 0.0213234); - ret += circ(uv, vec2(0.658212, 0.910553), 0.000741023); ret += circ(uv, vec2(0.514523, 0.243263), 0.0270685); - ret += circ(uv, vec2(0.0249494, 0.252872), 0.00876653); ret += circ(uv, vec2(0.502214, 0.47269), 0.0234534); ret += circ(uv, vec2(0.693271, 0.431469), 0.0246533); ret += circ(uv, vec2(0.415, 0.884418), 0.0271696); - ret += circ(uv, vec2(0.149073, 0.41204), 0.00497198); - ret += circ(uv, vec2(0.533816, 0.897634), 0.00650833); ret += circ(uv, vec2(0.0409132, 0.83406), 0.0191398); ret += circ(uv, vec2(0.638585, 0.646019), 0.0206129); - ret += circ(uv, vec2(0.660342, 0.966541), 0.0053511); - ret += circ(uv, vec2(0.513783, 0.142233), 0.00471653); - ret += circ(uv, vec2(0.124305, 0.644263), 0.00116724); ret += circ(uv, vec2(0.99871, 0.583864), 0.0107329); - ret += circ(uv, vec2(0.894879, 0.233289), 0.00667092); - ret += circ(uv, vec2(0.246286, 0.682766), 0.00411623); ret += circ(uv, vec2(0.0761895, 0.16327), 0.0145935); ret += circ(uv, vec2(0.949386, 0.802936), 0.0100873); ret += circ(uv, vec2(0.480122, 0.196554), 0.0110185); ret += circ(uv, vec2(0.896854, 0.803707), 0.013969); - ret += circ(uv, vec2(0.292865, 0.762973), 0.00566413); - ret += circ(uv, vec2(0.0995585, 0.117457), 0.00869407); - ret += circ(uv, vec2(0.377713, 0.00335442), 0.0063147); ret += circ(uv, vec2(0.506365, 0.531118), 0.0144016); ret += circ(uv, vec2(0.408806, 0.894771), 0.0243923); - ret += circ(uv, vec2(0.143579, 0.85138), 0.00418529); ret += circ(uv, vec2(0.0902811, 0.181775), 0.0108896); - ret += circ(uv, vec2(0.780695, 0.394644), 0.00475475); - ret += circ(uv, vec2(0.298036, 0.625531), 0.00325285); - ret += circ(uv, vec2(0.218423, 0.714537), 0.00157212); - ret += circ(uv, vec2(0.658836, 0.159556), 0.00225897); ret += circ(uv, vec2(0.987324, 0.146545), 0.0288391); - ret += circ(uv, vec2(0.222646, 0.251694), 0.00092276); - ret += circ(uv, vec2(0.159826, 0.528063), 0.00605293); return max(ret, 0.0); }