Skip to content

Godex and Godot communication

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

The power of Godex is that it's Godot baked, and in other words it's possible to take advantage of both the Godot Node mechanism and the Godex ECS mechanism: but how can we make both communicate?

⭐ Before to continue, it's advised to read this article where it's explained how Godot and Godex can stay in the same project.

We may want to use Godex only for the Gameplay logic, but still use Godot nodes like the animation player, the great UI nodes, the particles, etc.. There are two ways to do that:

  • Fetching the nodes from a System, and directly mutating these.
  • Fetching the godex data using a DynamicQuery from a script.

SceneTree manipulation from a System

One way to send information to Godot nodes, is to fetch the node and set the data directly. You can fetch the node within a System, using the databag SceneTreeDatabag.

void update_character_nodes(SceneTreeDatabag* p_scene_tree, Query<MyCharacter, Movement, Health> p_query){
  for(auto [character, movement, health] : p_query) {
    Node* anim_node = p_scene_tree->get_node(character->animation_node_path);
    anim_node->set("running_direction", movement->direction)
    anim_node->set("running_speed", movement->speed)

    Node* hud_node = p_scene_tree->get_node(character->health_hud_path);
    hud_node->set("health", health->health);
  }
}

The above system UpdateCharacterNodes, takes care to propagate the character status to the Animation and update the health bar HUD. This mechanism is really convenient, because you can still use a System to update the SceneTree. You can also create new nodes, load a scene, remove a node; you have full control over the SceneTree directly within the system.

This is the same system but in GDScript:

extends System

func _prepare():
	with_databag(ECS.SceneTreeDatabag, MUTABLE)

	var query := DynamicQuery.new()
	query.with_component(ECS.MyCharacter, IMMUTABLE)
	query.with_component(ECS.Movement, IMMUTABLE)
	query.with_component(ECS.Health, IMMUTABLE)
	with_query(query)

func _execute(scene_tree, query):
	while query.next():
		var anim_node = scene_tree.get_node(query[&"character"].animation_node_path)
		anim_node.running_direction = query[&"movement"].direction
		anim_node.running_speed = query[&"movement"].speed

		var hud_node = scene_tree.get_node(query[&"character"].animation_node_path)
		hud_node.health = query[&"health"].health

⭐ Note, it's safe mutate the SceneTree directly from a System, because the Systems that fetch the SceneTreeDatabag are always executed in single thread.

✍️ Of course you can also extract information from a node and populate a component.

Read from Godex

Within a normal script, we can extract the data from the godex World, and so update the nodes. This is done thanks to the DynamicQuery.

Inside the _process() function, of the AnimationPlayer.gd attached to the AnimationPlayer node, we can have the following code:

var query: DynamicQuery = null

func _ready():
	query = DynamicQuery.new()
	query.with_component(ECS.Player, false)
	query.with_component(ECS.Health, false)
	query.with_component(ECS.Shield, false)
	query.maybe_component(ECS.Stunned, false)
	query.maybe_component(ECS.HasBall, false)
	query.maybe_component(ECS.HasWeapon, false)


func _process(delta: float):
	## Extracts the gamplay info and animate the Character.

	# Prepare the query to extract data from the World
	query.begin(ECS.get_active_world())

	while query.next():
		# Do Stuff here
		# query[&"Player"]
		# query[&"Health"]
		# query[&"Shield"]
		# query[&"Stunned"]
		# query[&"HasBall"]
		# query[&"HasWeapon"]

	query.end()

The above script, in our dear _process() function, extracts data from the Godex World, and perform operation with it: which in this case is animate the Character.

📝 The two presented solutions are complementary. Choose the one that fits better you needs!

Write to Godex

Sometimes, we need to do the opposite operation, we have to bring information from Godot to Godex; this translates to write data inside the World, that guess what, can also be done via DynamicQuery.

Let's suppose that we have an Area that emits a signal when the Character enters, and so we want to decrease the health by 10:

var query: DynamicQuery = null


func _ready():
	query = DynamicQuery.new()
	query.with_component(ECS.Health, true) # This time it's MUTABLE


func _on_Area3D_body_entered(body):
	if body is Character:
		# This body is a character, let's decreate the health.
		query.begin(ECS.get_active_world())

		# This extracts the `Entity` node from the Character that entered the Area.
		# Imagine that the Character scene is as follows:
		# Character (RigidBody) 
		#  |- CharacterEntity (Entity)
		var entity = body.get_child("CharacterEntity")
		
		# If the `World` has this entity, fetch it.
		if query.has(entity.get_entity_id()):
			# Fetches a specific entity ID. You can think about it like data_array[entity_id]
			query.fetch(entity.get_entity_id())

			# Deal damage to this entity.
			query[0].health -= 10; #    same as --> query[&"Health"].health -= 10;

		query.end()

Each time a new Character enters the Area, this script uses the DynamicQuery to fetch the entity and subtract 10 points of health (note health_comp.health -= 10;).

Conclusion

If you need to use a Godot feature, or if for any reason you need to exchange data between Godot and Godex: You can use the presented mechanism. You can choose the best tool you need depending on the feature you have to implement!

I hope it was helpful, if you have any question, or you want to go more in depth, join the community on discord ✌️.