#Animated sprite "attachments"

9 messages · Page 1 of 1 (latest)

coarse sinew
#

I've designed a rough (working!) implementation that animates "attachments" in sync with their parent sprite's animation. My specific use case is for customizable clothing, hair, and other accessories for a player sprite.

How the implementation works:

The player is defined a bundle that contains a marker component, spritesheet bundle, and a benimator animation state component.

During the player spawn logic, any attachments (i.e. hair) are spawned using player.with_children(...). Right now, these attachments are only composed of a spritesheet bundle and marker component.

Finally, I have a monolithic animate_player system that does all of the heavy lifting (which I'm hoping can be simplified):

pub fn animate_player(
    time: Res<Time>,
    animations: Res<Assets<Animation>>,
    mut query: Query<
        (
            &mut AnimationState,
            &mut TextureAtlasSprite,
            &Movement,
            &Handle<Animation>,
        ),
        With<Player>,
    >,
    mut query_attachments: Query<(&PlayerAttachment, &mut TextureAtlasSprite), Without<Player>>,
) {
    for (mut player, mut texture, movement, handle) in &mut query {
        if let Some(direction_offset) = &movement.direction.as_ref().map(|dir| dir.to_offset()) {
            let animation = match animations.get(handle) {
                Some(anim) => anim,
                None => continue,
            };

            player.update(animation, time.delta());
            let index = player.frame_index();

            texture.index = index + direction_offset * 8;

            for (attachment, mut attachment_texture) in &mut query_attachments {
                attachment_texture.index = attachment.get_start_index()
                    + index
                    + direction_offset * attachment.get_row_length());
            }
        } else {
            texture.index = 0;

            for (attachment, mut attachment_texture) in &mut query_attachments {
                attachment_texture.index = attachment.get_start_index();
            }
        }
    }
}

I feel like this system could be broken up into two systems, one for animating the player and another for the player's attachments. However, I'm unsure of how to structure these two systems: specifically, the synchronization of the animation indexes between parent (player) and children (attachments).

EDIT: I'm not too concerned about the nested iteration, although it immediately sticks out to me when self-reviewing. 99% of the time, only one player will exist in the world, and on the occasion when more than one exists (i.e. customization UI that renders the player in a window), I'd like to have support for those player instances also being animated.

coarse sinew
#

I suppose, thinking aloud, this could be repurposed as a more general system (i.e. decoupled from the concept of a "player") for my project. There will probably be instances where I want to have other "layered" sprites that animate synchronously (e.g. NPCs, animals).

I could modify the query parameter to use Without<PlayerAttachment> (rather than With<Player>. This would then remove the need to have Without<Player> as a qualifier on the query_attachments parameter.

The Movement struct holds data on the direction that an entity is facing (this system is in the context of a top-down 2D world), as well as the current speed and other values. I could probably extract the direction into an individual component, and use that in the query, because the other elements of the movement component are not used in the context of animating.

coarse sinew
#

I guess the crux of my code review request is understanding how I can separate the systems and still synchronize the animation state between parents and children.

pearl stump
#

I think I'd decouple by a resource 🤔
as far as I understand it, you could have a resource of a HashMap<Entity, State>

#

and have every Animation Component have an Option<Entity>

#

if the option is None, they are the parent

#

if the Option is Some(Entity), they are a child of the entity

#

look up animation state in your resource hash map

#
#[derive(Resource)]
struct AnimationStates {
  states: HashMap<Entity, usize>
}

#[derive(Component)]
struct AnimationCoupling {
  parent: Option<Entity>
}

fn animate_all(
  states: Res<AnimationStates>,
  mut query: Query<(&mut TextureAtlasSprite, AnimationCoupling, Entity)>
)
{
  for (mut sprite, couple, entity) in &mut query {
    if let Some(parent) = couple.parent {
      sprite.index = states.states.get(parent).unwrap();
    }
    else {
      sprite.index = states.states.get(entity).unwrap();
    }
  }
}