diff --git a/examples/images.rs b/examples/images.rs index c6252de..7f49914 100644 --- a/examples/images.rs +++ b/examples/images.rs @@ -26,11 +26,10 @@ fn init_image( })); let mesh = Mesh2d(meshes.add(Rectangle::from_size(Vec2::splat(10.0)))); - // opaque // Each sprite should be square with the transparent parts being completely black // The blue sprite should be on top with the white and green one behind it - commands.spawn((Name::new("2D Example"),mesh, texture)); + commands.spawn((Name::new("2D Example"), mesh, texture)); } // 3d @@ -50,6 +49,4 @@ fn init_image( commands.spawn((name, mesh, material, t)); } - } - diff --git a/src/bin/flappy/main.rs b/src/bin/flappy/main.rs index 378f1ef..3d174c1 100644 --- a/src/bin/flappy/main.rs +++ b/src/bin/flappy/main.rs @@ -1,44 +1,98 @@ +use bevy::{input::common_conditions::input_just_released, render::view::VisibleEntities}; use games::*; fn main() { App::new() - .add_plugins(BaseGamePlugin { name: "flappy bird (with rewind)".into() }) - .add_systems(Startup, init_bird) - .add_systems(Update, flap) + .add_plugins(BaseGamePlugin { + name: "flappy bird (with rewind)".into(), + }) + .init_state::() + .add_systems(Startup, (init_bird, init_ui)) + .add_systems(OnEnter(PlayerState::Dead), kill_bird) + .add_systems(OnEnter(PlayerState::Alive), alive_bird) + .add_systems( + Update, + ( + // Systems to run when player state changes + ( + // Print out when this state changes for debugging purposes + debug_state_changes::, + // Toggle (UI) elements when the player dies/alives + toggle_state_visibility::, + ).run_if(state_changed::), + // Detect if the bird is "dead" by checking if it is visible + // from the point of view of the camera + detect_dead.run_if(in_state(PlayerState::Alive)), + // Toggle rewinding state when "R" is pressed/released + toggle_rewind.run_if( + input_just_pressed(KeyCode::KeyR).or(input_just_released(KeyCode::KeyR)), + ), + // Systems to run in the "play" state + ( + // Only flap when we press the space key + flap.run_if(input_just_pressed(KeyCode::Space)), + ) + .run_if(in_state(PlayerState::Alive)), + // Rewinding systems + rewind.run_if(in_state(PlayerState::Rewind)), + ), + ) .run(); } #[derive(Component)] struct Bird; +#[derive(States, Clone, Eq, PartialEq, Debug, Hash, Default, Component)] +enum PlayerState { + #[default] + Alive, + Rewind, + Dead, +} + fn init_bird( mut commands: Commands, server: Res, mut meshes: ResMut>, mut materials: ResMut>, ) { - let material = MeshMaterial3d(materials.add(StandardMaterial { - base_color_texture: Some(server.load("flappy/bevy.png")), - base_color: WHITE.with_alpha(0.9).into(), - alpha_mode: AlphaMode::Blend, - ..default() - })); + let material = MeshMaterial3d(materials.add(StandardMaterial { + base_color_texture: Some(server.load("flappy/bevy.png")), + base_color: WHITE.with_alpha(0.9).into(), + alpha_mode: AlphaMode::Blend, + ..default() + })); - let mesh = Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(1.0)))); + let mesh = Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(1.0)))); - let name = Name::new("bird"); + let name = Name::new("bird"); - let t = Transform::from_xyz(0.0, 0.0, -10.0).with_rotation(Quat::from_rotation_x(PI / 2.0)); + let t = Transform::from_xyz(0.0, 0.0, -10.0).with_rotation(Quat::from_rotation_x(PI / 2.0)); - let mass = ( - RigidBody::Dynamic, - Collider::capsule(1.0, 1.0), - Mass(1.0), - ); + let mass = (RigidBody::Dynamic, Collider::capsule(1.0, 1.0), Mass(1.0)); - let force = ExternalForce::default().with_persistence(false); + let force = ExternalForce::default().with_persistence(false); - commands.spawn((name, mesh, material, mass, t, Bird, force)); + commands.spawn((name, mesh, material, mass, t, Bird, force)); +} + +fn init_ui( + mut commands: Commands, +) { + commands.spawn(( + Node { + align_self: AlignSelf::Center, + justify_self: JustifySelf::Center, + flex_direction: FlexDirection::Column, + ..default() + }, + PlayerState::Dead, + children![ + Text::new("You Died"), + Text::new("Press R to Rewind"), + ], + )); } // TODO: Create floor (and ceiling?) @@ -46,12 +100,98 @@ fn init_bird( // TODO: Obstacles fn flap( - keyboard: Res>, + #[cfg(debug_assertions)] state: Res>, + #[cfg(debug_assertions)] keycode: Res>, mut bird: Query<(&Transform, &mut ExternalForce), With>, ) { - if keyboard.just_pressed(KeyCode::Space) { - bird.iter_mut().for_each(|(t, mut f)| { - f.apply_force(t.rotation * Vec3::NEG_Z * 1000.0); - }); + debug_assert!( + matches!(state.get(), PlayerState::Alive), + "Only flap when playing" + ); + debug_assert!( + keycode.just_pressed(KeyCode::Space), + "Only flap when space is just pressed" + ); + + bird.iter_mut().for_each(|(t, mut f)| { + f.apply_force(t.rotation * Vec3::NEG_Z * 1000.0); + }); +} + +fn toggle_rewind(keycode: Res>, mut next: ResMut>) { + debug_assert!( + keycode.just_pressed(KeyCode::KeyR) || keycode.just_released(KeyCode::KeyR), + "Only toggle rewind when R is pressed" + ); + + if keycode.pressed(KeyCode::KeyR) { + info!("Toggling rewind ON"); + next.set(PlayerState::Rewind) + } else { + info!("Toggling rewind OFF"); + next.set(PlayerState::Alive) } } + +fn rewind( + #[cfg(debug_assertions)] state: Res>, + birds: Query<&Transform, With>, +) { + debug_assert!( + matches!(state.get(), PlayerState::Rewind), + "Only rewind in the rewinding state" + ); + + birds.iter().for_each(|bird| { + info!("Position: {:?}", *bird); + }); +} + +// PERF: Runs more than it needs, should only execute when bird enters/exit frame +fn detect_dead( + #[cfg(debug_assertions)] state: Res>, + bird: Single>, + visible: Query<&VisibleEntities, With>, + mut next: ResMut>, +) { + debug_assert!( + matches!(state.get(), PlayerState::Alive), + "Only check if dead while alive" + ); + visible.iter().for_each(|ve| { + if ve.entities.iter().len() > 0 { + let bird_visible = ve + .entities + .iter() + .any(|(_type_id, list)| list.contains(&(*bird))); + + if bird_visible { + info!("Bird is visible, making alive"); + next.set(PlayerState::Alive); + } else { + info!("Bird is not visible, making dead"); + next.set(PlayerState::Dead); + } + } + }); +} + +fn alive_bird( + #[cfg(debug_assertions)] state: Res>, + bird: Single>, + mut commands: Commands, +) { + debug_assert!(!matches!(state.get(), PlayerState::Dead)); + debug!("Aliving bird"); + commands.entity(*bird).insert(RigidBody::Dynamic); +} + +fn kill_bird( + #[cfg(debug_assertions)] state: Res>, + bird: Single>, + mut commands: Commands, +) { + debug_assert!(matches!(state.get(), PlayerState::Dead)); + debug!("Killing bird"); + commands.entity(*bird).remove::(); +} diff --git a/src/bin/trees/debug.rs b/src/bin/trees/debug.rs index 88eccfb..ea965d4 100644 --- a/src/bin/trees/debug.rs +++ b/src/bin/trees/debug.rs @@ -4,26 +4,24 @@ pub(crate) struct TreesDebugPlugin; impl Plugin for TreesDebugPlugin { fn build(&self, app: &mut App) { - app.add_systems( - Startup, - init_debug_ui, - ).add_systems( - Update, - ( + app.add_systems(Startup, init_debug_ui) + .add_systems( + Update, ( - spawn_debug_buttons.run_if(on_event::>), + (spawn_debug_buttons.run_if(on_event::>),), + ( + monologue_asset_tooltip + .run_if(on_event::>.or(on_event::>)), + hide_menu.run_if(any_component_changed::), + clear_monologue.run_if(any_component_changed::), + control_menu.run_if(on_event::>.or(on_event::>)), + delete_tree.run_if(on_event::>), + drag_tree.run_if(on_event::>), + ) + .run_if(in_state(DebuggingState::On)), ), - ( - monologue_asset_tooltip - .run_if(on_event::>.or(on_event::>)), - hide_menu.run_if(any_component_changed::), - clear_monologue.run_if(any_component_changed::), - control_menu.run_if(on_event::>.or(on_event::>)), - delete_tree.run_if(on_event::>), - drag_tree.run_if(on_event::>), - ).run_if(in_state(DebuggingState::On)), ) - ).add_observer(add_dialog_option); + .add_observer(add_dialog_option); } } @@ -36,7 +34,6 @@ struct MonologuesList; #[derive(Component)] struct MonologuePreview; - fn drag_tree( mut events: EventReader>, state: Res>, @@ -47,8 +44,7 @@ fn drag_tree( debug_assert_eq!(*state.get(), DebuggingState::On); events.read().for_each(|event| { - if let Ok(mut t) = query.get_mut(event.target) - { + if let Ok(mut t) = query.get_mut(event.target) { let world_position = window .cursor_position() .and_then(|cursor| camera.0.viewport_to_world(camera.1, cursor).ok()) @@ -156,7 +152,6 @@ fn init_debug_ui(mut commands: Commands) { }); } - // When you pointer goes off of the '+' or any of it's children make the entire menu invisible fn control_menu( mut over_events: EventReader>, @@ -227,7 +222,6 @@ fn add_dialog_option(trigger: Trigger, mut commands: Comman .observe(hover_dialog_option_out); } - fn assign_monologue_event( trigger: Trigger>, mut events: EventWriter, @@ -237,13 +231,11 @@ fn assign_monologue_event( events.write(AssignMonologue(handle.clone())); } - /// Observer for the "Plant a new tree" button in the debug UI fn spawn_tree(_trigger: Trigger>, mut events: EventWriter) { events.write(PlantTree(None)); } - fn clear_monologue( mut nodes: Query< (Entity, &NavState), @@ -258,7 +250,6 @@ fn clear_monologue( }); } - // When you pointer over the '+' make the entire menu visible fn hide_menu(mut nodes: Query<(&mut Visibility, &NavState), Changed>) { nodes.iter_mut().for_each(|(mut v, n)| { @@ -269,7 +260,11 @@ fn hide_menu(mut nodes: Query<(&mut Visibility, &NavState), Changed>) }); } -fn delete_tree(mut events: EventReader>, mut commands: Commands, query: Query>) { +fn delete_tree( + mut events: EventReader>, + mut commands: Commands, + query: Query>, +) { events.read().for_each(|event| { if matches!(event.event.button, PointerButton::Middle) && query.contains(event.target) { debug!("Middle Click -> Despawning {}", event.target); @@ -412,4 +407,3 @@ fn spawn_debug_buttons( } }); } - diff --git a/src/bin/trees/main.rs b/src/bin/trees/main.rs index 10ab2bc..92c0798 100644 --- a/src/bin/trees/main.rs +++ b/src/bin/trees/main.rs @@ -3,13 +3,13 @@ #![allow(clippy::too_many_arguments)] #![feature(trim_prefix_suffix)] -mod mono; mod debug; +mod mono; use bevy::{picking::hover::HoverMap, platform::hash::RandomState}; +use debug::*; use games::*; use mono::*; -use debug::*; use std::hash::BuildHasher; fn main() { diff --git a/src/bin/trees/mono.rs b/src/bin/trees/mono.rs index 46d776f..90e2dca 100644 --- a/src/bin/trees/mono.rs +++ b/src/bin/trees/mono.rs @@ -16,7 +16,6 @@ impl Monologue { } } - #[derive(Debug, Error)] pub struct MonologueParseError; @@ -153,16 +152,12 @@ mod tests { let expected = Monologue { batches: vec![ MonologueLineBatch { - lines: vec![ - "hello".into() - ], + lines: vec!["hello".into()], }, MonologueLineBatch { - lines: vec![ - "world".into() - ] - } - ] + lines: vec!["world".into()], + }, + ], }; assert_eq!(parsed, expected); @@ -188,16 +183,12 @@ mod tests { let expected = Monologue { batches: vec![ MonologueLineBatch { - lines: vec![ - "hello".into() - ], + lines: vec!["hello".into()], }, MonologueLineBatch { - lines: vec![ - "world".into() - ] - } - ] + lines: vec!["world".into()], + }, + ], }; assert_eq!(parsed, expected); @@ -229,28 +220,21 @@ mod tests { let expected = Monologue { batches: vec![ MonologueLineBatch { - lines: vec![ - "a".into(), "b".into(), "c".into() - ] + lines: vec!["a".into(), "b".into(), "c".into()], }, MonologueLineBatch { - lines: vec![ - "d".into(), "e".into() - ] + lines: vec!["d".into(), "e".into()], }, MonologueLineBatch { - lines: vec![ - "f".into() - ] + lines: vec!["f".into()], }, - ] + ], }; assert_eq!(parsed, expected); } } - #[derive(Default)] struct MonologueLoader; diff --git a/src/debug.rs b/src/debug.rs index ef50e53..54f3497 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -390,7 +390,12 @@ fn toggle_physics_debug_render( ) { let (_, config) = config_store.config_mut::(); *config = match state.get() { + // TODO: Not all, don't want to hide mesh DebuggingState::On => PhysicsGizmos::all(), DebuggingState::Off => PhysicsGizmos::none(), }; } + +pub fn debug_state_changes(s: Res>) { + info!("State changed: {:?}", s); +} diff --git a/src/lib.rs b/src/lib.rs index 69f167c..9830614 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,12 +9,12 @@ mod ui; mod version; // Rust stdlib -pub use std::fmt::Display; pub use std::f32::consts::PI; +pub use std::fmt::Display; // Community libraries +pub use avian3d::prelude::*; pub use bevy::{ - sprite::AlphaMode2d, asset::{AssetLoader, LoadContext, LoadState, LoadedFolder, io::Reader}, color::palettes::css::*, gizmos::{aabb::AabbGizmoPlugin, light::LightGizmoPlugin}, @@ -29,9 +29,9 @@ pub use bevy::{ platform::collections::HashMap, prelude::*, reflect::TypePath, + sprite::AlphaMode2d, window::WindowResized, }; -pub use avian3d::prelude::*; pub use serde::Deserialize; pub use thiserror::Error;