diff --git a/src/bin/flappy/main.rs b/src/bin/flappy/main.rs index 36b662a..1f37e4b 100644 --- a/src/bin/flappy/main.rs +++ b/src/bin/flappy/main.rs @@ -1,3 +1,6 @@ +// Bevy basically forces "complex types" with Querys +#![allow(clippy::type_complexity)] + use bevy::input::common_conditions::input_just_released; use games::*; @@ -7,7 +10,7 @@ fn main() { name: "flappy bird (with rewind)".into(), }) .init_state::() - .add_systems(Startup, (init_bird, init_ground, init_ui, tweak_camera.after(setup_camera))) + .add_systems(Startup, (init_bird, init_obstacles, init_ui, tweak_camera.after(setup_camera))) .add_systems(OnEnter(PlayerState::Alive), alive_bird) .add_systems(OnExit(PlayerState::Alive), kill_bird) .add_systems( @@ -45,11 +48,13 @@ fn main() { } fn tweak_camera( - mut camera: Query<(&mut Camera, &mut AmbientLight), With>, + mut camera: Query<(&mut Camera, &mut AmbientLight, &mut Transform), With>, ) { - camera.iter_mut().for_each(|(mut c, mut al)| { + camera.iter_mut().for_each(|(mut c, mut al, mut t)| { c.clear_color = ClearColorConfig::Custom(WHITE.into()); al.brightness = 100.0; + // move the camera "back" so everything else is at 0 on the Z axis + t.translation.z = 10.0; }); } @@ -88,7 +93,7 @@ fn init_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, 0.0).with_rotation(Quat::from_rotation_x(PI / 2.0)); let physics = ( RigidBody::Dynamic, @@ -103,28 +108,59 @@ fn init_bird( commands.spawn((name, mesh, material, physics, t, Bird, tape)); } -#[derive(Component)] +#[derive(Component, Clone)] struct Ground; -fn init_ground( +#[derive(Component, Clone)] +struct Pipe; + +fn init_obstacles( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, ) { - let material = MeshMaterial3d(materials.add(StandardMaterial { - base_color: GREEN.into(), - ..default() - })); + let ground = { + let material = MeshMaterial3d(materials.add(StandardMaterial { + base_color: LIGHT_GREEN.into(), + ..default() + })); - let mesh = Mesh3d(meshes.add(Cuboid::new(10.0, 1.0, 1.0))); + let mesh = Mesh3d(meshes.add(Cuboid::new(10.0, 1.0, 1.0))); - let name = Name::new("ground"); + let name = Name::new("ground"); - let t = Transform::from_xyz(0.0, -4.0, -10.0); + let physics = (RigidBody::Static, Collider::cuboid(1.0, 1.0, 1.0)); - let physics = (RigidBody::Static, Collider::cuboid(1.0, 1.0, 1.0)); + (name, mesh, material, physics, Ground) + }; + let pipe = { + let material = MeshMaterial3d(materials.add(StandardMaterial { + base_color: GREEN.into(), + ..default() + })); + + let mesh = Mesh3d(meshes.add(Cuboid::new(1.0, 3.0, 1.0))); + let physics = (RigidBody::Static, Collider::cuboid(1.0, 3.0, 1.0)); + let name = Name::new("pipe"); + + (name.clone(), mesh.clone(), material.clone(), physics.clone(), Pipe) + }; + + // TODO: Instead of spawning infinite floor/pipes, we should instead spawn enough for 1-3 a few + // screens and then "move" them around. + // This is considerably more complexity so can be implemented later, but would keep memory + // overhead fairly static. + (1..50).for_each(|i| { + // TODO: Jitter up/down/close/far of pipes for challenge + let above = Transform::from_xyz(5.0 * i as f32, 4.0, 0.0); + let below = Transform::from_xyz(5.0 * i as f32, -4.0, 0.0); + let floor = Transform::from_xyz(1.0 * i as f32, -4.0, 0.0); + + commands.spawn((pipe.clone(), above)); + commands.spawn((pipe.clone(), below)); + commands.spawn((ground.clone(), floor)); + }); - commands.spawn((name, mesh, material, physics, t, Ground)); } fn init_ui( @@ -217,18 +253,21 @@ fn rewind( }); } -// PERF: Runs more than it needs, should only execute when bird enters/exit frame +// PERF: May run more than necessary, should be event-driven on aabb intersection fn detect_dead( #[cfg(debug_assertions)] state: Res>, bird: Single<&ColliderAabb, With>, - ground: Single<&ColliderAabb, With>, + obstacles: Query<&ColliderAabb, Or<(With, With)>>, mut next: ResMut>, ) { debug_assert!( matches!(state.get(), PlayerState::Alive), "Only check if dead while alive" ); - if bird.intersects(*ground) { + + if obstacles.iter().any(|obstacle| { + bird.intersects(obstacle) + }) { next.set(PlayerState::Dead); } }