Add tetromino movement
This commit is contained in:
@@ -2,14 +2,13 @@
|
|||||||
|
|
||||||
[ext_resource type="Script" uid="uid://c8041v2usigk4" path="res://scripts/tetromino.gd" id="1_hprdj"]
|
[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="Resource" uid="uid://curn7voye0ewx" path="res://resources/data/tetrominos/i_piece.tres" id="2_f3wyc"]
|
||||||
[ext_resource type="ArrayMesh" uid="uid://5jb2cluw5746" path="res://assets/models/MSH_T_Corta.res" id="3_f3wyc"]
|
[ext_resource type="ArrayMesh" uid="uid://cp721yqfdga1n" path="res://assets/models/MSH_T_Corta.res" id="3_f3wyc"]
|
||||||
|
|
||||||
[sub_resource type="BoxShape3D" id="BoxShape3D_f3wyc"]
|
[sub_resource type="BoxShape3D" id="BoxShape3D_f3wyc"]
|
||||||
|
|
||||||
[node name="Tetromino" type="Node3D"]
|
[node name="Tetromino" type="Node3D"]
|
||||||
script = ExtResource("1_hprdj")
|
script = ExtResource("1_hprdj")
|
||||||
resource = ExtResource("2_f3wyc")
|
resource = ExtResource("2_f3wyc")
|
||||||
mesh_color = Color(1, 0, 0, 1)
|
|
||||||
|
|
||||||
[node name="Visuals" type="Node3D" parent="."]
|
[node name="Visuals" type="Node3D" parent="."]
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ signal placement_invalid(reason: String)
|
|||||||
var _grid: Dictionary = {} # Key: Vector3i, Value: GridCell3D
|
var _grid: Dictionary = {} # Key: Vector3i, Value: GridCell3D
|
||||||
var _tetrominoes: Array[Tetromino] = []
|
var _tetrominoes: Array[Tetromino] = []
|
||||||
var _occupied_cells: Dictionary = {} # Key: Vector3i, Value: Tetromino3D reference
|
var _occupied_cells: Dictionary = {} # Key: Vector3i, Value: Tetromino3D reference
|
||||||
|
var selected_tetromino: Tetromino
|
||||||
|
|
||||||
# Ray casting for placement validation
|
# Ray casting for placement validation
|
||||||
var _space_state: PhysicsDirectSpaceState3D
|
var _space_state: PhysicsDirectSpaceState3D
|
||||||
@@ -29,6 +30,20 @@ func _ready() -> void:
|
|||||||
_space_state = get_world_3d().direct_space_state
|
_space_state = get_world_3d().direct_space_state
|
||||||
_initialize_grid()
|
_initialize_grid()
|
||||||
|
|
||||||
|
# Connect to every tetrominos already present on board
|
||||||
|
var curr_tetrominos = $TetrominoContainer.get_children();
|
||||||
|
for t in curr_tetrominos:
|
||||||
|
t.selected.connect(_on_tetromino_selected)
|
||||||
|
t.deselected.connect(_on_tetromino_deselected)
|
||||||
|
|
||||||
|
func _on_tetromino_selected(tetromino: Tetromino):
|
||||||
|
print("Tetromino selected")
|
||||||
|
selected_tetromino = tetromino
|
||||||
|
|
||||||
|
func _on_tetromino_deselected(tetromino: Tetromino):
|
||||||
|
print("Tetromino deselected")
|
||||||
|
selected_tetromino = null
|
||||||
|
|
||||||
|
|
||||||
## Initializes the 3D grid structure with empty cells.
|
## Initializes the 3D grid structure with empty cells.
|
||||||
func _initialize_grid() -> void:
|
func _initialize_grid() -> void:
|
||||||
@@ -72,16 +87,16 @@ func is_cell_empty(grid_pos: Vector3i) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
## Attempts to place a tetromino at the given grid position with optional rotation.
|
## Attempts to place a tetromino at the given grid position with optional rotation.
|
||||||
func place_tetromino(tetromino: Tetromino, grid_position: Vector3i, rotation: Quaternion = Quaternion.IDENTITY) -> bool:
|
func place_tetromino(tetromino: Tetromino, grid_position: Vector3i, rot: Quaternion = Quaternion.IDENTITY) -> bool:
|
||||||
# Validate placement
|
# Validate placement
|
||||||
if not _can_place_tetromino(tetromino, grid_position, rotation):
|
if not _can_place_tetromino(tetromino, grid_position, rot):
|
||||||
placement_invalid.emit("Invalid placement: overlapping or out of bounds")
|
placement_invalid.emit("Invalid placement: overlapping or out of bounds")
|
||||||
return false
|
return false
|
||||||
|
|
||||||
# Update tetromino state
|
# Update tetromino state
|
||||||
tetromino.grid_position = grid_position
|
tetromino.grid_position = grid_position
|
||||||
tetromino.rotation_quat = rotation
|
tetromino.rotation_quat = rot
|
||||||
tetromino.world_position = grid_to_world(grid_position)
|
tetromino.global_position = grid_to_world(grid_position)
|
||||||
|
|
||||||
# Mark cells as occupied
|
# Mark cells as occupied
|
||||||
var cells = tetromino.get_grid_cells(grid_position)
|
var cells = tetromino.get_grid_cells(grid_position)
|
||||||
@@ -97,8 +112,8 @@ func place_tetromino(tetromino: Tetromino, grid_position: Vector3i, rotation: Qu
|
|||||||
|
|
||||||
## Moves a tetromino to a new position (used during combat).
|
## Moves a tetromino to a new position (used during combat).
|
||||||
func move_tetromino(tetromino: Tetromino, new_grid_position: Vector3i) -> bool:
|
func move_tetromino(tetromino: Tetromino, new_grid_position: Vector3i) -> bool:
|
||||||
if tetromino not in _tetrominoes:
|
#if tetromino not in _tetrominoes:
|
||||||
return false
|
#return false
|
||||||
|
|
||||||
# Clear old occupation
|
# Clear old occupation
|
||||||
var old_cells = tetromino.get_grid_cells(tetromino.grid_position)
|
var old_cells = tetromino.get_grid_cells(tetromino.grid_position)
|
||||||
@@ -107,20 +122,20 @@ func move_tetromino(tetromino: Tetromino, new_grid_position: Vector3i) -> bool:
|
|||||||
if cell_pos in _grid:
|
if cell_pos in _grid:
|
||||||
_grid[cell_pos].owner = null
|
_grid[cell_pos].owner = null
|
||||||
|
|
||||||
# Validate new position
|
## Validate new position
|
||||||
if not _can_place_tetromino(tetromino, new_grid_position, tetromino.rotation_quat):
|
#if not _can_place_tetromino(tetromino, new_grid_position, tetromino.rotation_quat):
|
||||||
# Restore old occupation
|
## Restore old occupation
|
||||||
for cell_pos in old_cells:
|
#for cell_pos in old_cells:
|
||||||
_occupied_cells[cell_pos] = tetromino
|
#_occupied_cells[cell_pos] = tetromino
|
||||||
if cell_pos in _grid:
|
#if cell_pos in _grid:
|
||||||
_grid[cell_pos].owner = tetromino
|
#_grid[cell_pos].owner = tetromino
|
||||||
placement_invalid.emit("Cannot move to target position")
|
#placement_invalid.emit("Cannot move to target position")
|
||||||
return false
|
#return false
|
||||||
|
|
||||||
# Update position
|
# Update position
|
||||||
var old_position = tetromino.grid_position
|
var old_position = tetromino.grid_position
|
||||||
tetromino.grid_position = new_grid_position
|
tetromino.grid_position = new_grid_position
|
||||||
tetromino.world_position = grid_to_world(new_grid_position)
|
tetromino.global_position = grid_to_world(new_grid_position)
|
||||||
|
|
||||||
# Mark cells as occupied
|
# Mark cells as occupied
|
||||||
var new_cells = tetromino.get_grid_cells(new_grid_position)
|
var new_cells = tetromino.get_grid_cells(new_grid_position)
|
||||||
@@ -129,7 +144,7 @@ func move_tetromino(tetromino: Tetromino, new_grid_position: Vector3i) -> bool:
|
|||||||
if cell_pos in _grid:
|
if cell_pos in _grid:
|
||||||
_grid[cell_pos].owner = tetromino
|
_grid[cell_pos].owner = tetromino
|
||||||
|
|
||||||
tetromino_moved.emit(tetromino, grid_to_world(old_position), tetromino.world_position)
|
tetromino_moved.emit(tetromino, grid_to_world(old_position), tetromino.global_position)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
||||||
@@ -194,13 +209,13 @@ func get_tetromino_at(grid_pos: Vector3i) -> Tetromino:
|
|||||||
## Gets all adjacent tetrominoes within synergy radius.
|
## Gets all adjacent tetrominoes within synergy radius.
|
||||||
func get_adjacent_tetrominoes(tetromino: Tetromino, synergy_radius: float = 2.5) -> Array[Tetromino]:
|
func get_adjacent_tetrominoes(tetromino: Tetromino, synergy_radius: float = 2.5) -> Array[Tetromino]:
|
||||||
var adjacent: Array[Tetromino] = []
|
var adjacent: Array[Tetromino] = []
|
||||||
var world_pos = tetromino.world_position
|
var world_pos = tetromino.global_position
|
||||||
|
|
||||||
for other in _tetrominoes:
|
for other in _tetrominoes:
|
||||||
if other == tetromino:
|
if other == tetromino:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
var distance = world_pos.distance_to(other.world_position)
|
var distance = world_pos.distance_to(other.global_position)
|
||||||
if distance <= synergy_radius:
|
if distance <= synergy_radius:
|
||||||
adjacent.append(other)
|
adjacent.append(other)
|
||||||
|
|
||||||
@@ -208,9 +223,9 @@ func get_adjacent_tetrominoes(tetromino: Tetromino, synergy_radius: float = 2.5)
|
|||||||
|
|
||||||
|
|
||||||
## Validates whether a tetromino can be placed at the given position with rotation.
|
## Validates whether a tetromino can be placed at the given position with rotation.
|
||||||
func _can_place_tetromino(tetromino: Tetromino, grid_position: Vector3i, rotation: Quaternion) -> bool:
|
func _can_place_tetromino(tetromino: Tetromino, grid_position: Vector3i, rot: Quaternion) -> bool:
|
||||||
# Get the cells this tetromino would occupy
|
# Get the cells this tetromino would occupy
|
||||||
var cells = tetromino.get_grid_cells_with_rotation(grid_position, rotation)
|
var cells = tetromino.get_grid_cells_with_rotation(grid_position, rot)
|
||||||
|
|
||||||
# Check all cells are within bounds and empty
|
# Check all cells are within bounds and empty
|
||||||
for cell_pos in cells:
|
for cell_pos in cells:
|
||||||
@@ -222,7 +237,7 @@ func _can_place_tetromino(tetromino: Tetromino, grid_position: Vector3i, rotatio
|
|||||||
return false
|
return false
|
||||||
|
|
||||||
# Raycast check for physical obstacles
|
# Raycast check for physical obstacles
|
||||||
if not _validate_placement_raycast(tetromino, grid_position, rotation):
|
if not _validate_placement_raycast(tetromino, grid_position):
|
||||||
return false
|
return false
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@@ -230,7 +245,7 @@ func _can_place_tetromino(tetromino: Tetromino, grid_position: Vector3i, rotatio
|
|||||||
|
|
||||||
## Performs raycast validation for tetromino placement.
|
## Performs raycast validation for tetromino placement.
|
||||||
## Checks for physical obstacles using raycasting.
|
## Checks for physical obstacles using raycasting.
|
||||||
func _validate_placement_raycast(tetromino: Tetromino, grid_position: Vector3i, rotation: Quaternion) -> bool:
|
func _validate_placement_raycast(tetromino: Tetromino, grid_position: Vector3i) -> bool:
|
||||||
if not _space_state:
|
if not _space_state:
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ signal health_changed(amount: int)
|
|||||||
@export var starting_health: int = 100
|
@export var starting_health: int = 100
|
||||||
@export var starting_gold: int = 500
|
@export var starting_gold: int = 500
|
||||||
|
|
||||||
|
#
|
||||||
|
@onready var camera: Camera3D = $Camera3D
|
||||||
|
|
||||||
# State management
|
# State management
|
||||||
var _current_state: State = State.DRAFT
|
var _current_state: State = State.DRAFT
|
||||||
var _previous_state: State
|
var _previous_state: State
|
||||||
@@ -71,11 +74,11 @@ func _ready() -> void:
|
|||||||
_event_bus = get_node("/root/EventBus") if "/root/EventBus" in get_tree().root else null
|
_event_bus = get_node("/root/EventBus") if "/root/EventBus" in get_tree().root else null
|
||||||
|
|
||||||
# Find board manager in scene
|
# Find board manager in scene
|
||||||
_board_manager = get_parent().get_node_or_null("Board")
|
_board_manager = get_node_or_null("Board")
|
||||||
_combat_system = get_parent().get_node_or_null("CombatSystem")
|
#_combat_system = get_node_or_null("CombatSystem")
|
||||||
_synergy_system = get_parent().get_node_or_null("SynergySystem")
|
#_synergy_system = get_node_or_null("SynergySystem")
|
||||||
_enemy_spawner = get_parent().get_node_or_null("EnemySpawner")
|
#_enemy_spawner = get_node_or_null("EnemySpawner")
|
||||||
_ui_manager = get_parent().get_node_or_null("HUD")
|
#_ui_manager = get_node_or_null("HUD")
|
||||||
|
|
||||||
# Connect to signal events if event bus exists
|
# Connect to signal events if event bus exists
|
||||||
if _event_bus:
|
if _event_bus:
|
||||||
@@ -88,6 +91,28 @@ func _ready() -> void:
|
|||||||
_current_wave = 0
|
_current_wave = 0
|
||||||
_transition_to_state(State.DRAFT)
|
_transition_to_state(State.DRAFT)
|
||||||
|
|
||||||
|
func _input(event: InputEvent) -> void:
|
||||||
|
if _board_manager.selected_tetromino and event is InputEventMouseMotion:
|
||||||
|
var mouse_pos = get_viewport().get_mouse_position()
|
||||||
|
var ray_origin = camera.project_ray_origin(mouse_pos)
|
||||||
|
var ray_direction = camera.project_ray_normal(mouse_pos)
|
||||||
|
|
||||||
|
# Cast ray to find ground position (Y = 0 plane)
|
||||||
|
var t = -ray_origin.y / ray_direction.y if ray_direction.y != 0 else 0.0
|
||||||
|
var world_hit = ray_origin + ray_direction * t
|
||||||
|
|
||||||
|
# Convert to grid position
|
||||||
|
var new_grid_pos = _board_manager.world_to_grid(world_hit)
|
||||||
|
_board_manager.move_tetromino(_board_manager.selected_tetromino, new_grid_pos)
|
||||||
|
|
||||||
|
func _on_tetromino_selected(tetromino: Tetromino):
|
||||||
|
print("Tetromino selected")
|
||||||
|
_board_manager.selected_tetromino = tetromino
|
||||||
|
|
||||||
|
func _on_tetromino_deselected(tetromino: Tetromino):
|
||||||
|
print("Tetromino deselected")
|
||||||
|
_board_manager.selected_tetromino = null
|
||||||
|
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
_state_timer += delta
|
_state_timer += delta
|
||||||
|
|||||||
@@ -5,16 +5,17 @@
|
|||||||
class_name Tetromino
|
class_name Tetromino
|
||||||
extends Node3D
|
extends Node3D
|
||||||
|
|
||||||
signal hovered(tetromino)
|
signal hovered(tetromino: Tetromino)
|
||||||
signal unhovered(tetromino)
|
signal unhovered(tetromino: Tetromino)
|
||||||
signal selected(tetromino, grid_pos)
|
signal selected(tetromino: Tetromino)
|
||||||
signal deselected(tetromino)
|
signal deselected(tetromino: Tetromino)
|
||||||
|
|
||||||
|
|
||||||
# Data
|
# Data
|
||||||
@export var resource: TetrominoDefinition
|
@export var resource: TetrominoDefinition
|
||||||
|
|
||||||
# Visual
|
# Visual
|
||||||
@export var mesh_color: Color = Color.WHITE
|
var _mesh_color: Color = Color.WHITE
|
||||||
var _material: StandardMaterial3D
|
var _material: StandardMaterial3D
|
||||||
var _is_ghost: bool = false
|
var _is_ghost: bool = false
|
||||||
var _original_color: Color = Color.WHITE
|
var _original_color: Color = Color.WHITE
|
||||||
@@ -63,12 +64,12 @@ func get_grid_cells(at_position: Vector3i = grid_position) -> PackedVector3Array
|
|||||||
|
|
||||||
|
|
||||||
## Gets grid cells with a specific rotation applied.
|
## Gets grid cells with a specific rotation applied.
|
||||||
func get_grid_cells_with_rotation(at_position: Vector3i, rotation: Quaternion) -> PackedVector3Array:
|
func get_grid_cells_with_rotation(at_position: Vector3i, rot: Quaternion) -> PackedVector3Array:
|
||||||
var result = PackedVector3Array()
|
var result = PackedVector3Array()
|
||||||
|
|
||||||
# Apply rotation to relative grid cells
|
# Apply rotation to relative grid cells
|
||||||
for cell in _grid_cells:
|
for cell in _grid_cells:
|
||||||
var rotated = rotation * Vector3(cell)
|
var rotated = rot * Vector3(cell)
|
||||||
var rotated_int = Vector3i(rotated.round())
|
var rotated_int = Vector3i(rotated.round())
|
||||||
result.append(at_position + rotated_int)
|
result.append(at_position + rotated_int)
|
||||||
|
|
||||||
@@ -92,7 +93,7 @@ func get_bounds() -> AABB:
|
|||||||
|
|
||||||
## Sets the color/material of the tetromino.
|
## Sets the color/material of the tetromino.
|
||||||
func set_color(color: Color) -> void:
|
func set_color(color: Color) -> void:
|
||||||
mesh_color = color
|
_mesh_color = color
|
||||||
_original_color = color
|
_original_color = color
|
||||||
if _material:
|
if _material:
|
||||||
_material.albedo_color = color
|
_material.albedo_color = color
|
||||||
@@ -125,7 +126,7 @@ func set_highlighted(enabled: bool) -> void:
|
|||||||
if _material:
|
if _material:
|
||||||
_material.emission_enabled = enabled
|
_material.emission_enabled = enabled
|
||||||
if enabled:
|
if enabled:
|
||||||
_material.emission = mesh_color
|
_material.emission = _mesh_color
|
||||||
_material.emission_energy_multiplier = 2.0
|
_material.emission_energy_multiplier = 2.0
|
||||||
else:
|
else:
|
||||||
_material.emission_energy_multiplier = 0.0
|
_material.emission_energy_multiplier = 0.0
|
||||||
@@ -143,10 +144,8 @@ func _to_string() -> String:
|
|||||||
func _on_selection_area_input_event(camera: Node, event: InputEvent, event_position: Vector3, normal: Vector3, shape_idx: int) -> void:
|
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 is InputEventMouseButton:
|
||||||
if event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
|
if event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
|
||||||
print("Tetromino selected")
|
selected.emit(self)
|
||||||
selected.emit(self, grid_position)
|
|
||||||
elif event.pressed and event.button_index == MOUSE_BUTTON_RIGHT:
|
elif event.pressed and event.button_index == MOUSE_BUTTON_RIGHT:
|
||||||
print("Tetromino released")
|
|
||||||
deselected.emit(self)
|
deselected.emit(self)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user