use std::sync::Arc;
use bevy::prelude::*;
use spine_runtime::animation::{
AnimationState, AnimationStateData, Event as SpineEvent, state::StateEvent,
};
use spine_runtime::render::SkeletonRenderer;
use spine_runtime::skeleton::{Physics, Skeleton};
use crate::asset::SpineSkeletonAsset;
use crate::components::{SpineRender2d, SpineRender3d, SpineSkeleton, SpineSkeletonState};
#[derive(SystemSet, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SpineSet {
EnsureMarkers,
Init,
Tick,
BuildMeshes,
Events,
}
#[derive(Component, Debug, Clone, Copy)]
pub struct SpineInitialized;
pub fn initialize_spine_skeletons(
mut commands: Commands,
mut query: Query<(Entity, &mut SpineSkeleton), Without<SpineInitialized>>,
assets: Res<Assets<SpineSkeletonAsset>>,
) {
for (entity, mut sk) in &mut query {
let Some(asset) = assets.get(&sk.asset) else {
continue;
};
let data = Arc::clone(&asset.data);
let mut skeleton = Skeleton::new(Arc::clone(&data));
skeleton.update_cache();
skeleton.setup_pose();
skeleton.update_world_transform(Physics::None);
let state_data = Arc::new(AnimationStateData::new(Arc::clone(&data)));
let mut animation_state = AnimationState::new(state_data);
if let Some(pending) = sk.pending_animation.take()
&& let Err(err) =
animation_state.set_animation_by_name(pending.track, &pending.name, pending.looping)
{
warn!(
"spine_bevy: pending animation {:?} on track {} failed: {err:?}",
pending.name, pending.track
);
}
if let Some(skin) = sk.pending_skin.take() {
if let Err(err) = skeleton.set_skin_by_name(&skin) {
warn!("spine_bevy: pending skin {skin:?} failed: {err:?}");
} else {
skeleton.setup_pose_slots();
}
}
sk.state = Some(SpineSkeletonState {
skeleton,
animation_state,
renderer: SkeletonRenderer::new(),
events: Vec::new(),
meshes: Vec::new(),
materials: Vec::new(),
materials_3d: Vec::new(),
children: Vec::new(),
});
commands.entity(entity).insert(SpineInitialized);
}
}
#[allow(clippy::type_complexity)]
pub fn ensure_spine_render_marker(
mut commands: Commands,
query: Query<
Entity,
(
With<SpineSkeleton>,
Without<SpineRender2d>,
Without<SpineRender3d>,
),
>,
) {
for entity in &query {
commands.entity(entity).insert(SpineRender2d);
}
}
pub fn tick_spine_skeletons(
time: Res<Time>,
mut query: Query<&mut SpineSkeleton, With<SpineInitialized>>,
) {
let base_dt = time.delta_secs();
query.par_iter_mut().for_each(|mut sk| {
if sk.paused {
return;
}
let scale = sk.time_scale;
let physics = sk.physics;
let Some(state) = sk.state.as_mut() else {
return;
};
let dt = base_dt * scale;
state.animation_state.update(dt);
state.events.clear();
state
.animation_state
.apply(&mut state.skeleton, &mut state.events);
state.skeleton.update(dt);
state.skeleton.update_world_transform(physics);
let _ = state.renderer.render(&state.skeleton);
});
}
#[derive(Message, Debug, Clone)]
pub struct SpineStateEvent {
pub entity: Entity,
pub event: StateEvent,
}
#[derive(Message, Debug, Clone)]
pub struct SpineKeyframeEvent {
pub entity: Entity,
pub event: SpineEvent,
}
pub fn drain_spine_events(
mut query: Query<(Entity, &mut SpineSkeleton)>,
mut state_writer: MessageWriter<SpineStateEvent>,
mut keyframe_writer: MessageWriter<SpineKeyframeEvent>,
) {
for (entity, mut sk) in &mut query {
let Some(state) = sk.state.as_mut() else {
continue;
};
for event in state.animation_state.drain_events() {
state_writer.write(SpineStateEvent {
entity,
event: event.clone(),
});
}
for event in state.events.drain(..) {
keyframe_writer.write(SpineKeyframeEvent { entity, event });
}
}
}