making progress on modular text animation systems
parent
8a5a8d39d4
commit
09d48951bc
@ -1 +1,3 @@
|
|||||||
pub mod debug;
|
pub mod debug;
|
||||||
|
|
||||||
|
pub mod text;
|
||||||
|
|||||||
@ -0,0 +1,191 @@
|
|||||||
|
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_system(manage_texts).add_system(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)]
|
||||||
|
pub struct AnimatedText {
|
||||||
|
animation_type: Option<TextAnimationType>,
|
||||||
|
animation_status: TextAnimationStatus,
|
||||||
|
animation_duration: Option<Duration>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 enum TextAnimationType {
|
||||||
|
Typing(f32), // Typing text out for duration
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
enum TextAnimationStatus {
|
||||||
|
#[default]
|
||||||
|
Stopped,
|
||||||
|
Playing,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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<Text>>) {
|
||||||
|
// 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), With<AnimatedText>>,
|
||||||
|
time: Res<Time>,
|
||||||
|
) {
|
||||||
|
for (mut text, mut animated_text) in query.iter_mut() {
|
||||||
|
match animated_text.animation_status {
|
||||||
|
TextAnimationStatus::Stopped => (),
|
||||||
|
TextAnimationStatus::Playing => match animated_text.animation_type {
|
||||||
|
None => (),
|
||||||
|
Some(TextAnimationType::Typing(seconds)) => {
|
||||||
|
match animated_text.animation_duration {
|
||||||
|
None => {
|
||||||
|
// We have just started the animation, so set the duration appropriately
|
||||||
|
animated_text.animation_duration =
|
||||||
|
Some(Duration::from_secs_f32(seconds));
|
||||||
|
}
|
||||||
|
Some(mut inner) => {
|
||||||
|
// We are continuing the animation, so decrement the remaining duration
|
||||||
|
inner = Duration::from_secs_f32(
|
||||||
|
inner.as_secs_f32() - time.delta().as_secs_f32(),
|
||||||
|
);
|
||||||
|
|
||||||
|
todo!("Do the work of actually animating the text here");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PERF: Why does this slow down immediatly?
|
||||||
|
// if *duration > Duration::ZERO {
|
||||||
|
// *duration = duration.saturating_sub(time.delta());
|
||||||
|
//
|
||||||
|
// let percentage = 1.0 - (duration.as_secs_f32() / 30.0);
|
||||||
|
//
|
||||||
|
// let mut text = texts.single_mut();
|
||||||
|
//
|
||||||
|
// let len_total = desired.iter().fold(0, |acc, curr| acc + curr.len());
|
||||||
|
// let mut len_total = ((len_total as f32 * percentage) as usize).min(len_total as usize);
|
||||||
|
//
|
||||||
|
// for (section, desired) in text.sections.iter_mut().zip(desired.iter()) {
|
||||||
|
// // This section is empty, initialize with capacity
|
||||||
|
// if len_total == 0 {
|
||||||
|
// break;
|
||||||
|
//
|
||||||
|
// // Total amount of remaining text is greater than this section
|
||||||
|
// // Set this text block to the full section
|
||||||
|
// } else if len_total >= desirted.len() {
|
||||||
|
// // info!("len_total >= desired.len()");
|
||||||
|
// len_total -= desired.len();
|
||||||
|
// if section.value.len() != desired.len() {
|
||||||
|
// // info!("value != desired");
|
||||||
|
// section.value = desired.clone();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Total remaining text is less than this section
|
||||||
|
// // Set to a sub-string of text
|
||||||
|
// } else if len_total < desired.len() {
|
||||||
|
// // info!("len total < desired len");
|
||||||
|
// // Difference between current value and desired length
|
||||||
|
// let diff = desired
|
||||||
|
// .split_at(section.value.len())
|
||||||
|
// .1
|
||||||
|
// .split_at(len_total - section.value.len())
|
||||||
|
// .0
|
||||||
|
// .into();
|
||||||
|
// // info!("adding value {}", diff);
|
||||||
|
// section.value.push_str(diff);
|
||||||
|
// len_total = 0;
|
||||||
|
//
|
||||||
|
// // Undefined behavior
|
||||||
|
// } else {
|
||||||
|
// info!("Unexpected text animation situation");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
Loading…
Reference in New Issue