Godex and Godot communication - reeseschultz/godex GitHub Wiki
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 aSystem
, because theSystem
s 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 ✌️.