Merge pull request #325 from Excello-Recherche-Education/optimize-turtle-minigame

Optimize turtle minigame
This commit is contained in:
Adrien Ufferte
2026-06-09 15:46:43 +02:00
committed by GitHub
6 changed files with 87 additions and 94 deletions
+5
View File
@@ -6,6 +6,10 @@ const GAUGE_COLOR_LOW: Color = Color(0.55, 0.95, 0.45)
const GAUGE_COLOR_HIGH: Color = Color(0.996078, 0.776471, 0.2)
const MONKEY_SCENE_PATH: String = "res://sources/minigames/monkeys/monkey.tscn"
const TURTLE_SCENE_PATH: String = "res://sources/minigames/turtles/turtle.tscn"
# turtle.tscn no longer carries a default SpriteFrames (the three color
# spritesheets are ~123 MB of VRAM each and the turtles minigame picks one
# at runtime). For the static boss-friend turtle, pick one here.
const TURTLE_FRIEND_SPRITE_FRAMES_PATH: String = "res://sources/minigames/turtles/purple_turtle_animations.tres"
const PENGUIN_SCENE_PATH: String = "res://sources/minigames/penguin/penguin.tscn"
const FROG_SCENE_PATH: String = "res://sources/minigames/frog/frog.tscn"
const CRAB_SCENE_PATH: String = "res://sources/minigames/crabs/crab/crab.tscn"
@@ -103,6 +107,7 @@ func _instantiate_animal_friends() -> void:
turtle.rotation = 1.5707964
turtle.scale = Vector2(0.18, 0.18)
friends_container.add_child(turtle)
turtle.sprite_frames = load(TURTLE_FRIEND_SPRITE_FRAMES_PATH)
await get_tree().process_frame
if not is_inside_tree():
+41 -18
View File
@@ -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
-36
View File
@@ -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
+15 -3
View File
@@ -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))
+24 -4
View File
@@ -2,14 +2,34 @@ class_name Water
extends TextureRect
const WATER_RING_SCENE: PackedScene = preload("res://sources/utils/fx/water_ring.tscn")
# Rings are checked out for the full 3.0s particle lifetime.
# Worst case: 5 turtles (MAX_TURTLE_COUNT) each looping the swim animation
# every 8/12 = 0.667s emit ~7.5 rings/sec → ~23 concurrent in flight.
# We pre-allocate 24 and grow on demand if real usage ever exceeds that.
const POOL_SIZE: int = 24
@export var ring_color: Color
var _available_rings: Array[WaterRingFX] = []
func _ready() -> void:
for _index: int in POOL_SIZE:
_available_rings.append(_make_ring())
func spawn_water_ring(pos: Vector2) -> void:
var fx: WaterRingFX = WATER_RING_SCENE.instantiate()
var fx: WaterRingFX = _available_rings.pop_back() if not _available_rings.is_empty() else _make_ring()
fx.position = pos
fx.modulate = ring_color
add_child(fx)
fx.show()
await fx.play()
fx.queue_free()
fx.hide()
_available_rings.append(fx)
func _make_ring() -> WaterRingFX:
var fx: WaterRingFX = WATER_RING_SCENE.instantiate()
fx.modulate = ring_color
fx.hide()
add_child(fx)
return fx
+2 -33
View File
@@ -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);
}