Selection working.

Assuming every single selectable contain an Area3D at the root with selection collision to handle selection
This commit is contained in:
Lucas 2025-07-28 15:50:24 +02:00
parent 258ffc4523
commit 58df83ec50
No known key found for this signature in database
5 changed files with 57 additions and 40 deletions

View file

@ -1,4 +1,4 @@
[gd_scene load_steps=12 format=3 uid="uid://qbceryqqfoum"] [gd_scene load_steps=11 format=3 uid="uid://qbceryqqfoum"]
[ext_resource type="Texture2D" uid="uid://bsnc11pwypbny" path="res://packs/Kaykit-Proto/obj/prototypebits_texture.png" id="1_6oykk"] [ext_resource type="Texture2D" uid="uid://bsnc11pwypbny" path="res://packs/Kaykit-Proto/obj/prototypebits_texture.png" id="1_6oykk"]
[ext_resource type="Script" uid="uid://b8p60y4eo8w2n" path="res://core/scripts/entity.gd" id="1_w0ui8"] [ext_resource type="Script" uid="uid://b8p60y4eo8w2n" path="res://core/scripts/entity.gd" id="1_w0ui8"]
@ -8,7 +8,6 @@
[ext_resource type="ArrayMesh" uid="uid://cvlxxld3w6siv" path="res://packs/Kaykit-Proto/obj/Dummy_Base_Dummy_Body_Dummy_ArmRight.obj" id="5_w4l2r"] [ext_resource type="ArrayMesh" uid="uid://cvlxxld3w6siv" path="res://packs/Kaykit-Proto/obj/Dummy_Base_Dummy_Body_Dummy_ArmRight.obj" id="5_w4l2r"]
[ext_resource type="ArrayMesh" uid="uid://6h2lg4h0y0ea" path="res://packs/Kaykit-Proto/obj/Dummy_Base_Dummy_Body_Dummy_Head.obj" id="6_5dfyn"] [ext_resource type="ArrayMesh" uid="uid://6h2lg4h0y0ea" path="res://packs/Kaykit-Proto/obj/Dummy_Base_Dummy_Body_Dummy_Head.obj" id="6_5dfyn"]
[ext_resource type="ArrayMesh" uid="uid://b7cky6rmvhxl8" path="res://packs/Kaykit-Proto/obj/Dummy_Base_Dummy_Body_Dummy_Target.obj" id="7_d3okb"] [ext_resource type="ArrayMesh" uid="uid://b7cky6rmvhxl8" path="res://packs/Kaykit-Proto/obj/Dummy_Base_Dummy_Body_Dummy_Target.obj" id="7_d3okb"]
[ext_resource type="Texture2D" uid="uid://cg3hn4l231fws" path="res://packs/Kenney-BoardGamesIcon/chess_rook_red.svg" id="8_15r82"]
[sub_resource type="BoxShape3D" id="BoxShape3D_w0ui8"] [sub_resource type="BoxShape3D" id="BoxShape3D_w0ui8"]
size = Vector3(1, 2, 1) size = Vector3(1, 2, 1)
@ -21,10 +20,12 @@ transform = Transform3D(0.9, 0, 0, 0, 0.9, 0, 0, 0, 0.9, 0, 0, 0)
script = ExtResource("1_w0ui8") script = ExtResource("1_w0ui8")
metadata/_custom_type_script = "uid://b8p60y4eo8w2n" metadata/_custom_type_script = "uid://b8p60y4eo8w2n"
[node name="StaticBody3D" type="StaticBody3D" parent="."] [node name="SelectionArea" type="Area3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.0267677, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.0267677, 0)
collision_layer = 32768
collision_mask = 0
[node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D"] [node name="CollisionShape3D" type="CollisionShape3D" parent="SelectionArea"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.11408532, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.11408532, 0)
shape = SubResource("BoxShape3D_w0ui8") shape = SubResource("BoxShape3D_w0ui8")
@ -51,8 +52,3 @@ mesh = ExtResource("6_5dfyn")
[node name="DummyBaseDummyBodyDummyTarget" type="MeshInstance3D" parent="."] [node name="DummyBaseDummyBodyDummyTarget" type="MeshInstance3D" parent="."]
material_override = SubResource("StandardMaterial3D_th4ew") material_override = SubResource("StandardMaterial3D_th4ew")
mesh = ExtResource("7_d3okb") mesh = ExtResource("7_d3okb")
[node name="TextureRect" type="TextureRect" parent="."]
offset_right = 40.0
offset_bottom = 40.0
texture = ExtResource("8_15r82")

View file

@ -7,7 +7,7 @@ ignore_paths=Array[String]([])
[patterns] [patterns]
patterns=[["\\bTODO\\b", Color(0.588235, 0.945098, 0.678431, 1), 0], ["\\bHACK\\b", Color(0.835294, 0.737255, 0.439216, 1), 0], ["\\bFIXME\\b", Color(0.835294, 0.439216, 0.439216, 1), 0]] patterns=[["\\bTODO\\b", Color(0.588235, 0.945098, 0.678431, 1), 0], ["\\bHACK\\b", Color(0.835294, 0.737255, 0.439216, 1), 0], ["\\bFIXME\\b", Color(0.835294, 0.439216, 0.439216, 1), 0], ["\\bBUG\\b", Color(0.9607843, 0.48682597, 0.4509804, 1), 0]]
[config] [config]

View file

@ -1,5 +1,5 @@
@icon('uid://br8ndde8qty32') @icon('uid://br8ndde8qty32')
extends Node extends Node3D
class_name Entity class_name Entity
@ -11,8 +11,8 @@ class_name Entity
## Is a node, because we want to deactivate rendering when out of camera and only simulating the world. ## Is a node, because we want to deactivate rendering when out of camera and only simulating the world.
## Plus, only the entity is replicated in multiplayer ## Plus, only the entity is replicated in multiplayer
func _ready() -> void: func _ready() -> void:
add_to_group('entity')
add_to_group('selectable-entity') add_to_group('selectable-entity')
@ -26,17 +26,8 @@ func is_in_selection(selection_box:Rect2,player_cam:Camera3D) -> bool:
return false return false
## Check if the entity is under the raycast (usually made by [method SelectionManager.check_raycast])
## and colliding with entity bouding box
func is_under_mouse(raycast:Dictionary) -> bool:
if raycast:
var aabb:AABB = get_entity_aabb()
return aabb.has_point(raycast.position)
return false
## Calculate the node bouding box ## Calculate the node bouding box
## @deprecated
func get_entity_aabb(node: Node3D = self, ignore_top_level: bool = true, bounds_transform: Transform3D = Transform3D()) -> AABB: func get_entity_aabb(node: Node3D = self, ignore_top_level: bool = true, bounds_transform: Transform3D = Transform3D()) -> AABB:
if node == null: return AABB() if node == null: return AABB()
var box: AABB var box: AABB

View file

@ -11,6 +11,7 @@ var perform_selection := false # Flag to trigger selection in _physics_process
@onready var optimized_rect: bool = ProjectSettings.get_setting("game/interface/optimized_rectangle", false) @onready var optimized_rect: bool = ProjectSettings.get_setting("game/interface/optimized_rectangle", false)
@export var rect_style: StyleBoxFlat @export var rect_style: StyleBoxFlat
@onready var cam = get_viewport().get_camera_3d() @onready var cam = get_viewport().get_camera_3d()
@onready var hovercontrol:Button = $"../Button"
func _ready() -> void: func _ready() -> void:
@ -48,6 +49,8 @@ func _unhandled_input(e: InputEvent) -> void:
max(drag_start.y, e.position.y) - y_min) max(drag_start.y, e.position.y) - y_min)
queue_redraw() queue_redraw()
if !draw_selection and e is InputEventMouseMotion:
check_hover()
func _physics_process(_delta: float) -> void: func _physics_process(_delta: float) -> void:
@ -56,38 +59,60 @@ func _physics_process(_delta: float) -> void:
perform_selection = false perform_selection = false
func check_raycast(mouse_pos: Vector2, collision_mask: int = 4294967295) -> Dictionary: func check_hover():
# TODO: Hovering and display names
var selected = get_selected()
if selected.size() > 0:
hovercontrol.text = selected[0].name
else:
hovercontrol.text = ""
hovercontrol.queue_redraw()
pass
func get_selected() -> Array[Entity]:
var selected: Array[Node] = get_tree().get_nodes_in_group('selected-by-'+str(get_tree().get_multiplayer().get_unique_id()))
var selected_entity: Array[Entity]
selected_entity.assign(selected)
return selected_entity
## [param collision_mask] is 32768 by default, so the collision channel 16
func check_raycast(mouse_pos: Vector2, collision_mask: int = 32768) -> Dictionary:
var from = cam.project_ray_origin(mouse_pos) var from = cam.project_ray_origin(mouse_pos)
var to = from + cam.project_ray_normal(mouse_pos) * 1000 # Magic number var to = from + cam.project_ray_normal(mouse_pos) * 1000 # Magic number
var query = PhysicsRayQueryParameters3D.new() var query = PhysicsRayQueryParameters3D.create(from,to,collision_mask)
query.from = from query.collide_with_areas = true
query.to = to query.collide_with_bodies = false
query.collision_mask = collision_mask
var space_state = cam.get_world_3d().direct_space_state var space_state = cam.get_world_3d().direct_space_state
#DebugTools.DrawLine(from,to,25.0) #DebugTools.DrawLine(from,to,25.0)
return space_state.intersect_ray(query) return space_state.intersect_ray(query)
func deselect_all() -> void:
var selected = get_selected()
for entity:Entity in selected:
entity.deselect()
func update_selected_units() -> void: func update_selected_units() -> void:
var units_array = get_tree().get_nodes_in_group("selectable-entity") var units_array = get_tree().get_nodes_in_group("selectable-entity")
var raycast = {} var raycast = {}
# Only perform raycast if it's a single click (zero-sized select_box) # Only perform raycast if it's a single click (zero-sized select_box)
if select_box.size == Vector2.ZERO: if select_box.size == Vector2.ZERO:
raycast = check_raycast(select_box.position) raycast = check_raycast(select_box.position,)
if raycast: if raycast and raycast.collider:
print("found ",raycast.collider.name) # select entity under mouse
#breakpoint # maybe can be extracted from hover instead?
var entity = raycast.collider.get_parent()
deselect_all()
entity.select()
# BUG: Deselect previous selection
return
for entity: Entity in units_array: for entity: Entity in units_array:
# TODO: Optimize by searching only in spawned chunks # TODO: Optimize by searching only in spawned chunks
if select_box.size == Vector2.ZERO: # select entities within the selection/draggin box
# 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): if entity.is_in_selection(select_box, cam):
entity.select() entity.select()
else: else:

View file

@ -109,6 +109,11 @@ locale/locale_filter_mode=1
locale/language_filter=["en", "fr"] locale/language_filter=["en", "fr"]
locale/country_filter=["FR"] locale/country_filter=["FR"]
[layer_names]
3d_physics/layer_1="Static"
3d_physics/layer_16="Selection"
[physics] [physics]
3d/run_on_separate_thread=true 3d/run_on_separate_thread=true