Skip to content
Snippets Groups Projects
Verified Commit 1d437441 authored by Louis's avatar Louis :fire:
Browse files

Import from Advent codebase

parent cb6b95a9
No related branches found
No related tags found
No related merge requests found
# Generated by Cargo
# will have compiled files and executables
/target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
.idea/
\ No newline at end of file
[package]
name = "micro_banimate"
version = "0.1.0"
edition = "2021"
license = "Apache-2.0"
description = "Easily manage complex Bevy 2D sprite animations"
authors = [
"Louis Capitanchik <louis@microhacks.co.uk>"
]
[features]
default = ["json_loader", "ecs_tilemap"]
json_loader = ["serde", "dep:serde_json"]
toml_loader = ["serde", "dep:toml"]
ecs_tilemap = ["dep:bevy_ecs_tilemap"]
serde = ["dep:serde"]
[dependencies]
anyhow = "1.0.65"
serde = { version = "1.0.145", optional = true }
serde_json = { version = "1.0.85", optional = true }
toml = { version = "0.5.9", optional = true }
bevy = { version = "0.8.1", default-features = false, features = ["bevy_asset", "render"] }
bevy_ecs_tilemap = { version = "0.7.0", optional = true }
[toolchain]
channel = "stable"
\ No newline at end of file
hard_tabs = true
#group_imports = "StdExternalCrate"
use_field_init_shorthand = true
use_try_shorthand = true
\ No newline at end of file
use std::collections::HashMap;
use std::ops::{Deref, DerefMut};
use bevy::asset::Handle;
use bevy::prelude::{Bundle, Component};
use bevy::reflect::TypeUuid;
use crate::directionality::Directionality;
#[derive(Clone, PartialOrd, PartialEq, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AnimationFrames {
pub frames: Vec<usize>,
pub frame_secs: f32,
}
impl AnimationFrames {
pub fn len_secs(&self) -> f32 {
self.frames.len() as f32 * self.frame_secs
}
}
#[derive(Clone, Debug, TypeUuid, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[uuid = "a2823f96-0f63-434e-9030-d8f762898a18"]
pub struct AnimationSet(pub HashMap<String, AnimationFrames>);
impl Deref for AnimationSet {
type Target = HashMap<String, AnimationFrames>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for AnimationSet {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[derive(Copy, Clone, Debug, Component, PartialEq, Eq, Ord, PartialOrd, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SyncAnimationsToParent;
#[derive(Copy, Clone, Debug, Component, PartialEq, Eq, Ord, PartialOrd, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct HasAnimations;
#[derive(Copy, Clone, Debug, Component, PartialEq, Eq, Ord, PartialOrd, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct HasSimpleAnimations;
#[derive(Copy, Clone, Debug, Component, PartialEq, Eq, Ord, PartialOrd, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct HasDirectionalityAnimation;
#[derive(Copy, Clone, Debug, Component, PartialEq, Eq, Ord, PartialOrd, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AnimationPaused;
#[derive(Copy, Clone, Debug, Component, PartialEq, Eq, Ord, PartialOrd, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AnimationUserData(pub u128);
#[derive(Clone, Debug, Component, PartialEq, Eq, Ord, PartialOrd, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum AnimationMode {
#[default]
Loop,
Once,
OnceThenPlay(String),
}
#[derive(Clone, Debug, Component, PartialEq, PartialOrd, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AnimationStatus {
pub active_name: String,
pub active_step: usize,
pub frame_time: f32,
}
impl AnimationStatus {
pub fn assert_animation<T: ToString>(&mut self, name: T) {
self.active_name = name.to_string();
}
pub fn start_animation<T: ToString>(&mut self, name: T) {
self.active_name = name.to_string();
self.active_step = 0;
}
pub fn start_or_continue<T: ToString>(&mut self, name: T) {
let name = name.to_string();
if self.active_name != name {
self.active_name = name;
self.active_step = 0;
}
}
}
#[derive(Clone, Debug, Bundle, PartialEq, PartialOrd, Default)]
pub struct DirectionalSpriteAnimationBundle {
pub animation_handle: Handle<AnimationSet>,
pub mode: AnimationMode,
pub status: AnimationStatus,
pub direction: Directionality,
pub marker: HasDirectionalityAnimation,
}
impl DirectionalSpriteAnimationBundle {
pub fn new(initial_anim: String, handle: Handle<AnimationSet>) -> Self {
Self {
animation_handle: handle,
status: AnimationStatus {
active_name: initial_anim,
active_step: 0,
frame_time: 0.0,
},
mode: AnimationMode::Loop,
direction: Directionality::default(),
marker: HasDirectionalityAnimation,
}
}
pub fn with_initial_facing(
initial_anim: String,
handle: Handle<AnimationSet>,
direction: Directionality,
) -> Self {
Self {
animation_handle: handle,
status: AnimationStatus {
active_name: initial_anim,
active_step: 0,
frame_time: 0.0,
},
mode: AnimationMode::Loop,
marker: HasDirectionalityAnimation,
direction,
}
}
}
#[derive(Clone, Debug, Bundle, PartialEq, PartialOrd, Default)]
pub struct SpriteAnimationBundle {
pub animation_handle: Handle<AnimationSet>,
pub mode: AnimationMode,
pub status: AnimationStatus,
pub marker: HasAnimations,
}
impl SpriteAnimationBundle {
pub fn new(initial_anim: String, handle: Handle<AnimationSet>) -> Self {
Self {
animation_handle: handle,
status: AnimationStatus {
active_name: initial_anim,
active_step: 0,
frame_time: 0.0,
},
mode: AnimationMode::Loop,
marker: HasAnimations,
}
}
}
#[derive(Clone, Debug, Component, PartialEq, PartialOrd, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SimpleLoopedAnimation {
pub frames: Vec<usize>,
pub frame_secs: f32,
}
#[derive(Copy, Clone, Debug, Component, PartialEq, PartialOrd, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SimpleLoopedAnimationStatus {
pub active_step: usize,
pub frame_time: f32,
}
#[derive(Clone, Debug, Bundle, PartialEq, PartialOrd, Default)]
pub struct SimpleAnimationBundle {
pub anim: SimpleLoopedAnimation,
pub status: SimpleLoopedAnimationStatus,
pub marker: HasSimpleAnimations,
}
impl SimpleAnimationBundle {
pub fn new(frames: Vec<usize>, frame_secs: f32) -> Self {
SimpleAnimationBundle {
anim: SimpleLoopedAnimation { frames, frame_secs },
status: SimpleLoopedAnimationStatus {
active_step: 0,
frame_time: 0.0,
},
marker: HasSimpleAnimations,
}
}
}
#[derive(Clone, Debug, Component, PartialEq, Eq, PartialOrd, Ord, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AnimationOverride {
pub name: String,
pub user_data: u128,
}
#[derive(Clone, Debug, Bundle, PartialEq, PartialOrd, Default)]
pub struct ChildAnimationBundle {
pub animation_handle: Handle<AnimationSet>,
pub status: AnimationStatus,
pub marker: SyncAnimationsToParent,
}
impl ChildAnimationBundle {
pub fn new(handle: Handle<AnimationSet>) -> Self {
Self {
animation_handle: handle,
..Default::default()
}
}
}
use std::fmt::{Display, Formatter};
use bevy::math::{Vec2, Vec3};
use bevy::prelude::Component;
#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Horizontal {
Left,
#[default]
Right,
}
impl From<f32> for Horizontal {
fn from(other: f32) -> Self {
if other < 0.0 {
Self::Left
} else {
Self::Right
}
}
}
impl Display for Horizontal {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Horizontal::Left => f.write_str("left"),
Horizontal::Right => f.write_str("right"),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Vertical {
Up,
#[default]
Down,
}
impl From<f32> for Vertical {
fn from(other: f32) -> Self {
if other < 0.0 {
Self::Up
} else {
Self::Down
}
}
}
impl Display for Vertical {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Vertical::Up => f.write_str("up"),
Vertical::Down => f.write_str("down"),
}
}
}
#[derive(Clone, Debug, Component, PartialEq, Eq, Ord, PartialOrd, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Directionality {
pub vertical: Vertical,
pub horizontal: Horizontal,
}
impl From<Vec2> for Directionality {
fn from(other: Vec2) -> Self {
Self {
horizontal: other.x.into(),
vertical: other.y.into(),
}
}
}
impl From<Vec3> for Directionality {
fn from(other: Vec3) -> Self {
Self {
horizontal: other.x.into(),
vertical: other.y.into(),
}
}
}
impl Display for Directionality {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}_{}", self.horizontal, self.vertical)
}
}
pub mod definitions;
pub mod directionality;
pub mod loader;
pub mod query;
pub mod systems;
mod plugin {
use bevy::app::{PluginGroup, PluginGroupBuilder};
use crate::loader;
pub struct AdventAnimationsPlugin;
impl PluginGroup for AdventAnimationsPlugin {
fn build(&mut self, group: &mut PluginGroupBuilder) {
group.add(super::systems::AnimationSystemsPlugin);
#[cfg(any(feature = "json_loader", feature = "toml_loader"))]
group.add(loader::AnimationLoadersPlugin);
}
}
}
pub use plugin::AdventAnimationsPlugin;
#[cfg(feature = "json_loader")]
mod json {
use bevy::asset::{AssetLoader, BoxedFuture, Error, LoadContext, LoadedAsset};
use crate::definitions::AnimationSet;
pub struct AnimationSetJsonLoader;
impl AssetLoader for AnimationSetJsonLoader {
fn load<'a>(
&'a self,
bytes: &'a [u8],
load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, anyhow::Result<(), Error>> {
Box::pin(async move {
let value: AnimationSet = serde_json::from_slice(bytes)?;
load_context.set_default_asset(LoadedAsset::new(value));
Ok(())
})
}
fn extensions(&self) -> &[&str] {
static EXTENSIONS: &[&str] = &["anim.json"];
EXTENSIONS
}
}
}
#[cfg(feature = "toml_loader")]
mod toml {
use bevy::asset::{AssetLoader, BoxedFuture, Error, LoadContext, LoadedAsset};
use crate::definitions::AnimationSet;
pub struct AnimationSetTomlLoader;
impl AssetLoader for AnimationSetTomlLoader {
fn load<'a>(
&'a self,
bytes: &'a [u8],
load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, anyhow::Result<(), Error>> {
Box::pin(async move {
let value: AnimationSet = toml::from_slice(bytes)?;
load_context.set_default_asset(LoadedAsset::new(value));
Ok(())
})
}
fn extensions(&self) -> &[&str] {
static EXTENSIONS: &[&str] = &["anim.toml"];
EXTENSIONS
}
}
}
use bevy::app::App;
use bevy::prelude::{AddAsset, Plugin};
pub struct AnimationLoadersPlugin;
impl Plugin for AnimationLoadersPlugin {
fn build(&self, app: &mut App) {
app.add_asset::<crate::definitions::AnimationSet>();
#[cfg(feature = "json_loader")]
app.add_asset_loader(json::AnimationSetJsonLoader);
#[cfg(feature = "toml_loader")]
app.add_asset_loader(self::toml::AnimationSetTomlLoader);
}
}
use std::ops::{Deref, DerefMut};
use std::time::Duration;
use anyhow::{Error, Result};
use bevy::ecs::system::SystemParam;
use bevy::prelude::*;
use crate::definitions::{
AnimationFrames, AnimationMode, AnimationOverride, AnimationPaused, AnimationSet,
AnimationStatus, HasAnimations, HasDirectionalityAnimation, HasSimpleAnimations,
SyncAnimationsToParent,
};
use crate::directionality::{Directionality, Horizontal, Vertical};
use crate::systems::AnimationCompleted;
/// Manage animated entities
#[derive(SystemParam)]
pub struct AnimationQuery<'w, 's> {
commands: Commands<'w, 's>,
animations: Res<'w, Assets<AnimationSet>>,
inner: Query<
'w,
's,
(
Entity,
&'static Handle<AnimationSet>,
&'static mut AnimationMode,
&'static mut AnimationStatus,
),
(
Or<(With<HasAnimations>, With<HasDirectionalityAnimation>)>,
Without<HasSimpleAnimations>,
Without<SyncAnimationsToParent>,
),
>,
inner_child: Query<
'w,
's,
(
Entity,
&'static Parent,
&'static Handle<AnimationSet>,
&'static mut AnimationStatus,
),
(
With<SyncAnimationsToParent>,
Without<HasAnimations>,
Without<HasDirectionalityAnimation>,
Without<HasSimpleAnimations>,
),
>,
direction: Query<'w, 's, &'static mut Directionality>,
action_animation: Query<'w, 's, &'static AnimationOverride>,
tile_sprite: Query<'w, 's, &'static mut TextureAtlasSprite>,
paused: Query<'w, 's, Entity, With<AnimationPaused>>,
events: EventWriter<'w, 's, AnimationCompleted>,
}
impl<'w, 's> Deref for AnimationQuery<'w, 's> {
type Target = Query<
'w,
's,
(
Entity,
&'static Handle<AnimationSet>,
&'static mut AnimationMode,
&'static mut AnimationStatus,
),
(
Or<(With<HasAnimations>, With<HasDirectionalityAnimation>)>,
Without<HasSimpleAnimations>,
Without<SyncAnimationsToParent>,
),
>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<'w, 's> DerefMut for AnimationQuery<'w, 's> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
impl<'w, 's> AnimationQuery<'w, 's> {
/// Given an entity, ensure that the currently playing animation matches
/// the provided name. This method does not change the current animation
/// step
pub fn assert_animation<T: ToString>(&mut self, e: Entity, name: T) -> Result<()> {
let (_, _, _, mut status) = self.inner.get_mut(e)?;
status.assert_animation(name);
Ok(())
}
/// Given an entity, start an animation from that entity's animation set
/// from frame 0. If the currently playing animation is requested, it
/// will be restarted
pub fn start_animation<T: ToString>(&mut self, e: Entity, name: T) -> Result<()> {
let (_, _, _, mut status) = self.inner.get_mut(e)?;
status.start_animation(name);
Ok(())
}
/// Given an entity, start an animation from that entity's animation set
/// from frame 0. If the currently playing animation is requested, no change
/// will happen and it will continue to play uninterrupted
pub fn start_or_continue_animation<T: ToString>(&mut self, e: Entity, name: T) -> Result<()> {
let (_, _, _, mut status) = self.inner.get_mut(e)?;
status.start_or_continue(name);
Ok(())
}
/// Given an entity, start playing an animation once and then switch
/// to a looped version of another animation. If the currently playing
/// animation is requested, it will be restarted
pub fn start_animation_and_then<T: ToString, Y: ToString>(
&mut self,
e: Entity,
name: T,
next: Y,
) -> Result<()> {
let (_, _, mut mode, mut status) = self.inner.get_mut(e)?;
*mode = AnimationMode::OnceThenPlay(next.to_string());
status.active_name = name.to_string();
status.active_step = 0;
Ok(())
}
/// Given an entity, start playing an animation once and then switch
/// to a looped version of another animation. If the currently playing
/// animation is requested, it will be restarted
pub fn continue_animation_and_then<T: ToString, Y: ToString>(
&mut self,
e: Entity,
name: T,
next: Y,
) -> Result<()> {
let (_, _, mut mode, mut status) = self.inner.get_mut(e)?;
let name = name.to_string();
*mode = AnimationMode::OnceThenPlay(next.to_string());
if status.active_name != name {
status.active_name = name;
status.active_step = 0;
}
Ok(())
}
/// Set an entity's animations to play one time. The animation
/// will pause on the last frame
pub fn play_once(&mut self, e: Entity) -> Result<()> {
let (_, _, mut mode, _) = self.inner.get_mut(e)?;
*mode = AnimationMode::Once;
Ok(())
}
/// Set an entity's animations to loop continuously. The
/// animation will reset to the start once it has reached the
/// last frame
pub fn play_loop(&mut self, e: Entity) -> Result<()> {
let (_, _, mut mode, _) = self.inner.get_mut(e)?;
*mode = AnimationMode::Loop;
Ok(())
}
/// Set an entity's animations to play one time. The animation
/// will be changed to the specified set once the current animation
/// has reached the last frame
pub fn play_next<T: ToString>(&mut self, e: Entity, next: T) -> Result<()> {
let (_, _, mut mode, _) = self.inner.get_mut(e)?;
*mode = AnimationMode::OnceThenPlay(next.to_string());
Ok(())
}
/// Measure the amount of time it will take an animation to run.
/// The exact time that an animation runs for will differ slightly
/// from this, due to the minimum time increment being 1 frame
/// (16ms when running at 60fps)
pub fn get_animation_duration<T: ToString>(
&self,
handle: Handle<AnimationSet>,
name: T,
) -> Result<Duration> {
let sheet = self
.animations
.get(&handle)
.ok_or_else(|| anyhow::Error::msg("Failed to fetch animation set"))?;
let details = sheet.get(name.to_string().as_str()).ok_or_else(|| {
anyhow::Error::msg(format!("Missing animation from set: {}", name.to_string()))
})?;
Ok(Duration::from_secs_f32(
details.frame_secs * details.frames.len() as f32,
))
}
pub fn resolve_animation(
&self,
handle: &Handle<AnimationSet>,
name: &String,
) -> Option<&AnimationFrames> {
self.animations
.get(handle)
.and_then(|sheet| sheet.get(name))
}
/// Check whether or not an entity is currently paused. If so,
/// the entity will not tick its animation state
pub fn is_paused(&self, e: Entity) -> bool {
self.paused.get(e).is_ok()
}
/// Attach a "paused" marker to this entity that will cause
/// standard animations to not tick. This is safe to call
/// with an entity that might already be paused; no effect will
/// take place
pub fn pause(&mut self, e: Entity) {
self.commands.entity(e).insert(AnimationPaused);
}
/// Remove any "paused" markers from this entity, which will cause
/// standard animations to resume ticking. This is safe to call
/// with an entity that might already be paused; no effect will
/// take place
pub fn unpause(&mut self, e: Entity) {
self.commands.entity(e).remove::<AnimationPaused>();
}
pub fn set_direction(&mut self, e: Entity, horizontal: Horizontal, vertical: Vertical) {
if let Ok(mut direction) = self.direction.get_mut(e) {
direction.horizontal = horizontal;
direction.vertical = vertical;
}
}
pub fn set_partial_direction(
&mut self,
e: Entity,
horizontal: Option<Horizontal>,
vertical: Option<Vertical>,
) {
if let Ok(mut direction) = self.direction.get_mut(e) {
if let Some(horizontal) = horizontal {
direction.horizontal = horizontal;
}
if let Some(vertical) = vertical {
direction.vertical = vertical;
}
}
}
/// Apply a number of delta seconds to all unpaused animations
pub fn tick_all(&mut self, dt: f32) -> Result<()> {
for (entity, handle, mut mode, mut status) in &mut self.inner {
if self.paused.get(entity).is_ok() {
continue;
}
let sheet = match self.animations.get(handle) {
Some(sheet) => sheet,
None => continue,
};
let current = match sheet.get(&status.active_name) {
Some(set) => set,
None => continue,
};
status.frame_time += dt;
while status.frame_time >= current.frame_secs {
status.frame_time -= current.frame_secs;
status.active_step += 1;
}
if status.active_step >= current.frames.len() {
match mode.clone() {
AnimationMode::Loop => {
status.active_step = 0;
}
AnimationMode::Once => {
status.active_step = current.frames.len() - 1;
}
AnimationMode::OnceThenPlay(next) => {
*mode = AnimationMode::Loop;
status.active_name = next.clone();
status.frame_time = 0.0;
}
}
if let Ok(action_anim) = self.action_animation.get(entity) {
self.commands.entity(entity).remove::<AnimationOverride>();
self.events.send(AnimationCompleted {
entity,
user_data: action_anim.user_data,
});
}
}
if let Ok(mut sprite) = self.tile_sprite.get_mut(entity) {
sprite.index = current.frames[status.active_step];
}
}
Ok(())
}
/// Syncs all animations that are set to track their parents
pub fn sync_parent_animations(&mut self) {
for (entity, parent, handle, mut status) in &mut self.inner_child {
if let Ok((_, _, _, parent_status)) = self.inner.get(**parent) {
*status = parent_status.clone();
}
if let Ok(mut sprite) = self.tile_sprite.get_mut(entity) {
if let Some(current) = self
.animations
.get(handle)
.and_then(|sheet| sheet.get(&status.active_name))
{
sprite.index = current.frames[status.active_step];
}
}
}
}
pub fn apply_directionality_to<T: ToString>(&mut self, action: T, e: Entity) -> Result<()> {
if self.paused.get(e).is_ok() {
return Ok(());
}
let (_, animations, _, mut status) = self.inner.get_mut(e)?;
let direction = self.direction.get(e)?;
let over = self.action_animation.get(e).ok();
let set = self
.animations
.get(animations)
.ok_or_else(|| Error::msg("Missing Animation"))?;
let fallback = over
.map(|o| o.name.clone())
.unwrap_or_else(|| action.to_string());
let directed = format!("{}_{}", &fallback, direction);
// log::info!("Directed - {}", directed);
if set.contains_key(&directed) {
status.start_or_continue(directed);
} else {
status.start_or_continue(fallback);
}
Ok(())
}
}
use bevy::prelude::*;
use crate::definitions::*;
use crate::query::AnimationQuery;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, SystemLabel)]
pub enum AnimationSystems {
TickAnimations,
SyncAnimations,
}
#[derive(Copy, Clone, Debug, Component, PartialEq, Eq, Ord, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AnimationCompleted {
pub entity: Entity,
pub user_data: u128,
}
pub fn tick_animations(time: Res<Time>, mut query: AnimationQuery) {
let seconds = time.delta_seconds();
let _ = query.tick_all(seconds);
}
pub fn tick_simple_sprite_animations(
time: Res<Time>,
mut query: Query<
(
&SimpleLoopedAnimation,
&mut SimpleLoopedAnimationStatus,
&mut TextureAtlasSprite,
),
With<HasSimpleAnimations>,
>,
) {
let seconds = time.delta_seconds();
for (animation, mut state, mut sprite) in &mut query {
state.frame_time += seconds;
while state.frame_time >= animation.frame_secs {
state.frame_time -= animation.frame_secs;
state.active_step += 1;
if state.active_step >= animation.frames.len() {
state.active_step = 0;
}
}
sprite.index = animation.frames[state.active_step];
}
}
pub fn sync_parent_animations(mut query: AnimationQuery) {
query.sync_parent_animations();
}
#[cfg(feature = "ecs_tilemap")]
pub fn tick_simple_tilemap_animation(
time: Res<Time>,
mut query: Query<
(
&SimpleLoopedAnimation,
&mut SimpleLoopedAnimationStatus,
&mut bevy_ecs_tilemap::tiles::TileTexture,
),
With<HasSimpleAnimations>,
>,
) {
let seconds = time.delta_seconds();
for (animation, mut state, mut tile) in &mut query {
state.frame_time += seconds;
while state.frame_time >= animation.frame_secs {
state.frame_time -= animation.frame_secs;
state.active_step += 1;
if state.active_step >= animation.frames.len() {
state.active_step = 0;
}
}
*tile = bevy_ecs_tilemap::tiles::TileTexture(animation.frames[state.active_step] as u32);
}
}
pub struct AnimationSystemsPlugin;
impl Plugin for AnimationSystemsPlugin {
fn build(&self, app: &mut App) {
let mut tick_systems = SystemSet::new()
.label(AnimationSystems::TickAnimations)
.with_system(tick_animations)
.with_system(tick_simple_sprite_animations);
#[cfg(feature = "ecs_tilemap")]
{
tick_systems = tick_systems.with_system(tick_simple_tilemap_animation);
}
app.add_event::<AnimationCompleted>()
.add_system_set_to_stage(CoreStage::PostUpdate, tick_systems)
.add_system_to_stage(
CoreStage::PostUpdate,
sync_parent_animations
.label(AnimationSystems::SyncAnimations)
.after(AnimationSystems::TickAnimations),
);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment