use std::time::Duration; /// /// Animated Text /// /// The goal of this code is to get a rudimentary "Text Animation" system in place. /// /// The use cases are: /// * Typing Text; ala RPG dialogs like Pokemon /// /// Eventually adding more would be cool, like movement animations, but those get really /// complicated really fast... /// use bevy::prelude::*; pub struct AnimatedTextPlugin; impl Plugin for AnimatedTextPlugin { fn build(&self, app: &mut App) { app.add_systems(Update, manage_texts) .add_systems(Update, animate_texts); } } #[derive(Bundle, Default)] pub struct AnimatedTextBundle { pub text_bundle: TextBundle, pub animated_text: AnimatedText, } /// Animated Text Marker /// Use this to filter out entities managed by Text Animation systems #[derive(Component, Default, Debug)] pub struct AnimatedText { animation_type: Option, animation_status: TextAnimationStatus, animation_duration: Option, } /// Animated Text component /// /// Handles all of the logistics of running text animation impl AnimatedText { pub fn new(animation_type: TextAnimationType) -> Self { AnimatedText { animation_type: Some(animation_type), ..default() } } pub fn play(&mut self) { self.animation_status = TextAnimationStatus::Playing; self.animation_duration = match self.animation_type { None => None, Some(TextAnimationType::Typing(seconds)) => Some(Duration::from_secs_f32(seconds)), }; } pub fn stop(&mut self) { self.animation_status = TextAnimationStatus::Stopped; self.animation_duration = None; } pub fn toggle(&mut self) { use TextAnimationStatus::*; self.animation_status = match self.animation_status { Playing => Stopped, Stopped => Playing, } } } #[derive(Debug)] pub enum TextAnimationType { Typing(f32), // Typing text out for duration } #[derive(Default, Debug)] enum TextAnimationStatus { Playing, #[default] Stopped, } /// Manage individual text entities /// /// Animated text entities need to conform to different shapes for functional reasons. /// For example if each letter needs to be a different color, each TextSection must be a single /// character rather than a word or paragraph. /// /// Any time a text is updated we need to ensure it conforms to the needs of it's animation /// /// FIXME: Only update according to animation type fn manage_texts(mut texts: Query<&mut Text, (Changed, With)>) { // Check if any Text entities are "dirty" let dirty = texts .iter() .any(|text| text.sections.iter().any(|section| section.value.len() > 1)); // For each text // For each section // If section length > 1 // Break into length-1 strings if dirty { texts.iter_mut().for_each(|mut text| { // Replace the existing sections with broken down sections // Each Text Section is a single character // On it's own this should not cause the text to look different // But means we can modify each text section individually in color text.sections = text .sections .iter() .map(|section| { section.value.chars().map(|c| TextSection { value: c.into(), style: section.style.clone(), }) }) .flatten() .collect(); }); } } fn animate_texts(mut query: Query<(&mut Text, &mut AnimatedText)>, time: Res