BlogHome

Godot 4 tile destruction

2024-01-05

I've always liked games like Broforce. Partly because I could play with my siblings, but also because you could destroy the whole map.

Broforce tilemap destruction GIF

Similar effect to this from Broforce can be easily reproduced in Godot.

Firstly Tilemap node has to be created. Tilemap node

Add any tileset as you wish. I've used https://anokolisa.itch.io/sidescroller-pixelart-sprites-asset-pack-forest-16x16

Tileset

For the purpose of this demo I created beautiful blob of terrain :3 Terrain blob

Now's the time for a sauce. Actually destroying tiles.

Firstly, I'm getting the positon of mouse when left mouse button is clicked.

func _input(event):
	if event is InputEventMouseButton:
		if event.is_pressed() and event.button_index == MOUSE_BUTTON_LEFT:
			apply_damage_to_tile(get_global_mouse_position(), 35)

Then the function to actually destroy tile is called. It does two simple things: reduce tile durability, and destroy it while spawning particles when durability reaches 0.

But to do this and be easily able to search tiles two arrays have to be created:

@onready var tilemap: TileMap = get_node("TileMap")

@onready var used_cells = tilemap.get_used_cells(0).duplicate()
@onready var durability = used_cells.map(func(x): return 100)

these are declared at the top of the file. Used cells stores all coordinates of tilemap cells currently containing actual tiles. With this searching for clicked tile is easy.

func apply_damage_to_tile(local_pos, damage):
	var map_pos = tilemap.local_to_map(local_pos)
	var cell_to_destroy_index = used_cells.find(map_pos)

Now final thing is to actually "destroy" the tile. With tile index in our arrays we can access it's durability. If it is lower than zero, the tile can be de "destroyed" using set_cell() method.

if cell_to_destroy_index != null and used_cells[cell_to_destroy_index] != null and durability[cell_to_destroy_index] != null:
		durability[cell_to_destroy_index] -= damage
		if durability[cell_to_destroy_index] <= 0:
			tilemap.set_cell(0, map_pos, -1, Vector2i(-1, -1), -1)
			used_cells[cell_to_destroy_index] = null
			durability[cell_to_destroy_index] = null

To make it a bit more aesthetically pleasing, lets add some particles. Spawning them on tile destruction is a piece of cake. I've actually created separate function, to mantain different effects when destroying tile, and when only damaging it.

@onready var destroy_particles = load("res://destroy_particles.tscn")

func spawn_particles(amount, pos):
	var spawn: GPUParticles2D = destroy_particles.instantiate()
	add_child(spawn)
	spawn.set_position(pos)
	spawn.emitting = true
	spawn.amount = amount
	await spawn.finished
	spawn.queue_free()

func apply_damage_to_tile(local_pos, damage):
	var map_pos = tilemap.local_to_map(local_pos)
	var cell_to_destroy_index = used_cells.find(map_pos)
	if cell_to_destroy_index != null and used_cells[cell_to_destroy_index] != null and durability[cell_to_destroy_index] != null:
		durability[cell_to_destroy_index] -= damage
		if durability[cell_to_destroy_index] <= 0:
			tilemap.set_cell(0, map_pos, -1, Vector2i(-1, -1), -1)
			used_cells[cell_to_destroy_index] = null
			durability[cell_to_destroy_index] = null
			spawn_particles(500, tilemap.map_to_local(map_pos))
		else:
			spawn_particles(100, tilemap.map_to_local(map_pos))

The final result:

Now there could be added some mechanisms that make blocks "fall", or show different textures when damaged.