How do you properly explode an enemy? (Without freezing the main thread)

EDIT: Please note that the “Solved by” post is in no way a proper solution to this.
If you know a proper solution for this, then please educate the rest of us and I will set that as the solution.

Godot Version

4.3

Question

I have something like this:

class_name Enemy extends PathFollow2D

@export var explosion: PackedScene

func explode():
	explosion.instantiate()
	explosion.global_position = global_position
	for particle in explosion.get_children():
		particle.emitting = true
	Layers.bottom_layer.add_child(explosion)
    queue_free()

Enemies can have different kinds of explosions (basically a scene with a collection of GPUParticle2D).

It all works fine, but the first time a new type of explosion shows, the game freezes for a bit.

How can I make sure that the explosions are preloaded?

Should I try an entirely different approach?

Godot is pretty bad at pre-caching solutions. There is load_threaded_request, but I think it’s the instantiate that automatically loads more stuff. It will be faster when exported, but not perfect. If it’s specifically shader compilation snagging your frames then there are fewer options.

Here’s a video from a developer, timestamped at this exact struggle with some interesting solutions.

Haha that’s hilarious.
I was thinking of doing that as a temporary workaround while finding the proper way to do it.
But yeah wow, that’s really an extremely hacky way to do it.

1 Like

This is utterly ridiculous… and I see this as a temporary workaround rather than a solution.

func ridiculous_shader_caching_solution():
	for wave_tier in waves:
		for enemy_scene in enemies[wave_tier]:
			var enemy_instance = ships[wave_tier][enemy_scene].instantiate()
			enemy_instance.global_position = G.center
			G.enemy_layer.add_child(enemy_instance)
			enemy_instance.clear() # Will explode and free it

But I also want to thank you Garret @gertkeno for bringing this to my attention and for the time you gave to help.

Load and instantiate your enemy explosion in a background thread. If instantiating your explosion on the main thread is essentially a hiccup, doing the same thing in a background thread will be unnoticeable to the player.

I have an explosion object in Seas of Reverence that takes a long time to instantiate the first time (I’m not sure why, but I’m guessing textures). The first explosion object takes about two seconds, and the game freezes for that time when I instantiate the explosion on the main game thread.

When I instantiate the explosion on a background thread, the freeze goes away entirely. The object still takes two seconds to explode, but there is not even the slightest effect on the game’s fluidity.

Seas of Reverence also has magical spells that cause noticeable hiccups when they are instantiated on the main thread. Moving them to background threads eliminate the hiccups.

1 Like

Thanks. How?

Here’s the core of how to use threads. This comes from a file I use as a central threaded object loader:


func _ready():
	m_nError = ResourceLoader.load_threaded_request(m_stPath)
	if m_nError != OK:
		print("Can't start the threaded load.")
	
func _process(delta):
	if !m_bDone:
		m_nError = ResourceLoader.load_threaded_get_status(m_stPath)
		match m_nError:
			ResourceLoader.THREAD_LOAD_INVALID_RESOURCE:
				fatalError.emit(self)
				queue_free()
			ResourceLoader.THREAD_LOAD_IN_PROGRESS:
				pass
			ResourceLoader.THREAD_LOAD_FAILED:
				fatalError.emit(self)
				queue_free()
			ResourceLoader.THREAD_LOAD_LOADED:
				var scene = ResourceLoader.load_threaded_get(m_stPath).instantiate()
				
				m_bDone = true
				loaded.emit(self,scene)
				queue_free()
			_:
				fatalError.emit(self)
				queue_free()

Tips:
To display errors in the debug panel:

push_error("string")
push_warning("string")

To display errors in the output panel:

printerr("string")