bevy_map_animation

Crates.iobevy_map_animation
lib.rsbevy_map_animation
version0.3.1
created_at2025-12-21 07:20:37.786521+00
updated_at2026-01-20 11:16:42.627867+00
descriptionAnimation and sprite types for bevy_map_editor
homepage
repositoryhttps://github.com/jbuehler23/bevy_map_editor
max_upload_size
id1997635
size192,738
Joe Buehler (jbuehler23)

documentation

README

bevy_map_animation

Sprite sheet animations for the bevy_map_editor ecosystem.

Part of bevy_map_editor.

Features

  • Define sprite sheets with frame dimensions
  • Multiple named animations per sheet
  • Loop modes: Loop, Once, PingPong
  • Frame-based timing
  • Automatic sprite rect updates
  • Animation Triggers: One-shot events at specific times (sounds, VFX, game events)
  • Animation Windows: Duration-based events with Begin/Tick/End phases (hitboxes, combo windows)
  • Custom Event Types: Type-safe extensible trigger/window system with Bevy Observers

Types

Type Description
SpriteData Sprite sheet definition with animations
AnimationDef Single animation (frames, timing, loop mode)
AnimatedSprite Component for playing animations
AnimationTrigger One-shot trigger definition (time + payload)
AnimationWindow Duration-based window definition (start/end + payload)
LoopMode Loop, Once, or PingPong
WindowTracker Component to enable window event tracking

Events

Event Description
AnimationTriggerEvent Fired when a trigger fires (generic)
AnimationWindowEvent Fired for window phase changes (Begin/Tick/End)
AnimationSoundEvent Convenience event for sound payloads
AnimationParticleEvent Convenience event for particle/VFX payloads
AnimationCustomEvent Convenience event for custom payloads

Usage

Defining Animations (Code)

use bevy_map::animation::{SpriteData, AnimationDef, LoopMode};

let mut sprite = SpriteData::new("sprites/character.png", 32, 32);

sprite.add_animation("idle", AnimationDef {
    frames: vec![0, 1, 2, 3],
    frame_duration_ms: 200,
    loop_mode: LoopMode::Loop,
    ..Default::default()
});

sprite.add_animation("attack", AnimationDef {
    frames: vec![4, 5, 6, 7, 8],
    frame_duration_ms: 80,
    loop_mode: LoopMode::Once,
    ..Default::default()
});

Adding Triggers and Windows

use bevy_map::animation::{AnimationDef, AnimationTrigger, AnimationWindow, TriggerPayload};

let mut attack_anim = AnimationDef::new(vec![4, 5, 6, 7, 8], 80, LoopMode::Once);

// One-shot trigger at 240ms - play impact sound
attack_anim.add_trigger(AnimationTrigger::with_payload(
    "impact",
    240,
    TriggerPayload::Sound {
        path: "sounds/impact.ogg".into(),
        volume: 0.8,
    },
));

// Duration-based window from 160-320ms - hitbox active
attack_anim.add_window(AnimationWindow::with_payload(
    "hitbox",
    160,
    320,
    TriggerPayload::Custom {
        event_name: "attack_hitbox".into(),
        params: [("damage".into(), serde_json::json!(25))].into(),
    },
));

Handling Animation Events

use bevy::prelude::*;
use bevy_map::animation::{AnimationTriggerEvent, AnimationWindowEvent, WindowPhase};

fn handle_triggers(mut events: MessageReader<AnimationTriggerEvent>) {
    for event in events.read() {
        info!("Trigger: {} at {}", event.trigger_name, event.animation);
    }
}

fn handle_windows(mut events: MessageReader<AnimationWindowEvent>) {
    for event in events.read() {
        match event.phase {
            WindowPhase::Begin => info!("Window {} started", event.window_name),
            WindowPhase::Tick => { /* every frame while active */ },
            WindowPhase::End => info!("Window {} ended", event.window_name),
        }
    }
}

Custom Type-Safe Triggers

Define custom trigger types for type-safe event handling with Bevy Observers:

use bevy::prelude::*;
use bevy_map::animation::{AnimationTriggerType, AnimationEventExt};
use serde_json::Value;
use std::collections::HashMap;

#[derive(Event, Clone)]
pub struct AttackHitbox {
    pub damage: i32,
    pub knockback: f32,
}

impl AnimationTriggerType for AttackHitbox {
    fn trigger_name() -> &'static str {
        "attack_hitbox"  // matches event_name in TriggerPayload::Custom
    }

    fn from_params(params: &HashMap<String, Value>) -> Option<Self> {
        Some(Self {
            damage: params.get("damage")?.as_i64()? as i32,
            knockback: params.get("knockback")
                .and_then(|v| v.as_f64())
                .unwrap_or(5.0) as f32,
        })
    }
}

// Register the custom trigger type
app.register_animation_trigger::<AttackHitbox>();

// Handle with Bevy Observer
fn setup(mut commands: Commands) {
    commands.spawn((
        AnimatedSprite::new(/* ... */),
        Observer::new(|trigger: Trigger<AttackHitbox>| {
            let hitbox = trigger.event();
            info!("Attack! Damage: {}", hitbox.damage);
        }),
    ));
}

Playing Animations

use bevy::prelude::*;
use bevy_map::prelude::*;

fn play_animation(mut query: Query<&mut AnimatedSprite>) {
    for mut animated in query.iter_mut() {
        animated.play("idle");
    }
}

Enabling Window Events

Add WindowTracker component to entities that need window event tracking:

commands.spawn((
    AnimatedSprite::new(sprite_data_handle),
    WindowTracker::default(),  // Required for window events
    // ... other components
));

Auto-Loading from Maps

Use AnimatedSpriteHandle for automatic loading:

use bevy_map::runtime::AnimatedSpriteHandle;

commands.spawn((
    AnimatedSpriteHandle::new(
        asset_server.load("maps/game.map.json"),
        "player_sprite",
        "idle",
    ),
    Transform::default(),
));

Plugin

Add SpriteAnimationPlugin for automatic animation updates:

use bevy_map::prelude::*;

app.add_plugins(SpriteAnimationPlugin);

Note: MapRuntimePlugin includes this automatically.

License

MIT OR Apache-2.0

Commit count: 75

cargo fmt