making progress on modular text animation systems

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

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

@ -2,7 +2,7 @@ use std::time::Duration;
use bevy::prelude::*;
use monologue_trees::*;
use monologue_trees::{debug::*, text::*};
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",
@ -22,13 +22,14 @@ fn main() {
}),
..default()
}))
.add_plugin(debug::DebugInfo)
.add_plugin(DebugInfoPlugin)
.add_plugin(AnimatedTextPlugin)
.add_startup_system(load_fonts)
.add_startup_system(init_ui)
.add_system(manage_buttons)
.add_system(update_font)
.add_system(manage_fonts)
.add_system(mouse_cursor)
.add_system(play)
.add_system(manage_animation)
.run();
}
@ -93,20 +94,10 @@ fn init_ui(mut commands: Commands) {
));
parent.spawn((
TextBundle::from_sections(LOREM.iter().map(|&section| TextSection {
value: section.into(),
style: TextStyle {
font_size: 20.0,
..default()
},
}))
.with_style(Style {
max_size: Size {
width: Val::Px(450.),
height: Val::Undefined,
},
..default()
}),
AnimatedTextBundle {
text_bundle: TextBundle { ..default() },
animated_text: AnimatedText::new(TextAnimationType::Typing(12.0)),
},
Marker,
PreviewText,
));
@ -169,7 +160,7 @@ fn manage_buttons(
}
}
fn update_font(
fn manage_fonts(
mut texts: Query<&mut Text, (With<Marker>, With<PreviewText>)>,
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>)>,
interactions: Query<&Interaction, (Changed<Interaction>, With<FontButton>)>,
time: Res<Time>,
@ -209,74 +200,9 @@ fn play(
for interaction in interactions.iter() {
match interaction {
Interaction::Clicked => {
if *duration == Duration::ZERO {
// 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);
// Start playing animation
}
_ => (),
}
}
// 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
pub struct DebugInfo;
pub struct DebugInfoPlugin;
impl Plugin for DebugInfo {
impl Plugin for DebugInfoPlugin {
fn build(&self, app: &mut App) {
app.add_startup_system(init)
.add_system(toggle)
@ -28,8 +28,6 @@ struct DebugFps;
struct DebugActive;
fn init(mut commands: Commands, server: Res<AssetServer>) {
// commands.spawn((Camera2dBundle { ..default() }, DebugUi));
commands
.spawn((
NodeBundle {

@ -1 +1,3 @@
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