Technically works, but want to tweak showing/hiding text boxes

main
Elijah C. Voigt 2 years ago
parent 2bcc240cc3
commit d5f44b9639

@ -43,7 +43,7 @@ rgba_hidden = "#00000000"
rgba_visible = "#FFFFFFFF" rgba_visible = "#FFFFFFFF"
rgba_background = "#000000FF" rgba_background = "#000000FF"
# Higher rate is slower typing speed. Integers only! # Higher rate is slower typing speed. Integers only!
delay = 1.0 delay = 0.1
text = [ text = [
""" """
At the intersection of humanity's wildest imaginations and the infinite of the cosmos, the lines between possible and real fall apart like dissolving paper. At the intersection of humanity's wildest imaginations and the infinite of the cosmos, the lines between possible and real fall apart like dissolving paper.

@ -1,11 +1,7 @@
use std::collections::VecDeque; use bevy::core::FrameCount;
use bevy::{core::FrameCount, utils::HashMap};
use crate::prelude::*; use crate::prelude::*;
use self::ui::TextScroll;
pub(crate) struct IntroPlugin; pub(crate) struct IntroPlugin;
impl Plugin for IntroPlugin { impl Plugin for IntroPlugin {
@ -20,18 +16,21 @@ impl Plugin for IntroPlugin {
.add_systems(OnExit(GameState::Intro), deactivate::<Intro>) .add_systems(OnExit(GameState::Intro), deactivate::<Intro>)
.add_systems( .add_systems(
Update, Update,
play_text_scroll // Started when the TextScrollAnimation component is added to the parent entity
// Updated for as long as there is scrolling text
manage_scroll_text_animation
.run_if(in_state(GameState::Intro)) .run_if(in_state(GameState::Intro))
.run_if(any_component_changed::<ui::TextScroll>), .run_if(any_component_added::<ui::TextScrollAnimation>.or_else(
|keys: Res<Input<KeyCode>>| -> bool { keys.just_pressed(KeyCode::Return) },
)),
) )
.add_systems( .add_systems(
Update, Update,
// Play intro manages playing the intro of each individual paragraph // Play intro manages playing the intro of each individual paragraph
play_intro.run_if( // Runs every time the TextScroll component (managed by manage_scroll_text_animation) is updated
any_component_added::<TextScroll> scroll_text
.or_else(any_component_removed::<TextScroll>()) .run_if(in_state(GameState::Intro))
.or_else(|keys: Res<Input<KeyCode>>| -> bool { keys.just_pressed(KeyCode::Return) }) .run_if(any_with_component::<ui::TextScroll>()),
)
); );
} }
} }
@ -68,7 +67,6 @@ fn init_intro_text(
align_items: AlignItems::Center, align_items: AlignItems::Center,
flex_direction: FlexDirection::Column, flex_direction: FlexDirection::Column,
position_type: PositionType::Absolute, position_type: PositionType::Absolute,
padding: UiRect::all(Val::Px(50.0)),
..default() ..default()
}, },
background_color: Color::NONE.into(), background_color: Color::NONE.into(),
@ -77,12 +75,18 @@ fn init_intro_text(
}, },
)) ))
.with_children(|parent| { .with_children(|parent| {
texts.iter().for_each(|text| {
parent parent
.spawn(( .spawn((
Intro, Intro,
NodeBundle { NodeBundle {
style: Style { style: Style {
padding: UiRect::all(Val::Px(25.0)), width: Val::Percent(100.0),
justify_content: JustifyContent::Center,
align_content: AlignContent::Center,
flex_direction: FlexDirection::Column,
align_items: AlignItems::Center,
justify_items: JustifyItems::Center,
..default() ..default()
}, },
background_color: Color::hex(&background_hex).unwrap().into(), background_color: Color::hex(&background_hex).unwrap().into(),
@ -90,18 +94,35 @@ fn init_intro_text(
}, },
)) ))
.with_children(|parent| { .with_children(|parent| {
texts.iter().for_each(|text| {
parent.spawn(( parent.spawn((
Intro, Intro,
TextBundle::from_sections(text.chars().into_iter().map(|c| TextSection { TextBundle {
style: Style {
// position_type: PositionType::Absolute,
top: Val::Px(0.0),
left: Val::Px(0.0),
justify_self: JustifySelf::Center,
align_self: AlignSelf::Center,
..default()
},
text: Text {
sections: text
.chars()
.into_iter()
.map(|c| TextSection {
value: c.to_string(), value: c.to_string(),
style: TextStyle { style: TextStyle {
font_size: 16.0, font_size: 16.0,
color: Color::hex(&text_hidden_hex).unwrap(), color: Color::hex(&text_hidden_hex).unwrap(),
font: ui_font.handle.clone(), font: ui_font.handle.clone(),
}, },
})) })
.with_text_alignment(TextAlignment::Center), .collect(),
alignment: TextAlignment::Center,
..default()
},
..default()
},
)); ));
}); });
}); });
@ -109,100 +130,114 @@ fn init_intro_text(
} }
fn start_intro( fn start_intro(
query: Query<Entity, (With<Node>, With<Intro>)>, // Hack, this way of "finding" the Node with our animated Text is precarious
query: Query<Entity, (With<Node>, With<Intro>, With<Children>, Without<Parent>)>,
mut commands: Commands, mut commands: Commands,
) { ) {
query.iter().for_each(|e| { query.iter().for_each(|e| {
commands.entity(e).insert(TextScroll { progress: 0, start: 0 }); commands.entity(e).insert(ui::TextScrollAnimation);
}); });
} }
// Manages playing the intro animation fn manage_scroll_text_animation(
// Each paragrpah types out letter by letter in a separate animation handled by play_text_scroll roots: Query<Entity, With<ui::TextScrollAnimation>>,
// This simply marks each paragraph for play and (should) stay dormant while those animations are playing texts: Query<Entity, (With<Text>, With<Intro>)>,
fn play_intro( animated_texts: Query<&ui::TextScroll>,
parents: Query<Entity, (With<Node>, With<Intro>, With<TextScroll>)>,
mut texts: Query<(Entity, Option<&mut TextScroll>), (With<Text>, With<Intro>)>,
children: Query<&Children>, children: Query<&Children>,
mut queues: Local<HashMap<Entity, VecDeque<Entity>>>, time: Res<Time>,
mut commands: Commands,
tweaks_file: Res<tweak::GameTweaks>,
tweaks: Res<Assets<tweak::Tweaks>>,
framecount: Res<FrameCount>, framecount: Res<FrameCount>,
mut curr: Local<Option<Entity>>,
mut commands: Commands,
) { ) {
info!("Play intro..."); roots.iter().for_each(|r| {
info!("Processing {:?} at frame {:?}", r, framecount.0);
// Skip to next paragraph only the current paragraph's animation is complete
if animated_texts.is_empty() {
info!(
"No animations playing, moving on to next paragraph {:?}",
*curr
);
// Iterate over all UI nodes which are marked for animation // Create an iterator over all paragraphs
parents.iter().for_each(|p| { let mut paragraphs = texts.iter_many(children.iter_descendants(r));
// If the hash does not contain this parent key, add the VecDeque of child entities if curr.is_some() {
if !(*queues).contains_key(&p) { // Hide the last active entity
(*queues).insert(p, texts.iter_many(children.iter_descendants(p)).map(|(e, _)| e).collect()); if let Some(e) = *curr {
commands.entity(e).insert(Visibility::Hidden);
}
// Locate the last entity we were operating with
while let Some(entity) = paragraphs.next() {
if *curr == Some(entity) {
break;
} }
// Fetch the VecDeque so we can play with it
let queue = (*queues).get_mut(&p).unwrap();
// Pop the front of the line, this is the next entity to animate
if let Some(e) = queue.pop_front() {
if let Ok((e, opt_ts)) = texts.get_mut(e) {
// If this entity has a TextScroll already, update it
if let Some(mut ts) = opt_ts {
// Increment the progress counter by some variable amount
let tweak = tweaks.get(tweaks_file.handle.clone()).expect("Load tweaks");
let delay = tweak.get::<f32>("intro_delay").expect("[intro] delay = float");
ts.progress = ((framecount.0 - ts.start) as f32 / delay) as usize;
// Otherwise, initialize it with one
} else {
// Insert a fresh TextScroll into this paragraph
commands.entity(e).insert(TextScroll { start: framecount.0, progress: 0 });
} }
} else {
panic!("This shouldn't happen!");
} }
// Operate on the next entity in the list
*curr = paragraphs.next();
info!("Curr: {:?}", *curr);
// TEMP: Just testing if the paragraph animation works
if let Some(e) = *curr {
commands.entity(e).insert(ui::TextScroll {
progress: 0,
start: time.elapsed_seconds(),
});
} else { } else {
// We have played all child entities, so remove the animate marker commands.entity(r).remove::<ui::TextScrollAnimation>();
commands.entity(p).remove::<TextScroll>();
} }
}) }
});
} }
// Upon entering the Intro state, start the intro "animation" fn scroll_text(
fn play_text_scroll( mut texts: Query<(Entity, &mut Visibility, &mut Text, &mut ui::TextScroll)>,
tweaks_file: Res<tweak::GameTweaks>, tweaks_file: Res<tweak::GameTweaks>,
tweaks: Res<Assets<tweak::Tweaks>>, tweaks: Res<Assets<tweak::Tweaks>>,
mut text: Query<(Entity, &mut Text, &mut Visibility, &ui::TextScroll)>, time: Res<Time>,
keys: Res<Input<KeyCode>>,
mut commands: Commands, mut commands: Commands,
) { ) {
info!("Play text scroll"); let tweak = tweaks.get(tweaks_file.handle.clone()).expect("Load tweaks");
texts
.iter_mut()
.for_each(|(entity, mut vis, mut text, mut text_scroll)| {
debug!("Updating {:?} {:?}", entity, text_scroll);
// Iterate over all (one) text // If the animation just started, make visible
text.iter_mut().for_each(|(e, mut t, mut v, ui::TextScroll { progress, .. })| { if text_scroll.progress == 0 {
// If this is the first frame of the animation, make the object visibility *vis = Visibility::Inherited;
if *progress == 0 {
*v = Visibility::Inherited;
} }
let tweak = tweaks.get(tweaks_file.handle.clone()).expect("Load tweaks"); // If user pressed enter, skip to end of paragraph
text_scroll.progress = if keys.just_pressed(KeyCode::Return) {
usize::MAX
}
// Otherwise, progress by some fixed increment
else {
// Update animation progress for this paragrpah
let delay = tweak.get::<f32>("intro_delay").unwrap();
// Update text_scroll.progress based on frame count
((time.elapsed_seconds() - text_scroll.start) / delay) as usize
};
// Fetch desired color from tweakfile
let text_visible_hex = tweak.get::<String>("intro_rgba_visible").unwrap(); let text_visible_hex = tweak.get::<String>("intro_rgba_visible").unwrap();
let text_color = Color::hex(&text_visible_hex).unwrap(); let text_color = Color::hex(text_visible_hex).unwrap();
// Iterate over all characters in the intro text // Update characters in this paragraph
t.sections // PERF: Re-doing a lot of work every loop
.iter_mut() text.sections.iter_mut().enumerate().for_each(|(i, s)| {
.enumerate() if i <= text_scroll.progress {
// Only operate on sections up to this point s.style.color = text_color;
.filter_map(|(i, s)| (i <= *progress).then_some(s)) }
// Set the alpha to 1.0 making it visible
.for_each(|s| {
s.style.color = text_color.clone();
}); });
// Once the animation is done, remove the marker component // If the entire paragraph is played out, remove the component
if t.sections.iter().all(|s| s.style.color == text_color) { if text.sections.iter().all(|s| s.style.color == text_color) {
commands.entity(e).remove::<ui::TextScroll>(); commands.entity(entity).remove::<ui::TextScroll>();
} }
}); });
} }
// Intro animation reveals one character every nth frame
// Hit enter to skip the "animation"

@ -6,8 +6,14 @@ impl Plugin for LoadingPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(Startup, initialize) app.add_systems(Startup, initialize)
.add_systems(Update, loading.run_if(in_state(GameState::Loading))) .add_systems(Update, loading.run_if(in_state(GameState::Loading)))
.add_systems(OnEnter(GameState::Loading), (activate::<Loading>, enter_loading)) .add_systems(
.add_systems(OnExit(GameState::Loading), (deactivate::<Loading>, exit_loading)); OnEnter(GameState::Loading),
(activate::<Loading>, enter_loading),
)
.add_systems(
OnExit(GameState::Loading),
(deactivate::<Loading>, exit_loading),
);
} }
} }
@ -41,13 +47,17 @@ fn initialize(mut commands: Commands, server: Res<AssetServer>) {
/// On enter loading, activate camera /// On enter loading, activate camera
fn enter_loading(mut cameras: Query<&mut Camera, With<Loading>>) { fn enter_loading(mut cameras: Query<&mut Camera, With<Loading>>) {
cameras.iter_mut().for_each(|mut camera| camera.is_active = true); cameras
.iter_mut()
.for_each(|mut camera| camera.is_active = true);
} }
/// On exit loading, deactivate camera /// On exit loading, deactivate camera
/// This used to be general purpose, but we got rid of the 2d display mode, so it is hard coded for loading -> main game /// This used to be general purpose, but we got rid of the 2d display mode, so it is hard coded for loading -> main game
fn exit_loading(mut cameras: Query<&mut Camera, With<Loading>>) { fn exit_loading(mut cameras: Query<&mut Camera, With<Loading>>) {
cameras.iter_mut().for_each(|mut camera| camera.is_active = false); cameras
.iter_mut()
.for_each(|mut camera| camera.is_active = false);
} }
fn loading( fn loading(

@ -34,7 +34,7 @@ pub(crate) struct UiFont {
#[derive(Debug, Component)] #[derive(Debug, Component)]
pub(crate) struct TextScroll { pub(crate) struct TextScroll {
pub progress: usize, pub progress: usize,
pub start: u32, pub start: f32,
} }
#[derive(Debug, Component)] #[derive(Debug, Component)]

Loading…
Cancel
Save