diff --git a/src/bin/flappy/main.rs b/src/bin/flappy/main.rs index 0d45e56..6a43d08 100644 --- a/src/bin/flappy/main.rs +++ b/src/bin/flappy/main.rs @@ -14,6 +14,9 @@ fn main() { Physics2dPlugin, )) .insert_resource(Gravity(Vec2::NEG_Y * 9.8 * 100.0)) + .init_resource::() + .init_resource::() + .init_resource::() .init_state::() .add_systems( Startup, @@ -64,6 +67,13 @@ fn main() { un_pause_game .run_if(input_just_pressed(KeyCode::Space)) .run_if(in_state(PlayerState::Pause)), + // Stats that get synced to the UI + ( + sync_resource_to_ui::.run_if(resource_changed::), + sync_resource_to_ui::.run_if(resource_changed::), + sync_resource_to_ui::.run_if(resource_changed::), + ), + scoring.run_if(on_event::) ), ) .run(); @@ -124,7 +134,7 @@ fn init_bird( let tape = Tape::default(); - commands.spawn((name, mesh, material, physics, t, Bird, tape)); + commands.spawn((name, mesh, material, physics, t, Bird, tape, CollisionEventsEnabled)); } #[derive(Component, Clone)] @@ -133,6 +143,9 @@ struct Ground; #[derive(Component, Clone)] struct Pipe; +#[derive(Component, Clone)] +struct Hitbox; + fn init_obstacles( mut commands: Commands, mut meshes: ResMut>, @@ -170,6 +183,26 @@ fn init_obstacles( Pipe, ) }; + let hitbox = { + let material = MeshMaterial2d(materials.add(ColorMaterial { + color: PINK.into(), + ..default() + })); + let mesh = Mesh2d(meshes.add(Rectangle::new(0.1, 10.0))); + + let physics = (RigidBody::Static, Collider::rectangle(1.0, 10.0)); + let name = Name::new("hitbox"); + + ( + material.clone(), + mesh.clone(), + name.clone(), + physics.clone(), + Sensor, + Hitbox, + CollisionEventsEnabled, + ) + }; // TODO: Instead of spawning infinite floor/pipes, we should instead spawn enough for 1-3 a few // screens and then "move" them around. @@ -183,6 +216,9 @@ fn init_obstacles( let below = Transform::from_xyz(300.0 * i as f32, -200.0, -1.0).with_scale(Vec3::splat(100.0)); commands.spawn((pipe.clone(), below)); + let hitbox_pos = Transform::from_xyz(300.0 * i as f32, 0.0, 10.0).with_scale(Vec3::splat(100.0)); + commands.spawn((hitbox_pos, hitbox.clone())); + let floor = Transform::from_xyz(300.0 * i as f32, -300.0, 1.0).with_scale(Vec3::splat(100.0)); commands.spawn((ground.clone(), floor)); }); @@ -197,7 +233,13 @@ fn init_ui(mut commands: Commands) { ..default() }, PlayerState::Stasis, - children![Text::new("You Died"), Text::new("Press R to Rewind"),], + children![ + (Text::new("You Died"), TextLayout::new_with_justify(JustifyText::Center)), + (SyncResource::::default(), Text::default(), TextLayout::new_with_justify(JustifyText::Center)), + (SyncResource::::default(), Text::default(), TextLayout::new_with_justify(JustifyText::Center)), + (SyncResource::::default(), Text::default(), TextLayout::new_with_justify(JustifyText::Center)), + (Text::new("Press R to Rewind"), TextLayout::new_with_justify(JustifyText::Center)), + ], )); fn start_game(_trigger: Trigger>, mut next: ResMut>) { @@ -259,6 +301,7 @@ fn flap( #[cfg(debug_assertions)] state: Res>, #[cfg(debug_assertions)] keycode: Res>, mut bird: Query<(&Transform, &mut ExternalImpulse), With>, + mut flaps: ResMut, ) { debug_assert!( matches!(state.get(), PlayerState::Alive), @@ -269,6 +312,10 @@ fn flap( "Only flap when space is just pressed" ); + // Increment flap stat + flaps.0 += 1; + + // Flap birds wings bird.iter_mut().for_each(|(_, mut f)| { f.apply_impulse(Vec2::Y * 5000.0 + Vec2::X * 1000.0); }); @@ -309,6 +356,7 @@ fn record( fn rewind( #[cfg(debug_assertions)] state: Res>, mut birds: Query<(&mut Transform, &mut AngularVelocity, &mut LinearVelocity, &mut Tape), With>, + mut frames: ResMut, ) { debug_assert!( matches!(state.get(), PlayerState::Rewind), @@ -316,11 +364,14 @@ fn rewind( ); birds.iter_mut().for_each(|(mut transform, mut angular_velocity, mut linear_velocity, mut tape)| { - if let Some(t) = tape.translations.pop() { - transform.translation = t; - } - if let Some(r) = tape.rotations.pop() { - transform.rotation = r; + match (tape.translations.pop(), tape.rotations.pop()) { + (Some(t), Some(r)) => { + transform.translation = t; + transform.rotation = r; + frames.0 += 1; + }, + (None, None) => (), + _ => panic!("Translations and rotations are out of sync!"), } // TODO: Only need to set {angular|linear}_velocity at end of Rewind if let Some(av) = tape.angular_velocities.pop() { @@ -373,3 +424,53 @@ fn camera_follow_bird( ) { camera.translation.x = bird.translation.x; } + +/// How many pipes the player has passed +#[derive(Resource, Default)] +struct Score(usize); + +impl Display for Score { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + writeln!(f, "Score: {}", self.0) + } +} + +/// Number of frames that were rewound +#[derive(Resource, Default)] +struct RewindFrames(usize); + +impl Display for RewindFrames { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + writeln!(f, "Frames Rewound: {}", self.0) + } +} + +// Track number of times player flapped their wings +#[derive(Resource, Default)] +struct Flaps(usize); + +impl Display for Flaps { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + writeln!(f, "Flaps: {}", self.0) + } +} + +fn scoring( + mut events: EventReader, + state: Res>, + bird: Query>, + hitboxes: Query>, + mut score: ResMut, +) { + events.read().filter(|CollisionEnded(a, b)| { + bird.contains(*a) && hitboxes.contains(*b) + }).for_each(|_| { + info!("Hit event while {:?}", state.get()); + match state.get() { + PlayerState::Alive => score.0 = score.0.saturating_add(1), + PlayerState::Rewind => score.0 = score.0.saturating_sub(1), + _ => panic!("How are you scoring at a time like this??"), + } + }) + +} diff --git a/src/physics2d.rs b/src/physics2d.rs index e801c83..cf35573 100644 --- a/src/physics2d.rs +++ b/src/physics2d.rs @@ -20,7 +20,7 @@ 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::On => PhysicsGizmos::default(), DebuggingState::Off => PhysicsGizmos::none(), }; }