Day 5: Drag and Drop
[Tower Defense]
Placing pieces on the grid…was initially click-to-select > click-to-place and a Discord comment said “just make it drag and drop.” Ok fine.
Ghost piece
I need a way to show where the piece would go, it needs to look like a suggestion and not like a placed piece. So I went with a partially transparent “ghost” piece.
One thing that bugged me right away, when you start dragging, the ghost renders with its (0,0) at the cursor position. So the piece jumps. You click the middle of a piece and suddenly the top-left corner teleports to the cursor.
The fix is literally just subtraction. Track where on the piece you clicked relative to the piece’s top-left corner, subtract that from the cursor position.
It makes so much sense now, but it took me way too long to get there.
The ghost has two modes depending on where the cursor is:
- Off the map: ghost just follows the cursor freely, pixel by pixel.
- On the map: ghost snaps to the grid cell under the cursor. While the cursor moves within the same cell, nothing rerenders because the piece is already snapped there. It just sits.
Path validation
The whole point of placing pieces is to create obstacles for enemies to path around. Like most tower defense games, I shouldn’t allow the player to block the path entirely.
So every time the ghost snaps to a new grid cell, I:
- Build a fresh AStarGrid2D from the current grid state
- Mark the ghost’s cells as solid
- Check if a path still exists from spawn to goal
func does_not_block() -> bool:
for cell in ghost.get_cells():
# Mark all cells in the ghost as solid.
astar_grid.set_point_solid(cell)
var path = astar_grid.get_id_path(spawn, goal)
return len(path) > 0
If the path is blocked, or the ghost overlaps an already-placed piece, the ghost turns red. You can’t place it there.
(Note: there is an edge case where this breaks…but I won’t get into it now.)
(The treasure chest is the “goal”)
Godot has a whole 2D transforms page for coordinate conversion, screen-to-world and all that. In my case though, no camera movement, no scaling, mouse position is just…the position. Straightforward.
Sprites from kenney.nl.