I am wondering if the approach I finally worked out is a correct one, or should I came back to the drawing board once again 😛
The game I am working on is a 2D strategy game, where there will be many randomly generated characters for recruitment and afterwards form a party and fight with enemies on maps. So there will be three states of the characters:
- Available for recruitment
- Recruited
- Selected in party
Originally, I planned to keep a hashset of character Ids inside a resource struct and in all underlying systems acessing that resource, getting the Ids and query all characters entities to get the ones with matching Ids.
I felt like it was a little anti-pattern of the ECS - so I reworked it into three Component structs, where the character entitites would be put into as their childrens. This seems more efficient, as I wouldn't need to menage all other data associated with given character manually (their Statistics, Inventory, Skills etc.) - all it can be kept as Components attached to these character entities.
I want to standardize all of the actions, and bind them to underlying structs. I feel that is is a little strange to pass all the queries and commands into underlying functions, but I see the sense in that. Below is the example of one of the Components:
pub struct PlayerAvailableCharacters {
available_lifetime: HashMap<Entity, usize>,
default_lifetime: usize
}
impl PlayerAvailableCharacters {
/// Initializes new component. Allows setting default lifetime of character availability
pub fn init (
commands: &mut Commands,
default_lifetime: usize
) -> Entity {
commands
.spawn(
Self {
available_lifetime: HashMap::new(),
default_lifetime
}
).id()
}
/// Add new character entity into PlayerAvailableCharacters, and set its availability
pub fn add_new_character (
query: &mut Query<(Entity, &mut PlayerAvailableCharacters)>,
commands: &mut Commands,
char_entity: Entity,
custom_lifetime: Option<usize>,
) {
if let Ok((parent, mut chars)) = query.get_single_mut() {
let lifetime = if custom_lifetime.is_none() {
chars.default_lifetime
} else {
custom_lifetime.unwrap()
};
chars.available_lifetime.insert(
char_entity,
lifetime);
commands
.entity(char_entity)
.set_parent(parent);
}
}
/// Tick the lifetime, and remove all characters that passed their availability time
pub fn tick_available_lifetime (
query: &mut Query<(Entity, &mut PlayerAvailableCharacters)>,
custom_ticksize: Option<usize>,
commands: &mut Commands,
) -> Option<usize> {
if let Ok((_, mut chars)) = query.get_single_mut() {
let ticksize = if custom_ticksize.is_some() {
custom_ticksize.unwrap()
} else {
1
};
let mut identities_to_remove = HashSet::new();
for (identity, lifetime) in chars.available_lifetime.iter_mut() {
if *lifetime <= ticksize {
identities_to_remove.insert(identity.clone());
} else {
*lifetime -= &ticksize;
}
};
let count = identities_to_remove.len();
for identity in identities_to_remove {
commands.entity(identity).despawn_recursive();
chars.available_lifetime.remove(&identity);
}
return Some(count);
}
return None;
}
}