use bevy::core::FrameCount; use crate::prelude::*; pub(crate) struct IntroPlugin; impl Plugin for IntroPlugin { fn build(&self, app: &mut App) { app.init_resource::() .add_systems( OnExit(GameState::Loading), init_intro_text .run_if(resource_exists::()) .run_if(run_once()), ) .add_systems(OnEnter(GameState::Intro), manage_intro) .add_systems(OnExit(GameState::Intro), cleanup_intro) // All of these run during GameState::Intro .add_systems( Update, ( manage_intro.run_if(any_component_removed::()), // 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( any_component_added::.or_else( |keys: Res>| -> bool { keys.just_pressed(KeyCode::Return) }, ), ), // Play intro manages playing the intro of each individual paragraph // Runs every time the TextScroll component (managed by manage_scroll_text_animation) is updated scroll_text.run_if(any_with_component::()), ) .run_if(in_state(GameState::Intro)), ); } } #[derive(Debug, Resource)] struct IntroPlayed; #[derive(Debug, Component)] struct IntroUi; // Draw the intro text (invisible) on startup // Requires the Tweakfile to be loaded fn init_intro_text( tweaks_file: Res, tweaks: Res>, ui_font: Res, mut commands: Commands, ) { let tweak = tweaks.get(tweaks_file.handle.clone()).expect("Load tweaks"); let texts = tweak.get::>("intro_text").expect("Intro text"); let background_hex = tweak.get::("intro_rgba_background").unwrap(); let text_hidden_hex = tweak.get::("intro_rgba_hidden").unwrap(); commands .spawn(( IntroUi, NodeBundle { style: Style { width: Val::Percent(100.0), height: Val::Percent(100.0), justify_content: JustifyContent::Center, align_items: AlignItems::Center, position_type: PositionType::Absolute, padding: UiRect::all(Val::Px(50.0)), ..default() }, background_color: Color::NONE.into(), visibility: Visibility::Hidden, ..default() }, )) .with_children(|parent| { texts.iter().for_each(|text| { parent .spawn(( IntroUi, NodeBundle { style: Style { position_type: PositionType::Absolute, padding: UiRect::all(Val::Px(25.0)), ..default() }, visibility: Visibility::Hidden, background_color: Color::hex(&background_hex).unwrap().into(), ..default() }, )) .with_children(|parent| { parent.spawn(( IntroUi, TextBundle { 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(), }, }) .collect(), alignment: TextAlignment::Center, ..default() }, ..default() }, )); }); }); }); } fn manage_intro( // Hack, this way of "finding" the root Node containing our paragraphs is precarious query: Query, With, Without)>, mut commands: Commands, ) { info!("Managing intro"); query.iter().for_each(|e| { commands .entity(e) .insert(ui::TextScrollAnimation) .insert(Visibility::Visible); }); } fn cleanup_intro( query: Query>, mut texts: Query<&mut Text, With>, mut curr: ResMut, tweaks_file: Res, tweaks: Res>, mut commands: Commands, ) { info!("Cleaning up intro"); query.iter().for_each(|e| { commands .entity(e) .remove::() .remove::() .insert(Visibility::Hidden); }); { // Reset text colors let tweak = tweaks.get(tweaks_file.handle.clone()).expect("Load tweaks"); let text_hidden_hex = tweak.get::("intro_rgba_hidden").unwrap(); let text_hidden_color = Color::hex(text_hidden_hex).unwrap(); texts.iter_mut().for_each(|mut text| { text.sections.iter_mut().for_each(|s| { s.style.color = text_hidden_color; }); }); } curr.0 = None; } #[derive(Debug, Resource, Default)] struct CurrentIntroParagraph(Option); fn manage_scroll_text_animation( roots: Query< Entity, Or<( With, Added, )>, >, texts: Query, With)>, animated_texts: Query<&ui::TextScroll>, parents: Query<&Parent>, children: Query<&Children>, time: Res