"do nothing" is done. we have rewind alive/dead/rewind states implemented

main
Elijah Voigt 3 months ago
parent 9f8b4053f6
commit ce4b19dd7d

@ -26,7 +26,6 @@ fn init_image(
})); }));
let mesh = Mesh2d(meshes.add(Rectangle::from_size(Vec2::splat(10.0)))); let mesh = Mesh2d(meshes.add(Rectangle::from_size(Vec2::splat(10.0))));
// opaque // opaque
// Each sprite should be square with the transparent parts being completely black // 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 // The blue sprite should be on top with the white and green one behind it
@ -50,6 +49,4 @@ fn init_image(
commands.spawn((name, mesh, material, t)); commands.spawn((name, mesh, material, t));
} }
} }

@ -1,16 +1,56 @@
use bevy::{input::common_conditions::input_just_released, render::view::VisibleEntities};
use games::*; use games::*;
fn main() { fn main() {
App::new() App::new()
.add_plugins(BaseGamePlugin { name: "flappy bird (with rewind)".into() }) .add_plugins(BaseGamePlugin {
.add_systems(Startup, init_bird) name: "flappy bird (with rewind)".into(),
.add_systems(Update, flap) })
.init_state::<PlayerState>()
.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::<PlayerState>,
// Toggle (UI) elements when the player dies/alives
toggle_state_visibility::<PlayerState>,
).run_if(state_changed::<PlayerState>),
// 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(); .run();
} }
#[derive(Component)] #[derive(Component)]
struct Bird; struct Bird;
#[derive(States, Clone, Eq, PartialEq, Debug, Hash, Default, Component)]
enum PlayerState {
#[default]
Alive,
Rewind,
Dead,
}
fn init_bird( fn init_bird(
mut commands: Commands, mut commands: Commands,
server: Res<AssetServer>, server: Res<AssetServer>,
@ -30,28 +70,128 @@ fn init_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 = ( let mass = (RigidBody::Dynamic, Collider::capsule(1.0, 1.0), Mass(1.0));
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?) // TODO: Create floor (and ceiling?)
// Q: Move bird + camera or move world around bird & camera? // Q: Move bird + camera or move world around bird & camera?
// TODO: Obstacles // TODO: Obstacles
fn flap( fn flap(
keyboard: Res<ButtonInput<KeyCode>>, #[cfg(debug_assertions)] state: Res<State<PlayerState>>,
#[cfg(debug_assertions)] keycode: Res<ButtonInput<KeyCode>>,
mut bird: Query<(&Transform, &mut ExternalForce), With<Bird>>, mut bird: Query<(&Transform, &mut ExternalForce), With<Bird>>,
) { ) {
if keyboard.just_pressed(KeyCode::Space) { 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)| { bird.iter_mut().for_each(|(t, mut f)| {
f.apply_force(t.rotation * Vec3::NEG_Z * 1000.0); f.apply_force(t.rotation * Vec3::NEG_Z * 1000.0);
}); });
} }
fn toggle_rewind(keycode: Res<ButtonInput<KeyCode>>, mut next: ResMut<NextState<PlayerState>>) {
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<State<PlayerState>>,
birds: Query<&Transform, With<Bird>>,
) {
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<State<PlayerState>>,
bird: Single<Entity, With<Bird>>,
visible: Query<&VisibleEntities, With<Camera>>,
mut next: ResMut<NextState<PlayerState>>,
) {
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<State<PlayerState>>,
bird: Single<Entity, With<Bird>>,
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<State<PlayerState>>,
bird: Single<Entity, With<Bird>>,
mut commands: Commands,
) {
debug_assert!(matches!(state.get(), PlayerState::Dead));
debug!("Killing bird");
commands.entity(*bird).remove::<RigidBody>();
} }

@ -4,15 +4,11 @@ pub(crate) struct TreesDebugPlugin;
impl Plugin for TreesDebugPlugin { impl Plugin for TreesDebugPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems( app.add_systems(Startup, init_debug_ui)
Startup, .add_systems(
init_debug_ui,
).add_systems(
Update, Update,
( (
( (spawn_debug_buttons.run_if(on_event::<AssetEvent<Monologue>>),),
spawn_debug_buttons.run_if(on_event::<AssetEvent<Monologue>>),
),
( (
monologue_asset_tooltip monologue_asset_tooltip
.run_if(on_event::<Pointer<Over>>.or(on_event::<Pointer<Out>>)), .run_if(on_event::<Pointer<Over>>.or(on_event::<Pointer<Out>>)),
@ -21,9 +17,11 @@ impl Plugin for TreesDebugPlugin {
control_menu.run_if(on_event::<Pointer<Over>>.or(on_event::<Pointer<Out>>)), control_menu.run_if(on_event::<Pointer<Over>>.or(on_event::<Pointer<Out>>)),
delete_tree.run_if(on_event::<Pointer<Click>>), delete_tree.run_if(on_event::<Pointer<Click>>),
drag_tree.run_if(on_event::<Pointer<Drag>>), drag_tree.run_if(on_event::<Pointer<Drag>>),
).run_if(in_state(DebuggingState::On)),
) )
).add_observer(add_dialog_option); .run_if(in_state(DebuggingState::On)),
),
)
.add_observer(add_dialog_option);
} }
} }
@ -36,7 +34,6 @@ struct MonologuesList;
#[derive(Component)] #[derive(Component)]
struct MonologuePreview; struct MonologuePreview;
fn drag_tree( fn drag_tree(
mut events: EventReader<Pointer<Drag>>, mut events: EventReader<Pointer<Drag>>,
state: Res<State<DebuggingState>>, state: Res<State<DebuggingState>>,
@ -47,8 +44,7 @@ fn drag_tree(
debug_assert_eq!(*state.get(), DebuggingState::On); debug_assert_eq!(*state.get(), DebuggingState::On);
events.read().for_each(|event| { 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 let world_position = window
.cursor_position() .cursor_position()
.and_then(|cursor| camera.0.viewport_to_world(camera.1, cursor).ok()) .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 // When you pointer goes off of the '+' or any of it's children make the entire menu invisible
fn control_menu( fn control_menu(
mut over_events: EventReader<Pointer<Over>>, mut over_events: EventReader<Pointer<Over>>,
@ -227,7 +222,6 @@ fn add_dialog_option(trigger: Trigger<OnAdd, DialogOption>, mut commands: Comman
.observe(hover_dialog_option_out); .observe(hover_dialog_option_out);
} }
fn assign_monologue_event( fn assign_monologue_event(
trigger: Trigger<Pointer<Click>>, trigger: Trigger<Pointer<Click>>,
mut events: EventWriter<AssignMonologue>, mut events: EventWriter<AssignMonologue>,
@ -237,13 +231,11 @@ fn assign_monologue_event(
events.write(AssignMonologue(handle.clone())); events.write(AssignMonologue(handle.clone()));
} }
/// Observer for the "Plant a new tree" button in the debug UI /// Observer for the "Plant a new tree" button in the debug UI
fn spawn_tree(_trigger: Trigger<Pointer<Click>>, mut events: EventWriter<PlantTree>) { fn spawn_tree(_trigger: Trigger<Pointer<Click>>, mut events: EventWriter<PlantTree>) {
events.write(PlantTree(None)); events.write(PlantTree(None));
} }
fn clear_monologue( fn clear_monologue(
mut nodes: Query< mut nodes: Query<
(Entity, &NavState), (Entity, &NavState),
@ -258,7 +250,6 @@ fn clear_monologue(
}); });
} }
// When you pointer over the '+' make the entire menu visible // When you pointer over the '+' make the entire menu visible
fn hide_menu(mut nodes: Query<(&mut Visibility, &NavState), Changed<NavState>>) { fn hide_menu(mut nodes: Query<(&mut Visibility, &NavState), Changed<NavState>>) {
nodes.iter_mut().for_each(|(mut v, n)| { nodes.iter_mut().for_each(|(mut v, n)| {
@ -269,7 +260,11 @@ fn hide_menu(mut nodes: Query<(&mut Visibility, &NavState), Changed<NavState>>)
}); });
} }
fn delete_tree(mut events: EventReader<Pointer<Click>>, mut commands: Commands, query: Query<Entity, With<Tree>>) { fn delete_tree(
mut events: EventReader<Pointer<Click>>,
mut commands: Commands,
query: Query<Entity, With<Tree>>,
) {
events.read().for_each(|event| { events.read().for_each(|event| {
if matches!(event.event.button, PointerButton::Middle) && query.contains(event.target) { if matches!(event.event.button, PointerButton::Middle) && query.contains(event.target) {
debug!("Middle Click -> Despawning {}", event.target); debug!("Middle Click -> Despawning {}", event.target);
@ -412,4 +407,3 @@ fn spawn_debug_buttons(
} }
}); });
} }

@ -3,13 +3,13 @@
#![allow(clippy::too_many_arguments)] #![allow(clippy::too_many_arguments)]
#![feature(trim_prefix_suffix)] #![feature(trim_prefix_suffix)]
mod mono;
mod debug; mod debug;
mod mono;
use bevy::{picking::hover::HoverMap, platform::hash::RandomState}; use bevy::{picking::hover::HoverMap, platform::hash::RandomState};
use debug::*;
use games::*; use games::*;
use mono::*; use mono::*;
use debug::*;
use std::hash::BuildHasher; use std::hash::BuildHasher;
fn main() { fn main() {

@ -16,7 +16,6 @@ impl Monologue {
} }
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub struct MonologueParseError; pub struct MonologueParseError;
@ -153,16 +152,12 @@ mod tests {
let expected = Monologue { let expected = Monologue {
batches: vec![ batches: vec![
MonologueLineBatch { MonologueLineBatch {
lines: vec![ lines: vec!["hello".into()],
"hello".into()
],
}, },
MonologueLineBatch { MonologueLineBatch {
lines: vec![ lines: vec!["world".into()],
"world".into() },
] ],
}
]
}; };
assert_eq!(parsed, expected); assert_eq!(parsed, expected);
@ -188,16 +183,12 @@ mod tests {
let expected = Monologue { let expected = Monologue {
batches: vec![ batches: vec![
MonologueLineBatch { MonologueLineBatch {
lines: vec![ lines: vec!["hello".into()],
"hello".into()
],
}, },
MonologueLineBatch { MonologueLineBatch {
lines: vec![ lines: vec!["world".into()],
"world".into() },
] ],
}
]
}; };
assert_eq!(parsed, expected); assert_eq!(parsed, expected);
@ -229,28 +220,21 @@ mod tests {
let expected = Monologue { let expected = Monologue {
batches: vec![ batches: vec![
MonologueLineBatch { MonologueLineBatch {
lines: vec![ lines: vec!["a".into(), "b".into(), "c".into()],
"a".into(), "b".into(), "c".into()
]
}, },
MonologueLineBatch { MonologueLineBatch {
lines: vec![ lines: vec!["d".into(), "e".into()],
"d".into(), "e".into()
]
}, },
MonologueLineBatch { MonologueLineBatch {
lines: vec![ lines: vec!["f".into()],
"f".into()
]
}, },
] ],
}; };
assert_eq!(parsed, expected); assert_eq!(parsed, expected);
} }
} }
#[derive(Default)] #[derive(Default)]
struct MonologueLoader; struct MonologueLoader;

@ -390,7 +390,12 @@ fn toggle_physics_debug_render(
) { ) {
let (_, config) = config_store.config_mut::<PhysicsGizmos>(); let (_, config) = config_store.config_mut::<PhysicsGizmos>();
*config = match state.get() { *config = match state.get() {
// TODO: Not all, don't want to hide mesh
DebuggingState::On => PhysicsGizmos::all(), DebuggingState::On => PhysicsGizmos::all(),
DebuggingState::Off => PhysicsGizmos::none(), DebuggingState::Off => PhysicsGizmos::none(),
}; };
} }
pub fn debug_state_changes<S: States + std::fmt::Debug>(s: Res<State<S>>) {
info!("State changed: {:?}", s);
}

@ -9,12 +9,12 @@ mod ui;
mod version; mod version;
// Rust stdlib // Rust stdlib
pub use std::fmt::Display;
pub use std::f32::consts::PI; pub use std::f32::consts::PI;
pub use std::fmt::Display;
// Community libraries // Community libraries
pub use avian3d::prelude::*;
pub use bevy::{ pub use bevy::{
sprite::AlphaMode2d,
asset::{AssetLoader, LoadContext, LoadState, LoadedFolder, io::Reader}, asset::{AssetLoader, LoadContext, LoadState, LoadedFolder, io::Reader},
color::palettes::css::*, color::palettes::css::*,
gizmos::{aabb::AabbGizmoPlugin, light::LightGizmoPlugin}, gizmos::{aabb::AabbGizmoPlugin, light::LightGizmoPlugin},
@ -29,9 +29,9 @@ pub use bevy::{
platform::collections::HashMap, platform::collections::HashMap,
prelude::*, prelude::*,
reflect::TypePath, reflect::TypePath,
sprite::AlphaMode2d,
window::WindowResized, window::WindowResized,
}; };
pub use avian3d::prelude::*;
pub use serde::Deserialize; pub use serde::Deserialize;
pub use thiserror::Error; pub use thiserror::Error;

Loading…
Cancel
Save