extends Control class_name SelectionManager var draw_selection := false var drag_start: Vector2 var select_box: Rect2 var perform_selection := false # Flag to trigger selection in _physics_process @onready var box_color: Color = ProjectSettings.get_setting("game/interface/selection_rectangle_color", Color("#00ff0066")) @onready var box_color_outline: Color = ProjectSettings.get_setting("game/interface/selection_rectangle_color_outline", Color("#00ff00")) @onready var optimized_rect: bool = ProjectSettings.get_setting("game/interface/optimized_rectangle", false) @export var rect_style: StyleBoxFlat @onready var cam = get_viewport().get_camera_3d() func _ready() -> void: if optimized_rect:return func _unhandled_input(e: InputEvent) -> void: if e is InputEventMouseButton and e.button_index == MOUSE_BUTTON_LEFT: if e.pressed: draw_selection = true drag_start = e.position else: # When button is released draw_selection = false if drag_start.is_equal_approx(e.position): # Single click: set a zero-sized selection box select_box = Rect2(e.position, Vector2.ZERO) print("one click") else: # Drag: calculate the final selection box var x_min = min(drag_start.x, e.position.x) var y_min = min(drag_start.y, e.position.y) select_box = Rect2(x_min, y_min, max(drag_start.x, e.position.x) - x_min, max(drag_start.y, e.position.y) - y_min) perform_selection = true # Trigger selection in _physics_process queue_redraw() elif draw_selection and e is InputEventMouseMotion: # Update selection box for drawing during drag var x_min = min(drag_start.x, e.position.x) var y_min = min(drag_start.y, e.position.y) select_box = Rect2(x_min, y_min, max(drag_start.x, e.position.x) - x_min, max(drag_start.y, e.position.y) - y_min) queue_redraw() func _physics_process(_delta: float) -> void: if perform_selection: update_selected_units() perform_selection = false func check_raycast(mouse_pos: Vector2, collision_mask: int = 4294967295) -> Dictionary: var from = cam.project_ray_origin(mouse_pos) var to = from + cam.project_ray_normal(mouse_pos) * 1000 # Magic number var query = PhysicsRayQueryParameters3D.new() query.from = from query.to = to query.collision_mask = collision_mask var space_state = cam.get_world_3d().direct_space_state #DebugTools.DrawLine(from,to,25.0) return space_state.intersect_ray(query) func update_selected_units() -> void: var units_array = get_tree().get_nodes_in_group("selectable-entity") var raycast = {} # Only perform raycast if it's a single click (zero-sized select_box) if select_box.size == Vector2.ZERO: raycast = check_raycast(select_box.position) if raycast: print("found ",raycast.collider.name) #breakpoint for entity: Entity in units_array: # TODO: Optimize by searching only in spawned chunks if select_box.size == Vector2.ZERO: # Single click: select entity under mouse via raycast if raycast and entity.is_under_mouse(raycast): entity.select() else: entity.deselect() else: # Drag: select entities within the selection box if entity.is_in_selection(select_box, cam): entity.select() else: entity.deselect() func _draw() -> void: if not (draw_selection or perform_selection):return if select_box and rect_style: if optimized_rect: draw_rect(select_box,box_color) draw_rect(select_box,box_color_outline,false,2.0,true) elif rect_style: draw_style_box(rect_style,select_box)