making progress on modular text animation systems

main
Elijah Voigt 2 years ago
parent 8a5a8d39d4
commit 09d48951bc

@ -12,6 +12,6 @@ fn main() {
}), }),
..default() ..default()
})) }))
.add_plugin(debug::DebugInfo) .add_plugin(debug::DebugInfoPlugin)
.run() .run()
} }

@ -2,7 +2,7 @@ use std::time::Duration;
use bevy::prelude::*; use bevy::prelude::*;
use monologue_trees::*; use monologue_trees::{debug::*, text::*};
const LOREM: [&str; 5] = [ const LOREM: [&str; 5] = [
"Ullam nostrum aut amet adipisci consequuntur quisquam nemo consequatur. Vel eum et ullam ullam aperiam earum voluptas consequuntur. Blanditiis earum voluptatem voluptas animi dolorum fuga aliquam ea.\n", "Ullam nostrum aut amet adipisci consequuntur quisquam nemo consequatur. Vel eum et ullam ullam aperiam earum voluptas consequuntur. Blanditiis earum voluptatem voluptas animi dolorum fuga aliquam ea.\n",
@ -22,13 +22,14 @@ fn main() {
}), }),
..default() ..default()
})) }))
.add_plugin(debug::DebugInfo) .add_plugin(DebugInfoPlugin)
.add_plugin(AnimatedTextPlugin)
.add_startup_system(load_fonts) .add_startup_system(load_fonts)
.add_startup_system(init_ui) .add_startup_system(init_ui)
.add_system(manage_buttons) .add_system(manage_buttons)
.add_system(update_font) .add_system(manage_fonts)
.add_system(mouse_cursor) .add_system(mouse_cursor)
.add_system(play) .add_system(manage_animation)
.run(); .run();
} }
@ -93,20 +94,10 @@ fn init_ui(mut commands: Commands) {
)); ));
parent.spawn(( parent.spawn((
TextBundle::from_sections(LOREM.iter().map(|&section| TextSection { AnimatedTextBundle {
value: section.into(), text_bundle: TextBundle { ..default() },
style: TextStyle { animated_text: AnimatedText::new(TextAnimationType::Typing(12.0)),
font_size: 20.0,
..default()
},
}))
.with_style(Style {
max_size: Size {
width: Val::Px(450.),
height: Val::Undefined,
}, },
..default()
}),
Marker, Marker,
PreviewText, PreviewText,
)); ));
@ -169,7 +160,7 @@ fn manage_buttons(
} }
} }
fn update_font( fn manage_fonts(
mut texts: Query<&mut Text, (With<Marker>, With<PreviewText>)>, mut texts: Query<&mut Text, (With<Marker>, With<PreviewText>)>,
interaction: Query<(&Interaction, &FontButton), Changed<Interaction>>, interaction: Query<(&Interaction, &FontButton), Changed<Interaction>>,
) { ) {
@ -199,7 +190,7 @@ fn mouse_cursor(
} }
} }
fn play( fn manage_animation(
mut texts: Query<&mut Text, (With<Marker>, With<PreviewText>)>, mut texts: Query<&mut Text, (With<Marker>, With<PreviewText>)>,
interactions: Query<&Interaction, (Changed<Interaction>, With<FontButton>)>, interactions: Query<&Interaction, (Changed<Interaction>, With<FontButton>)>,
time: Res<Time>, time: Res<Time>,
@ -209,74 +200,9 @@ fn play(
for interaction in interactions.iter() { for interaction in interactions.iter() {
match interaction { match interaction {
Interaction::Clicked => { Interaction::Clicked => {
if *duration == Duration::ZERO { // Start playing animation
// Get end result desired strings
*desired = texts
.single_mut()
.sections
.iter()
.map(|section| section.value.clone())
.collect();
// Clear current text
texts
.single_mut()
.sections
.iter_mut()
.for_each(|section| section.value = String::new());
}
*duration = Duration::from_secs(30);
} }
_ => (), _ => (),
} }
} }
// PERF: Why does this slow down immediatly?
if *duration > Duration::ZERO {
*duration = duration.saturating_sub(time.delta());
// info!("Delta: {:?}", 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");
}
}
}
} }

@ -6,9 +6,9 @@ use bevy::{
}; };
/// Debug info plugin /// Debug info plugin
pub struct DebugInfo; pub struct DebugInfoPlugin;
impl Plugin for DebugInfo { impl Plugin for DebugInfoPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_startup_system(init) app.add_startup_system(init)
.add_system(toggle) .add_system(toggle)
@ -28,8 +28,6 @@ struct DebugFps;
struct DebugActive; struct DebugActive;
fn init(mut commands: Commands, server: Res<AssetServer>) { fn init(mut commands: Commands, server: Res<AssetServer>) {
// commands.spawn((Camera2dBundle { ..default() }, DebugUi));
commands commands
.spawn(( .spawn((
NodeBundle { NodeBundle {

@ -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…
Cancel
Save