Skip to content

Spawner

Andrea Catania edited this page Jul 22, 2021 · 12 revisions

You can add or remove Components directly from a System using the Storage<ComponentName>.

/// Add the Winner component to the Red team
void my_system(Query<EntityID, RedTeam> &p_query, Storage<Winner> *p_winners_storage) {
	for (auto [entity, red_team] : p_query) {
		p_winners_storage->insert(entity, Winner());
	}
}

However, when you don't know the exact component to add or remove when you write your system, you can use a Spawner: it allows to spawn a specific group of components that the user can specify at editor time. This feature is generally used by plugins or modules with Systems that doesn't know the component to spawn until the game starts.

How it works

Let's imagine the situation where we are creating a plugin that provides a volume; When an entity enters this volume a component (defined by the user at editor time) is added. As you can imagine, the Component defined by the user, is unknown when we create the System that detects the overlap. Instead to fetch a specific Storage, as shown in the beginning of this article, the system fetches a Spawner:

void spawner_example(OverlapSettings* p_settings, Spawner<OverlapEventSpawner> &p_spawner) {
	// Detect the overlap here an assign `entity_id`
	// ...

	p_spawner.insert_dynamic(p_settings->component_to_spawn, entity_id, p_settings->component_data);
}

The user can assign any Component to the Spawner OverlapSettings, at editor time: for example we could add the DamageComponent to the OverlapEventSpawner so to allow the above System to spawn such component.

The alternative to this approach is to fetch the Storage from the World Databag:

void bad_system(OverlapSettings* p_settings, World* p_world) {
	// Detect the overlap here an assign `entity_id`
	// ...

	StorageBase* storage = world->get_storage(p_settings->component_to_spawn);
	storage->insert_dynamic(entity_id, p_settings->component_data);
}

The above approach works just fine, but it's sub-optimal since the System will run in single thread: since it's fetching the entire World.

When you need to add components that you know only at runtime, use the Spawner is a much better option.

💡 The Spawner can only add a specific set of components: this allows Godex to correctly organize the multi-threaded pipeline dispatching.

Syntax

To allow a Spawner to spawn a specific component, it's necessary to specify that during the component definition.

In GDScript this is done by implementing the function get_spawners():

extends Component

func get_spawners():
	return ['OverlapEventSpawner']

var damage_amount: int = 10

While in C++, it's done with the macro SPAWNERS():

struct Damage {
	COMPONENT(Damage, DenseVectorStorage)
	SPAWNERS(OverlapEventSpawner)

	int damage_amount = 10;
};

Now the Damage component can be spawned by the OverlapEventSpawner.

New Spawner & usage

If you want to create an engine feature or a plugin, you may need to define a new Spawner so, it's possible to use the following syntax:

struct OverlapEventSpawner {
	SPAWNER(OverlapEventSpawner)
};

You can fetch it using the Spawner utility with a system:

// Way 1
void spawner_example(Spawner<OverlapEventSpawner> &p_spawner) {
	// ...
	Dictionary data;
	data["damage"] = 10;
	p_spawner.insert_dynamic(component_id, entity_id, data);
}

// Way 2
void spawner_example(Spawner<OverlapEventSpawner> &p_spawner) {
	// ...
	p_spawner.insert(entity_id, Damage(22));
}