diff --git a/TETROMINO_SELECTOR.md b/TETROMINO_SELECTOR.md index 92832a5..b3db608 100644 --- a/TETROMINO_SELECTOR.md +++ b/TETROMINO_SELECTOR.md @@ -1,19 +1,22 @@ # Tetromino Selector System ## Overview -Adds interactive tetromino selection and repositioning during gameplay. Players can select already-placed tetrominoes, move them, rotate them, and place them in new positions. +Adds interactive tetromino selection and repositioning during gameplay. Players can select already- +placed tetrominoes, move them, rotate them, and place them in new positions. ## Components ### 1. TetrominoSelector (scripts/tetromino_selector.gd) + Main controller for tetromino selection logic. **Features:** -- Raycast-based tetromino detection on left-click -- Ghost mode visualization (semi-transparent) +- Raycast-based tetromino detection on left-click from camera to world - Real-time position tracking with mouse movement -- Rotation with spacebar (45° increments) + +- Ghost mode visualization (semi-transparent) - Placement validation using board manager +- Rotation with spacebar (45° increments) - Cancellation with right-click or ESC key **Signals:** @@ -78,12 +81,12 @@ Added collision detection for raycast selection. - Player left-clicks to place - Selector validates placement using BoardManager3D._can_place_tetromino() - If valid: - - Updates board manager occupied cells - - Tetromino exits ghost mode - - Signals emit `selection_placed` + - Updates board manager occupied cells + - Tetromino exits ghost mode + - Signals emit `selection_placed` - If invalid: - - Tetromino snaps back to original position - - Reverts to original rotation + - Tetromino snaps back to original position + - Reverts to original rotation 5. **Cancellation** - Player right-clicks or presses ESC diff --git a/assets/models/MSH_1x3.res b/assets/models/MSH_1x3.res index e704101..9367ae6 100644 Binary files a/assets/models/MSH_1x3.res and b/assets/models/MSH_1x3.res differ diff --git a/assets/models/MSH_T_Corta.res b/assets/models/MSH_T_Corta.res new file mode 100644 index 0000000..7099e05 Binary files /dev/null and b/assets/models/MSH_T_Corta.res differ diff --git a/resources/data/tetrominos/i_piece.tres b/resources/data/tetrominos/i_piece.tres index 762a80f..4825a39 100644 --- a/resources/data/tetrominos/i_piece.tres +++ b/resources/data/tetrominos/i_piece.tres @@ -1,7 +1,9 @@ -[gd_resource type="Resource" script_class="TetrominoDefinition" load_steps=2 format=3 uid="uid://curn7voye0ewx"] +[gd_resource type="Resource" script_class="TetrominoDefinition" load_steps=3 format=3 uid="uid://curn7voye0ewx"] [ext_resource type="Script" uid="uid://p168a1urs4im" path="res://scripts/tetromino_definition.gd" id="1_suu2q"] +[ext_resource type="ArrayMesh" uid="uid://dev1jigsm4i4e" path="res://assets/models/MSH_1x2_Orizzontale.res" id="1_tot0g"] [resource] script = ExtResource("1_suu2q") +mesh = ExtResource("1_tot0g") metadata/_custom_type_script = "uid://p168a1urs4im" diff --git a/resources/data/tetrominos/t_piece.tres b/resources/data/tetrominos/t_piece.tres new file mode 100644 index 0000000..56fea7d --- /dev/null +++ b/resources/data/tetrominos/t_piece.tres @@ -0,0 +1,9 @@ +[gd_resource type="Resource" script_class="TetrominoDefinition" load_steps=3 format=3 uid="uid://cqgsjnof4dly0"] + +[ext_resource type="Script" uid="uid://p168a1urs4im" path="res://scripts/tetromino_definition.gd" id="1_0geyk"] +[ext_resource type="ArrayMesh" uid="uid://5jb2cluw5746" path="res://assets/models/MSH_T_Corta.res" id="1_il264"] + +[resource] +script = ExtResource("1_0geyk") +mesh = ExtResource("1_il264") +metadata/_custom_type_script = "uid://p168a1urs4im" diff --git a/scenes/board/board.tscn b/scenes/board/board.tscn index a1cb571..dd1d633 100644 --- a/scenes/board/board.tscn +++ b/scenes/board/board.tscn @@ -1,8 +1,9 @@ -[gd_scene load_steps=5 format=3 uid="uid://j3vihw63lw7q"] +[gd_scene load_steps=6 format=3 uid="uid://j3vihw63lw7q"] [ext_resource type="Script" uid="uid://bvxt8mmibr1uj" path="res://scripts/board_manager.gd" id="1_jnyf0"] [ext_resource type="PackedScene" uid="uid://btdnshtrnejt" path="res://scenes/board/tetromino.tscn" id="2_jnyf0"] [ext_resource type="Script" uid="uid://bv7xi75mklk7d" path="res://scripts/tetromino_selector.gd" id="3_jnyf0"] +[ext_resource type="Resource" uid="uid://cqgsjnof4dly0" path="res://resources/data/tetrominos/t_piece.tres" id="3_q07he"] [sub_resource type="PlaneMesh" id="PlaneMesh_s78fa"] size = Vector2(1, 1) @@ -25,6 +26,7 @@ mesh = SubResource("PlaneMesh_s78fa") [node name="TetrominoContainer" type="Node3D" parent="."] [node name="Tetromino" parent="TetrominoContainer" instance=ExtResource("2_jnyf0")] +resource = ExtResource("3_q07he") [node name="TetrominoSelector" type="Node3D" parent="."] script = ExtResource("3_jnyf0") diff --git a/scenes/board/tetromino.tscn b/scenes/board/tetromino.tscn index 4e06dbd..7a45ce3 100644 --- a/scenes/board/tetromino.tscn +++ b/scenes/board/tetromino.tscn @@ -1,14 +1,10 @@ -[gd_scene load_steps=6 format=3 uid="uid://btdnshtrnejt"] +[gd_scene load_steps=5 format=3 uid="uid://btdnshtrnejt"] [ext_resource type="Script" uid="uid://c8041v2usigk4" path="res://scripts/tetromino.gd" id="1_hprdj"] [ext_resource type="Resource" uid="uid://curn7voye0ewx" path="res://resources/data/tetrominos/i_piece.tres" id="2_f3wyc"] -[ext_resource type="ArrayMesh" uid="uid://e1oj3t6audj3" path="res://assets/models/MSH_1x3.res" id="3_f3wyc"] - -[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_f3wyc"] -albedo_color = Color(1, 0, 0, 1) +[ext_resource type="ArrayMesh" uid="uid://5jb2cluw5746" path="res://assets/models/MSH_T_Corta.res" id="3_f3wyc"] [sub_resource type="BoxShape3D" id="BoxShape3D_f3wyc"] -size = Vector3(3.0031738, 1, 1) [node name="Tetromino" type="Node3D"] script = ExtResource("1_hprdj") @@ -19,7 +15,6 @@ mesh_color = Color(1, 0, 0, 1) [node name="MeshInstance3D" type="MeshInstance3D" parent="Visuals"] layers = 2 -material_override = SubResource("StandardMaterial3D_f3wyc") mesh = ExtResource("3_f3wyc") skeleton = NodePath("../..") @@ -28,5 +23,8 @@ collision_layer = 16 collision_mask = 0 [node name="CollisionShape3D" type="CollisionShape3D" parent="SelectionArea"] -transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -1.6541168e-10, 0.49949223, -0.0037841797) shape = SubResource("BoxShape3D_f3wyc") + +[connection signal="input_event" from="SelectionArea" to="." method="_on_selection_area_input_event"] +[connection signal="mouse_entered" from="SelectionArea" to="." method="_on_selection_area_mouse_entered"] +[connection signal="mouse_exited" from="SelectionArea" to="." method="_on_selection_area_mouse_exited"] diff --git a/scripts/tetromino.gd b/scripts/tetromino.gd index 4cc1b1a..a881692 100644 --- a/scripts/tetromino.gd +++ b/scripts/tetromino.gd @@ -5,6 +5,11 @@ class_name Tetromino extends Node3D +signal hovered(tetromino) +signal unhovered(tetromino) +signal selected(tetromino, grid_pos) +signal deselected(tetromino) + # Data @export var resource: TetrominoDefinition @@ -28,15 +33,15 @@ var _grid_cells: PackedVector3Array = [] # Relative cell coordinates func _ready() -> void: - #_initialize_material() - #_set_mesh_representation() + _initialize_material() + _set_mesh_representation() pass ## Initializes the material for visual representation. func _initialize_material() -> void: _material = StandardMaterial3D.new() - _material.albedo_color = mesh_color + _material.albedo_color = Color.RED _material.metallic = 0.3 _material.roughness = 0.7 @@ -44,15 +49,9 @@ func _initialize_material() -> void: ## Creates a simple mesh representation based on shape type. func _set_mesh_representation() -> void: _mesh_instance.mesh = resource.mesh - #_mesh_instance.set_surface_override_material(0, _material) + _mesh_instance.set_surface_override_material(0, _material) + $SelectionArea/CollisionShape3D.shape = _mesh_instance.mesh.create_trimesh_shape() _grid_cells = resource.grid_cells - - -## Creates a simple box mesh for tetromino visualization. -func _create_box_mesh(width: float, height: float, depth: float) -> BoxMesh: - var box = BoxMesh.new() - box.size = Vector3(width, height, depth) - return box ## Gets the grid cells occupied by this tetromino at its current position. @@ -139,3 +138,26 @@ func _to_string() -> String: grid_position, _grid_cells.size() ] + +## Callback used to handle tetromino selection and deselection +func _on_selection_area_input_event(camera: Node, event: InputEvent, event_position: Vector3, normal: Vector3, shape_idx: int) -> void: + if event is InputEventMouseButton: + if event.pressed and event.button_index == MOUSE_BUTTON_LEFT: + print("Tetromino selected") + selected.emit(self, grid_position) + elif event.pressed and event.button_index == MOUSE_BUTTON_RIGHT: + print("Tetromino released") + deselected.emit(self) + + +## Callback used to handle tetromino hovering +func _on_selection_area_mouse_entered() -> void: + print("Tetromino hovered") + set_highlighted(true) + hovered.emit(self) + +## Callback used to handle tetromino unhovering +func _on_selection_area_mouse_exited() -> void: + print("Tetromino unhovered") + set_highlighted(false) + unhovered.emit(self) diff --git a/scripts/tetromino_selector.gd b/scripts/tetromino_selector.gd index 75ccb92..e5dac41 100644 --- a/scripts/tetromino_selector.gd +++ b/scripts/tetromino_selector.gd @@ -33,34 +33,34 @@ func _ready() -> void: pass -func _input(event: InputEvent) -> void: - if event is InputEventMouseButton: - if event.pressed and event.button_index == MOUSE_BUTTON_LEFT: - _handle_left_click() - elif event.pressed and event.button_index == MOUSE_BUTTON_RIGHT: - _handle_right_click() - - if _selected_tetromino and event is InputEventMouseMotion: - _update_ghost_position() - - if _selected_tetromino: - if event.is_action_pressed("ui_select"): # Spacebar - _rotate_ghost() - elif event.is_action_pressed("ui_cancel"): # ESC - _cancel_selection() +#func _input(event: InputEvent) -> void: + #if event is InputEventMouseButton: + #if event.pressed and event.button_index == MOUSE_BUTTON_LEFT: + #_handle_left_click() + #elif event.pressed and event.button_index == MOUSE_BUTTON_RIGHT: + #_handle_right_click() + # + #if _selected_tetromino and event is InputEventMouseMotion: + #_update_ghost_position() + # + #if _selected_tetromino: + #if event.is_action_pressed("ui_select"): # Spacebar + #_rotate_ghost() + #elif event.is_action_pressed("ui_cancel"): # ESC + #_cancel_selection() -## Handles left mouse click for selection or placement. -func _handle_left_click() -> void: - if _selected_tetromino: - # Try to place the ghost tetromino - _place_selection() - return - - # Try to select a tetromino - var tetromino = _raycast_tetromino() - if tetromino: - _select_tetromino(tetromino) +### Handles left mouse click for selection or placement. +#func _handle_left_click() -> void: + #if _selected_tetromino: + ## Try to place the ghost tetromino + #_place_selection() + #return + # + ## Try to select a tetromino + #var tetromino = _raycast_tetromino() + #if tetromino: + #_select_tetromino(tetromino) ## Handles right mouse click to deselect. @@ -69,34 +69,34 @@ func _handle_right_click() -> void: _cancel_selection() -## Raycasts from mouse position to find a tetromino. -func _raycast_tetromino() -> Tetromino: - if not camera: - return null - - var mouse_pos = get_viewport().get_mouse_position() - var from = camera.project_ray_origin(mouse_pos) - var to = from + camera.project_ray_normal(mouse_pos) * 10000.0 - - var space_state = get_world_3d().direct_space_state - var collision_mask = 0xFFFFFFFF # Hit all layers - var query = PhysicsRayQueryParameters3D.create(from, to, collision_mask) - query.collide_with_areas = true - - var result = space_state.intersect_ray(query) - - if result and result.get("collider"): - var collider = result["collider"] - # Walk up the node tree to find a Tetromino - var node = collider - while node: - if node is Tetromino: - return node - node = node.get_parent() - else: - print_debug("Raycast hit nothing") - - return null +### Raycasts from mouse position to find a tetromino. +#func _raycast_tetromino() -> Tetromino: + #if not camera: + #return null + # + #var mouse_pos = get_viewport().get_mouse_position() + #var from = camera.project_ray_origin(mouse_pos) + #var to = from + camera.project_ray_normal(mouse_pos) * 10000.0 + # + #var space_state = get_world_3d().direct_space_state + #var collision_mask = 0xFFFFFFFF # Hit all layers + #var query = PhysicsRayQueryParameters3D.create(from, to, collision_mask) + #query.collide_with_areas = true; + # + #var result = space_state.intersect_ray(query) + # + #if result and result.get("collider"): + #var collider = result["collider"] + ## Walk up the node tree to find a Tetromino + #var node = collider + #while node: + #if node is Tetromino: + #return node + #node = node.get_parent() + #else: + #print_debug("Raycast hit nothing") + # + #return null ## Selects a tetromino and converts it to ghost mode.