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_background = "#000000FF"
# Higher rate is slower typing speed. Integers only!
delay = 1.0
delay = 0.1
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.

@ -1,11 +1,7 @@
use std::collections::VecDeque;
use bevy::{core::FrameCount, utils::HashMap};
use bevy::core::FrameCount;
use crate::prelude::*;
use self::ui::TextScroll;
pub(crate) struct IntroPlugin;
impl Plugin for IntroPlugin {
@ -20,18 +16,21 @@ impl Plugin for IntroPlugin {
.add_systems(OnExit(GameState::Intro), deactivate::<Intro>)
.add_systems(
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(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(
Update,
// Play intro manages playing the intro of each individual paragraph
play_intro.run_if(
any_component_added::<TextScroll>
.or_else(any_component_removed::<TextScroll>())
.or_else(|keys: Res<Input<KeyCode>>| -> bool { keys.just_pressed(KeyCode::Return) })
)
// Runs every time the TextScroll component (managed by manage_scroll_text_animation) is updated
scroll_text
.run_if(in_state(GameState::Intro))
.run_if(any_with_component::<ui::TextScroll>()),
);
}
}
@ -68,7 +67,6 @@ fn init_intro_text(
align_items: AlignItems::Center,
flex_direction: FlexDirection::Column,
position_type: PositionType::Absolute,
padding: UiRect::all(Val::Px(50.0)),
..default()
},
background_color: Color::NONE.into(),
@ -77,12 +75,18 @@ fn init_intro_text(
},
))
.with_children(|parent| {
texts.iter().for_each(|text| {
parent
.spawn((
Intro,
NodeBundle {
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()
},
background_color: Color::hex(&background_hex).unwrap().into(),
@ -90,18 +94,35 @@ fn init_intro_text(
},
))
.with_children(|parent| {
texts.iter().for_each(|text| {
parent.spawn((
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(),
style: TextStyle {
font_size: 16.0,
color: Color::hex(&text_hidden_hex).unwrap(),
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(
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,
) {
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
// Each paragrpah types out letter by letter in a separate animation handled by play_text_scroll
// This simply marks each paragraph for play and (should) stay dormant while those animations are playing
fn play_intro(
parents: Query<Entity, (With<Node>, With<Intro>, With<TextScroll>)>,
mut texts: Query<(Entity, Option<&mut TextScroll>), (With<Text>, With<Intro>)>,
fn manage_scroll_text_animation(
roots: Query<Entity, With<ui::TextScrollAnimation>>,
texts: Query<Entity, (With<Text>, With<Intro>)>,
animated_texts: Query<&ui::TextScroll>,
children: Query<&Children>,
mut queues: Local<HashMap<Entity, VecDeque<Entity>>>,
mut commands: Commands,
tweaks_file: Res<tweak::GameTweaks>,
tweaks: Res<Assets<tweak::Tweaks>>,
time: Res<Time>,
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
parents.iter().for_each(|p| {
// If the hash does not contain this parent key, add the VecDeque of child entities
if !(*queues).contains_key(&p) {
(*queues).insert(p, texts.iter_many(children.iter_descendants(p)).map(|(e, _)| e).collect());
// Create an iterator over all paragraphs
let mut paragraphs = texts.iter_many(children.iter_descendants(r));
if curr.is_some() {
// Hide the last active entity
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 {
// We have played all child entities, so remove the animate marker
commands.entity(p).remove::<TextScroll>();
commands.entity(r).remove::<ui::TextScrollAnimation>();
}
})
}
});
}
// Upon entering the Intro state, start the intro "animation"
fn play_text_scroll(
fn scroll_text(
mut texts: Query<(Entity, &mut Visibility, &mut Text, &mut ui::TextScroll)>,
tweaks_file: Res<tweak::GameTweaks>,
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,
) {
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
text.iter_mut().for_each(|(e, mut t, mut v, ui::TextScroll { progress, .. })| {
// If this is the first frame of the animation, make the object visibility
if *progress == 0 {
*v = Visibility::Inherited;
// If the animation just started, make visible
if text_scroll.progress == 0 {
*vis = 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_color = Color::hex(&text_visible_hex).unwrap();
let text_color = Color::hex(text_visible_hex).unwrap();
// Iterate over all characters in the intro text
t.sections
.iter_mut()
.enumerate()
// Only operate on sections up to this point
.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();
// Update characters in this paragraph
// PERF: Re-doing a lot of work every loop
text.sections.iter_mut().enumerate().for_each(|(i, s)| {
if i <= text_scroll.progress {
s.style.color = text_color;
}
});
// Once the animation is done, remove the marker component
if t.sections.iter().all(|s| s.style.color == text_color) {
commands.entity(e).remove::<ui::TextScroll>();
// If the entire paragraph is played out, remove the component
if text.sections.iter().all(|s| s.style.color == text_color) {
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) {
app.add_systems(Startup, initialize)
.add_systems(Update, loading.run_if(in_state(GameState::Loading)))
.add_systems(OnEnter(GameState::Loading), (activate::<Loading>, enter_loading))
.add_systems(OnExit(GameState::Loading), (deactivate::<Loading>, exit_loading));
.add_systems(
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
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
/// 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>>) {
cameras.iter_mut().for_each(|mut camera| camera.is_active = false);
cameras
.iter_mut()
.for_each(|mut camera| camera.is_active = false);
}
fn loading(

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

Loading…
Cancel
Save