2025-04-26 20:13:48 +00:00
|
|
|
extends Control
|
2025-04-09 08:15:55 +00:00
|
|
|
|
2025-04-26 20:13:48 +00:00
|
|
|
@export var bg_color = Color.SKY_BLUE
|
|
|
|
|
@export var shape_name := "linea"
|
|
|
|
|
@export var line_width := 60.0
|
|
|
|
|
@export var line_color = Color.GRAY
|
|
|
|
|
@export var shape_scale := 0.9
|
|
|
|
|
@export var resolution := 360
|
|
|
|
|
@export var tracker_radius := 100
|
|
|
|
|
@export var follow_threshold := 100.0
|
|
|
|
|
|
|
|
|
|
@onready var shape_line = $MarginContainer/VBoxContainer/Control/ShapeLine
|
|
|
|
|
@onready var shape_area = $MarginContainer/VBoxContainer/Control/ShapeArea
|
|
|
|
|
@onready var tracker = $MarginContainer/VBoxContainer/Control/Tracker
|
|
|
|
|
@onready var tracker_shape = $MarginContainer/VBoxContainer/Control/Tracker/CollisionShape2D
|
|
|
|
|
@onready var collision_polygon = $MarginContainer/VBoxContainer/Control/ShapeArea/CollisionPolygon2D
|
|
|
|
|
|
|
|
|
|
@onready var menu = $MarginContainer/VBoxContainer/HBoxContainer/MenuButton
|
|
|
|
|
@onready var exit_menu_confirm_exit_button = $ExitButtonWindow/VBoxContainer/HBoxContainer/ConfirmExitButton
|
|
|
|
|
@onready var exit_menu_dont_exit_button = $ExitButtonWindow/VBoxContainer/HBoxContainer/DontExitButton
|
|
|
|
|
|
|
|
|
|
@onready var audio_player = $AudioStreamPlayer2D
|
|
|
|
|
|
|
|
|
|
var shape_points := PackedVector2Array()
|
|
|
|
|
var progress: Array[bool] = []
|
2025-04-12 15:51:49 +00:00
|
|
|
|
|
|
|
|
var fail_count := 0
|
2025-04-26 20:13:48 +00:00
|
|
|
var last_frame_inside := true
|
|
|
|
|
var completed := false
|
|
|
|
|
|
|
|
|
|
var shapes: Array[Dictionary] = [
|
|
|
|
|
{"name": "linea"},
|
|
|
|
|
{"name": "circulo"},
|
|
|
|
|
{"name": "corazon"},
|
|
|
|
|
]
|
|
|
|
|
var shape_order = []
|
|
|
|
|
var ordered_shapes = []
|
|
|
|
|
var current_index = 0
|
2025-04-09 08:15:55 +00:00
|
|
|
|
|
|
|
|
func _ready():
|
2025-04-26 20:13:48 +00:00
|
|
|
RenderingServer.set_default_clear_color(bg_color)
|
|
|
|
|
|
|
|
|
|
audio_player.play()
|
|
|
|
|
|
|
|
|
|
tracker_shape.shape = CircleShape2D.new()
|
|
|
|
|
tracker_shape.shape.radius = tracker_radius
|
|
|
|
|
|
|
|
|
|
# Generar lista ordenada
|
|
|
|
|
for name in shape_order:
|
|
|
|
|
for shape in shapes:
|
|
|
|
|
if shape["name"] == name:
|
|
|
|
|
ordered_shapes.append(shape)
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
menu.get_popup().add_item("Reproducir sonido", 0)
|
|
|
|
|
menu.get_popup().add_item("Salir", 1)
|
|
|
|
|
menu.get_popup().connect("id_pressed", self._on_menu_item_selected)
|
|
|
|
|
|
|
|
|
|
exit_menu_confirm_exit_button.pressed.connect(_on_exit_menu_button_pressed.bind(true))
|
|
|
|
|
exit_menu_dont_exit_button.pressed.connect(_on_exit_menu_button_pressed.bind(false))
|
|
|
|
|
|
|
|
|
|
# Cargar primera forma
|
|
|
|
|
load_shape(ordered_shapes[current_index])
|
|
|
|
|
|
|
|
|
|
func _on_menu_item_selected(id):
|
|
|
|
|
match id:
|
|
|
|
|
0:
|
|
|
|
|
audio_player.play()
|
|
|
|
|
1:
|
|
|
|
|
$ExitButtonWindow.show()
|
|
|
|
|
$Blocker.visible = true
|
|
|
|
|
|
|
|
|
|
func _on_exit_menu_button_pressed(exit : bool):
|
|
|
|
|
if exit:
|
|
|
|
|
GameData.clear_active_games()
|
|
|
|
|
ScreenManager.go_back_to_previous_scene()
|
2025-04-12 15:51:49 +00:00
|
|
|
else:
|
2025-04-26 20:13:48 +00:00
|
|
|
$ExitButtonWindow.hide()
|
|
|
|
|
$Blocker.visible = false
|
2025-04-12 15:51:49 +00:00
|
|
|
|
|
|
|
|
|
2025-04-26 20:13:48 +00:00
|
|
|
func load_shape(shape_data: Dictionary):
|
|
|
|
|
var shape_name = shape_data["name"]
|
|
|
|
|
var center = get_rect().size / 2
|
|
|
|
|
var radius = calculate_scale()
|
|
|
|
|
shape_points = generate_shape_points(shape_name, center, radius)
|
|
|
|
|
draw_shape(shape_points)
|
|
|
|
|
collision_polygon.polygon = shape_points
|
2025-04-12 15:51:49 +00:00
|
|
|
fail_count = 0
|
2025-04-26 20:13:48 +00:00
|
|
|
completed = false
|
2025-04-12 15:51:49 +00:00
|
|
|
|
|
|
|
|
|
2025-04-26 20:13:48 +00:00
|
|
|
func _on_next_shape_pressed():
|
|
|
|
|
var shape_name = ordered_shapes[current_index]["name"]
|
|
|
|
|
|
|
|
|
|
if not completed:
|
|
|
|
|
GameData.remove_active_game(shape_name)
|
|
|
|
|
|
|
|
|
|
current_index += 1
|
|
|
|
|
if current_index < ordered_shapes.size():
|
|
|
|
|
load_shape(ordered_shapes[current_index])
|
|
|
|
|
else:
|
|
|
|
|
$EndOfShapesWindow.show()
|
|
|
|
|
$Blocker.visible = true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _on_end_of_game_button():
|
|
|
|
|
ScreenManager.go_back_to_previous_scene()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func calculate_scale() -> float:
|
|
|
|
|
var size = get_rect().size
|
|
|
|
|
return min(size.x, size.y) * shape_scale / 2.0
|
|
|
|
|
|
|
|
|
|
func generate_shape_points(shape: String, center: Vector2, radius: float) -> PackedVector2Array:
|
2025-04-12 15:51:49 +00:00
|
|
|
var points := PackedVector2Array()
|
2025-04-26 20:13:48 +00:00
|
|
|
|
|
|
|
|
match shape:
|
|
|
|
|
"circulo":
|
|
|
|
|
for i in range(resolution + 1):
|
|
|
|
|
var angle = TAU * i / resolution
|
|
|
|
|
points.append(center + Vector2(cos(angle), sin(angle)) * radius)
|
|
|
|
|
|
|
|
|
|
"corazon":
|
|
|
|
|
for i in range(resolution + 1):
|
|
|
|
|
var t = TAU * i / resolution
|
|
|
|
|
|
|
|
|
|
var x = 16 * pow(sin(t), 3)
|
|
|
|
|
var y = 13 * cos(t) - 5 * cos(2 * t) - 2 * cos(3 * t) - cos(4 * t)
|
|
|
|
|
|
|
|
|
|
var point = Vector2(x, -y) * (radius / 17.0)
|
|
|
|
|
points.append(center + point)
|
|
|
|
|
|
|
|
|
|
"linea":
|
|
|
|
|
for i in range(resolution + 1):
|
|
|
|
|
var t = float(i) / resolution
|
|
|
|
|
points.append(center + Vector2(-radius + t * radius * 2, 0))
|
|
|
|
|
|
2025-04-12 15:51:49 +00:00
|
|
|
return points
|
2025-04-26 20:13:48 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
func draw_shape(points: PackedVector2Array):
|
|
|
|
|
shape_line.clear_points()
|
|
|
|
|
progress.clear()
|
|
|
|
|
shape_line.width = line_width
|
|
|
|
|
completed = false
|
|
|
|
|
|
|
|
|
|
for point in points:
|
|
|
|
|
shape_line.add_point(point)
|
|
|
|
|
progress.append(false)
|
|
|
|
|
|
|
|
|
|
update_line_gradient()
|
|
|
|
|
|
|
|
|
|
func _process(_delta):
|
|
|
|
|
var pos = get_local_mouse_position()
|
|
|
|
|
tracker.position = pos
|
|
|
|
|
|
|
|
|
|
var is_inside = tracker.get_overlapping_areas().has(shape_area)
|
|
|
|
|
|
|
|
|
|
if not is_inside and last_frame_inside:
|
|
|
|
|
fail_count += 1
|
|
|
|
|
GameData.add_failure_to_shape(ordered_shapes[current_index -1]["name"])
|
|
|
|
|
print("❌ Fallo detectado. Total fallos:", fail_count)
|
|
|
|
|
last_frame_inside = is_inside
|
|
|
|
|
|
|
|
|
|
var closest_index := -1
|
|
|
|
|
var closest_dist := follow_threshold
|
|
|
|
|
|
|
|
|
|
for i in range(shape_points.size()):
|
|
|
|
|
var dist = pos.distance_to(shape_points[i])
|
|
|
|
|
if dist < closest_dist:
|
|
|
|
|
closest_index = i
|
|
|
|
|
closest_dist = dist
|
|
|
|
|
|
|
|
|
|
if closest_index != -1:
|
|
|
|
|
var window = 5
|
|
|
|
|
for offset in range(-window, window + 1):
|
|
|
|
|
var index = clamp(closest_index + offset, 0, shape_points.size() - 1)
|
|
|
|
|
progress[index] = true
|
|
|
|
|
|
|
|
|
|
update_line_gradient()
|
|
|
|
|
check_completion()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func update_line_gradient():
|
|
|
|
|
var grad := Gradient.new()
|
|
|
|
|
var colors := []
|
|
|
|
|
var offsets := []
|
|
|
|
|
|
|
|
|
|
for i in range(progress.size()):
|
|
|
|
|
var t = float(i) / float(progress.size() - 1)
|
|
|
|
|
offsets.append(t)
|
|
|
|
|
if progress[i]:
|
|
|
|
|
colors.append(Color.GREEN)
|
|
|
|
|
else:
|
|
|
|
|
colors.append(line_color)
|
|
|
|
|
|
|
|
|
|
grad.colors = colors
|
|
|
|
|
grad.offsets = offsets
|
|
|
|
|
shape_line.gradient = grad
|
|
|
|
|
|
|
|
|
|
func check_completion():
|
|
|
|
|
if not completed and progress.all(func(p): return p):
|
|
|
|
|
completed = true
|
|
|
|
|
await get_tree().create_timer(1.0).timeout # espera 1 segundo
|
|
|
|
|
|
|
|
|
|
# Llamar a end_game para registrar la forma completada
|
|
|
|
|
var shape_name = ordered_shapes[current_index]["name"]
|
|
|
|
|
await GameData.end_game(shape_name)
|
|
|
|
|
|
|
|
|
|
current_index += 1
|
|
|
|
|
if current_index < ordered_shapes.size():
|
|
|
|
|
load_shape(ordered_shapes[current_index])
|
|
|
|
|
else:
|
|
|
|
|
$EndOfShapesWindow.show()
|
|
|
|
|
$Blocker.visible = true
|