Create Bevy Relations from LDtk Entity References
LDtk allows entities to point to other entities using a field.
This is analogous to a bevy "relation" - a component on one entity that stores the Entity
identifier of another entity.
This chapter goes through one possible method for resolving LDtk entity references as such.
This code is used in the field_instances
cargo example, and facilitates "enemy" entities pointing to another "enemy" entity as their "mother".
Register unresolved reference
First, create a component representing an "unresolved" entity reference, storing the target entity's LDtk iid rather than a bevy Entity
:
#![allow(unused)] fn main() { use bevy::prelude::*; use bevy_ecs_ldtk::prelude::*; #[derive(Debug, Default, Deref, DerefMut, Component)] pub struct UnresolvedMotherRef(Option<EntityIid>); }
Create a method for constructing this component from an &EntityInstance
.
This should retrieve the value of the entity reference field instance on the LDtk entity.
Most likely, you'll use a hard-coded field identifier ("mother" in this example) to find it:
#![allow(unused)] fn main() { use bevy::prelude::*; use bevy_ecs_ldtk::prelude::*; pub struct UnresolvedMotherRef(Option<EntityIid>); impl UnresolvedMotherRef { pub fn from_mother_field(entity_instance: &EntityInstance) -> UnresolvedMotherRef { UnresolvedMotherRef( entity_instance .get_maybe_entity_ref_field("mother") .expect("expected entity to have mother entity ref field") .as_ref() .map(|entity_ref| EntityIid::new(entity_ref.entity_iid.clone())), ) } } }
Add this component to the LdtkEntity
and configure it to be constructed using this method.
This guide assumes that you've already registered this bundle to the app.
#![allow(unused)] fn main() { use bevy::prelude::*; use bevy_ecs_ldtk::prelude::*; #[derive(Debug, Default, Deref, DerefMut, Component)] pub struct UnresolvedMotherRef(Option<EntityIid>); impl UnresolvedMotherRef { fn from_mother_field(_: &EntityInstance) -> UnresolvedMotherRef { todo!() } } #[derive(Default, Bundle, LdtkEntity)] pub struct EnemyBundle { #[with(UnresolvedMotherRef::from_mother_field)] unresolved_mother: UnresolvedMotherRef, #[sprite_sheet_bundle] sprite_sheet_bundle: SpriteSheetBundle, } }
Resolve reference in post-processing
Create a second relational component that stores the actual bevy Entity
that this Unresolved
reference should "resolve" to.
#![allow(unused)] fn main() { use bevy::prelude::*; use bevy_ecs_ldtk::prelude::*; #[derive(Debug, Deref, DerefMut, Component, Reflect)] pub struct Mother(Entity); }
Finally, create a "post-processing" system that takes entities with the Unresolved
component, finds the entity with the matching EntityIid
, and replaces the Unresolved
component with the relational component.
#![allow(unused)] fn main() { use bevy::prelude::*; use bevy_ecs_ldtk::prelude::*; #[derive(Debug, Default, Deref, DerefMut, Component)] pub struct UnresolvedMotherRef(Option<EntityIid>); #[derive(Debug, Deref, DerefMut, Component, Reflect)] pub struct Mother(Entity); pub fn resolve_mother_references( mut commands: Commands, unresolved_mothers: Query<(Entity, &UnresolvedMotherRef), Added<UnresolvedMotherRef>>, ldtk_entities: Query<(Entity, &EntityIid)>, ) { for (child_entity, unresolved_mother_ref) in unresolved_mothers.iter() { if let Some(mother_iid) = unresolved_mother_ref.0.as_ref() { let (mother_entity, _) = ldtk_entities .iter() .find(|(_, iid)| *iid == mother_iid) .expect("enemy's mother entity should exist"); commands .entity(child_entity) .remove::<UnresolvedMotherRef>() .insert(Mother(mother_entity)); } else { commands .entity(child_entity) .remove::<UnresolvedMotherRef>(); } } } }