extends Control @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] = [] var fail_count := 0 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 func _ready(): 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() else: $ExitButtonWindow.hide() $Blocker.visible = false 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 fail_count = 0 completed = false 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: var points := PackedVector2Array() 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)) return points 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