## Lane.gd ## Manages a player's lane: a TileMap background, a grid of TowerSlots, ## and the creep Path2D that winds through the play area. ## ## Lane layout (64px tiles, 4 cols wide × 8 rows tall build area): ## ## col: 0 1 2 3 [path col 4] ## row 0 [ ] [ ] [ ] [ ] ↓ ## row 1 [ ] [ ] [ ] [ ] ↓ ## row 2 [ ] [ ] [ ] [ ] ←← ## row 3 [ ] [ ] [ ] [ ] ↑ ## row 4 [ ] [ ] [ ] [ ] ↑ ## row 5 [ ] [ ] [ ] [ ] →→ ## row 6 [ ] [ ] [ ] [ ] ↓ ## row 7 [ ] [ ] [ ] [ ] ↓ → EXIT ## ## The path enters from the top-right, snakes left/right, exits bottom-right. ## Creeps follow the Path2D curve; towers build on the coloured cells. class_name Lane extends Node2D @export var player_id: int = 0 @export var slot_columns: int = 4 @export var slot_rows: int = 8 @export var tile_size: float = 64.0 # Colour used to tint each slot cell (overridden in editor per player) @export var slot_color: Color = Color(0.18, 0.22, 0.28, 0.85) @export var path_color: Color = Color(0.35, 0.30, 0.20, 1.0) var _slots: Array[Node] = [] # Path points in local space. The snake runs down the right side of the # build grid, then doubles back. Adjust these to reshape the lane. const PATH_POINTS: Array = [ Vector2(288, -32), # entry: above top of lane (col 4.5, off-screen) Vector2(288, 32), # row 0 right edge Vector2(288, 96), # row 1 Vector2(288, 160), # row 2 — turn left Vector2( 32, 160), # row 2 left edge — turn up Vector2( 32, 96), # row 1 left Vector2( 32, 32), # row 0 left — turn right (second pass) Vector2(288, 32), # NOTE: this pass is a separate sweep for zigzag lanes # Simplified single-snake for MVP — straight entry, one hairpin, exit: ] # Cleaner single-hairpin path used at runtime func _build_path_points() -> PackedVector2Array: var pts := PackedVector2Array() var w := slot_columns * tile_size # 256 var h := slot_rows * tile_size # 512 var mid := tile_size * 0.5 # 32 — centre of path corridor # Entry: top-right corridor (one tile to the right of the build grid) pts.append(Vector2(w + mid, -tile_size)) # off-screen top pts.append(Vector2(w + mid, h * 0.25)) # quarter down # Hairpin: sweep left across the top of the grid pts.append(Vector2(w + mid, h * 0.375)) pts.append(Vector2(-mid, h * 0.375)) # left gutter # Come back up then sweep across mid pts.append(Vector2(-mid, h * 0.5)) pts.append(Vector2(w + mid, h * 0.5)) # Second hairpin lower pts.append(Vector2(w + mid, h * 0.625)) pts.append(Vector2(-mid, h * 0.625)) # Exit: bottom-right pts.append(Vector2(-mid, h * 0.875)) pts.append(Vector2(w + mid, h * 0.875)) pts.append(Vector2(w + mid, h + tile_size)) # off-screen bottom return pts func _ready() -> void: _draw_background() _build_slots() _build_path() ## Draws the lane background using plain ColorRect nodes (no TileSet required). ## Replace with a real TileMap once you have art assets. func _draw_background() -> void: var w := slot_columns * tile_size var h := slot_rows * tile_size var corridor := tile_size # width of the path corridor on each side # Main play field var field := ColorRect.new() field.size = Vector2(w, h) field.color = Color(0.12, 0.14, 0.16) add_child(field) # Left path gutter var left_gutter := ColorRect.new() left_gutter.size = Vector2(corridor, h) left_gutter.position = Vector2(-corridor, 0) left_gutter.color = path_color add_child(left_gutter) # Right path corridor var right_corridor := ColorRect.new() right_corridor.size = Vector2(corridor, h) right_corridor.position = Vector2(w, 0) right_corridor.color = path_color add_child(right_corridor) # Hairpin cross-corridors var hairpin_rows := [0.375, 0.5, 0.625] for frac in hairpin_rows: var bar := ColorRect.new() bar.size = Vector2(w + corridor * 2, corridor) bar.position = Vector2(-corridor, h * frac - corridor * 0.5) bar.color = path_color add_child(bar) # Grid lines over the build area for col in range(slot_columns + 1): var line := ColorRect.new() line.size = Vector2(1, h) line.position = Vector2(col * tile_size, 0) line.color = Color(1, 1, 1, 0.06) add_child(line) for row in range(slot_rows + 1): var line := ColorRect.new() line.size = Vector2(w, 1) line.position = Vector2(0, row * tile_size) line.color = Color(1, 1, 1, 0.06) add_child(line) func _build_slots() -> void: var slot_scene := load("res://scenes/gameplay/TowerSlot.tscn") as PackedScene if not slot_scene: push_error("Lane: TowerSlot.tscn not found") return for row in range(slot_rows): for col in range(slot_columns): var slot = slot_scene.instantiate() slot.player_id = player_id slot.position = Vector2(col * tile_size + tile_size * 0.5, row * tile_size + tile_size * 0.5) slot.slot_clicked.connect(_on_slot_clicked) add_child(slot) _slots.append(slot) func _build_path() -> void: var path := Path2D.new() path.name = "CreepPath" var curve := Curve2D.new() for pt in _build_path_points(): curve.add_point(pt) path.curve = curve add_child(path) func get_path_points() -> PackedVector2Array: var path := get_node_or_null("CreepPath") as Path2D if path: return path.curve.get_baked_points() return PackedVector2Array() func _on_slot_clicked(slot: Node) -> void: EventBus.slot_clicked_in_lane.emit(player_id, slot)