//! This example illustrates how to use [`States`] for high-level app control flow. //! States are a powerful but intuitive tool for controlling which logic runs when. //! You can have multiple independent states, and the [`OnEnter`] and [`OnExit`] schedules //! can be used to great effect to ensure that you handle setup and teardown appropriately. //! //! In this case, we're transitioning from a `Menu` state to an `InGame` state. use bevy::{dev_tools::states::*, prelude::*}; fn main() { App::new() .add_plugins(DefaultPlugins) .init_state::<AppState>() // Alternatively we could use .insert_state(AppState::Menu) .add_systems(Startup, setup) // This system runs when we enter `AppState::Menu`, during the `StateTransition` schedule. // All systems from the exit schedule of the state we're leaving are run first, // and then all systems from the enter schedule of the state we're entering are run second. .add_systems(OnEnter(AppState::Menu), setup_menu) // By contrast, update systems are stored in the `Update` schedule. They simply // check the value of the `State<T>` resource to see if they should run each frame. .add_systems(Update, menu.run_if(in_state(AppState::Menu))) .add_systems(OnExit(AppState::Menu), cleanup_menu) .add_systems(OnEnter(AppState::InGame), setup_game) .add_systems( Update, (movement, change_color).run_if(in_state(AppState::InGame)), ) .add_systems(Update, log_transitions::<AppState>) .run(); } #[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, States)] enum AppState { #[default] Menu, InGame, } #[derive(Resource)] struct MenuData { button_entity: Entity, } const NORMAL_BUTTON: Color = Color::srgb(0.15, 0.15, 0.15); const HOVERED_BUTTON: Color = Color::srgb(0.25, 0.25, 0.25); const PRESSED_BUTTON: Color = Color::srgb(0.35, 0.75, 0.35); fn setup(mut commands: Commands) { commands.spawn(Camera2d); } fn setup_menu(mut commands: Commands) { let button_entity = commands .spawn(Node { // center button width: Val::Percent(100.), height: Val::Percent(100.), justify_content: JustifyContent::Center, align_items: AlignItems::Center, ..default() }) .with_children(|parent| { parent .spawn(( Button, Node { width: Val::Px(150.), height: Val::Px(65.), // horizontally center child text justify_content: JustifyContent::Center, // vertically center child text align_items: AlignItems::Center, ..default() }, BackgroundColor(NORMAL_BUTTON), )) .with_children(|parent| { parent.spawn(( Text::new("Play"), TextFont { font_size: 33.0, ..default() }, TextColor(Color::srgb(0.9, 0.9, 0.9)), )); }); }) .id(); commands.insert_resource(MenuData { button_entity }); } fn menu( mut next_state: ResMut<NextState<AppState>>, mut interaction_query: Query< (&Interaction, &mut BackgroundColor), (Changed<Interaction>, With<Button>), >, ) { for (interaction, mut color) in &mut interaction_query { match *interaction { Interaction::Pressed => { *color = PRESSED_BUTTON.into(); next_state.set(AppState::InGame); } Interaction::Hovered => { *color = HOVERED_BUTTON.into(); } Interaction::None => { *color = NORMAL_BUTTON.into(); } } } } fn cleanup_menu(mut commands: Commands, menu_data: Res<MenuData>) { commands.entity(menu_data.button_entity).despawn_recursive(); } fn setup_game(mut commands: Commands, asset_server: Res<AssetServer>) { commands.spawn(Sprite::from_image(asset_server.load("branding/icon.png"))); } const SPEED: f32 = 100.0; fn movement( time: Res<Time>, input: Res<ButtonInput<KeyCode>>, mut query: Query<&mut Transform, With<Sprite>>, ) { for mut transform in &mut query { let mut direction = Vec3::ZERO; if input.pressed(KeyCode::ArrowLeft) { direction.x -= 1.0; } if input.pressed(KeyCode::ArrowRight) { direction.x += 1.0; } if input.pressed(KeyCode::ArrowUp) { direction.y += 1.0; } if input.pressed(KeyCode::ArrowDown) { direction.y -= 1.0; } if direction != Vec3::ZERO { transform.translation += direction.normalize() * SPEED * time.delta_secs(); } } } fn change_color(time: Res<Time>, mut query: Query<&mut Sprite>) { for mut sprite in &mut query { let new_color = LinearRgba { blue: ops::sin(time.elapsed_secs() * 0.5) + 2.0, ..LinearRgba::from(sprite.color) }; sprite.color = new_color.into(); } }