#Component constructor function automatically spawning entity

5 messages · Page 1 of 1 (latest)

slate rune
#

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;
  }
}
tiny osprey
#

Maybe I'm overlooking part of the requirement here, but, if there is an existing Entity that represents a character, could you not just attach a component representing the available lifetime directly to that Entity?

// Code is totally unchecked, just for discussion

#[derive(Component)]
pub struct Availability {
    lifetime: usize
}

pub fn add_new_character(commands: &mut Commands, char_entity: Entity) {
    commands
        .entity(char_entity)
        .insert(Availability { lifetime: 128 })
}

pub fn tick_lifetime_system(
    mut query: Query<(Entity, Availability), With<Character>>,
    commands: &mut Commands
) {
    for (char_entity, availability) in query.iter() {
        // tick the lifetime

        if (/* time is up */) {
            commands
                .entity(char_entity)
                .remove::<Availability>()
        }
    }
}
slate rune
# tiny osprey Maybe I'm overlooking part of the requirement here, but, if there is an existing...

Hah, that's the benefit of having someone to discuss these kind of concepts.

Yeah, I think I should simplify it to have just Available, Recruited and InParty Components and have systems to menage all of this. I need to switch from OOPy way of thinking here.

There are some things I should keep track of, like "How many characters are currently Available, Recruited and InParty", and for that I could maybe attach a Resource that will be updated every time some change is made with these character-menagement systems.

slate rune
#

I'm wondering, if I want to keep track of the character count in three categories (so having either the Availability, Recruited or InParty components), wouldn't it be handy to have Resource like below have the FromWorld impl?

#[derive(Resource)]
pub struct CharacterCount {
  pub available: usize,
  pub recruited: usize,
  pub in_party: usize
}

While loading the savestate the World would have the Entities loaded with correct components, so the CharacterCount could use that information - to not have that resource serialized and deserialized during saving the game.

#

Never implemented FromWorld before, though, and didn't saw any example of theimplementation, so I don't know if that is the correct way of thinking though. From what I've read this trait definition it seems like the right call, though