Compare commits

..

2 Commits

@ -14,6 +14,10 @@ fn main() {
Physics2dPlugin, Physics2dPlugin,
)) ))
.insert_resource(Gravity(Vec2::NEG_Y * 9.8 * 100.0)) .insert_resource(Gravity(Vec2::NEG_Y * 9.8 * 100.0))
.init_resource::<Score>()
.init_resource::<RewindFrames>()
.init_resource::<Flaps>()
.init_resource::<Deaths>()
.init_state::<PlayerState>() .init_state::<PlayerState>()
.add_systems( .add_systems(
Startup, Startup,
@ -49,7 +53,7 @@ fn main() {
// Systems to run in the "play" state // Systems to run in the "play" state
( (
// Only flap when we press the space key // Only flap when we press the space key
flap.run_if(input_just_pressed(KeyCode::Space)), flap_kb.run_if(input_just_pressed(KeyCode::Space)),
// Rewinding systems // Rewinding systems
record.run_if(any_component_changed::<Transform>), record.run_if(any_component_changed::<Transform>),
) )
@ -64,8 +68,17 @@ fn main() {
un_pause_game un_pause_game
.run_if(input_just_pressed(KeyCode::Space)) .run_if(input_just_pressed(KeyCode::Space))
.run_if(in_state(PlayerState::Pause)), .run_if(in_state(PlayerState::Pause)),
// Stats that get synced to the UI
(
sync_resource_to_ui::<Score>.run_if(resource_changed::<Score>),
sync_resource_to_ui::<Flaps>.run_if(resource_changed::<Flaps>),
sync_resource_to_ui::<Deaths>.run_if(resource_changed::<Deaths>),
sync_resource_to_ui::<RewindFrames>.run_if(resource_changed::<RewindFrames>),
),
scoring.run_if(on_event::<CollisionEnded>)
), ),
) )
.add_observer(flap)
.run(); .run();
} }
@ -124,7 +137,7 @@ fn init_bird(
let tape = Tape::default(); 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)] #[derive(Component, Clone)]
@ -133,6 +146,9 @@ struct Ground;
#[derive(Component, Clone)] #[derive(Component, Clone)]
struct Pipe; struct Pipe;
#[derive(Component, Clone)]
struct Hitbox;
fn init_obstacles( fn init_obstacles(
mut commands: Commands, mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>, mut meshes: ResMut<Assets<Mesh>>,
@ -170,6 +186,26 @@ fn init_obstacles(
Pipe, 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 // TODO: Instead of spawning infinite floor/pipes, we should instead spawn enough for 1-3 a few
// screens and then "move" them around. // screens and then "move" them around.
@ -183,6 +219,9 @@ fn init_obstacles(
let below = Transform::from_xyz(300.0 * i as f32, -200.0, -1.0).with_scale(Vec3::splat(100.0)); 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)); 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)); 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)); commands.spawn((ground.clone(), floor));
}); });
@ -197,7 +236,14 @@ fn init_ui(mut commands: Commands) {
..default() ..default()
}, },
PlayerState::Stasis, 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::<Score>::default(), Text::default(), TextLayout::new_with_justify(JustifyText::Center)),
(SyncResource::<Flaps>::default(), Text::default(), TextLayout::new_with_justify(JustifyText::Center)),
(SyncResource::<RewindFrames>::default(), Text::default(), TextLayout::new_with_justify(JustifyText::Center)),
(SyncResource::<Deaths>::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<Pointer<Click>>, mut next: ResMut<NextState<PlayerState>>) { fn start_game(_trigger: Trigger<Pointer<Click>>, mut next: ResMut<NextState<PlayerState>>) {
@ -219,20 +265,18 @@ fn init_ui(mut commands: Commands) {
)) ))
.observe(start_game); .observe(start_game);
fn start_rewind(trigger: Trigger<Pointer<Pressed>>, mut next: ResMut<NextState<PlayerState>>) { commands.spawn(
next.set(PlayerState::Rewind);
}
fn end_rewind(trigger: Trigger<Pointer<Released>>, mut next: ResMut<NextState<PlayerState>>) {
next.set(PlayerState::Alive);
}
commands
.spawn((
Node { Node {
align_self: AlignSelf::End, align_self: AlignSelf::End,
justify_self: JustifySelf::Center, justify_self: JustifySelf::Center,
flex_direction: FlexDirection::Column, flex_direction: FlexDirection::Row,
..default()
},
).with_children(|parent| {
parent
.spawn((
Node {
..default() ..default()
}, },
Button, Button,
@ -240,6 +284,31 @@ fn init_ui(mut commands: Commands) {
)) ))
.observe(start_rewind) .observe(start_rewind)
.observe(end_rewind); .observe(end_rewind);
parent
.spawn((
Node {
..default()
},
Button,
children![Text::new("Flap!"),],
))
.observe(flap_button);
});
}
fn start_rewind(_trigger: Trigger<Pointer<Pressed>>, mut next: ResMut<NextState<PlayerState>>) {
next.set(PlayerState::Rewind);
}
fn end_rewind(_trigger: Trigger<Pointer<Released>>, mut next: ResMut<NextState<PlayerState>>) {
next.set(PlayerState::Alive);
}
fn flap_button(trigger: Trigger<Pointer<Pressed>>, mut commands: Commands, bird: Single<Entity, With<Bird>>) {
let e = *bird;
info!("Flapping {:?}", e);
commands.trigger_targets(Flap, e);
} }
/// Pause the game when the player presses "Escape" /// Pause the game when the player presses "Escape"
@ -254,11 +323,29 @@ fn un_pause_game(mut next: ResMut<NextState<PlayerState>>) {
// 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
#[derive(Component, Clone, Event)]
struct Flap;
// Observer for flapping
fn flap(trigger: Trigger<Flap>,
mut bird: Query<&mut ExternalImpulse, With<Bird>>,
mut flaps: ResMut<Flaps>,
) {
info!("real flap for {:?}", trigger.target());
// Increment flap stat
flaps.0 += 1;
// Flap birds wings
if let Ok(mut f) = bird.get_mut(trigger.target()) {
f.apply_impulse(Vec2::Y * 5000.0 + Vec2::X * 1000.0);
}
}
fn flap( fn flap_kb(
#[cfg(debug_assertions)] state: Res<State<PlayerState>>, #[cfg(debug_assertions)] state: Res<State<PlayerState>>,
#[cfg(debug_assertions)] keycode: Res<ButtonInput<KeyCode>>, #[cfg(debug_assertions)] keycode: Res<ButtonInput<KeyCode>>,
mut bird: Query<(&Transform, &mut ExternalImpulse), With<Bird>>, birds: Query<Entity, With<Bird>>,
mut commands: Commands,
) { ) {
debug_assert!( debug_assert!(
matches!(state.get(), PlayerState::Alive), matches!(state.get(), PlayerState::Alive),
@ -269,8 +356,9 @@ fn flap(
"Only flap when space is just pressed" "Only flap when space is just pressed"
); );
bird.iter_mut().for_each(|(_, mut f)| { birds.iter().for_each(|e| {
f.apply_impulse(Vec2::Y * 5000.0 + Vec2::X * 1000.0); info!("Flapping {:?}", e);
commands.trigger_targets(Flap, e);
}); });
} }
@ -309,6 +397,7 @@ fn record(
fn rewind( fn rewind(
#[cfg(debug_assertions)] state: Res<State<PlayerState>>, #[cfg(debug_assertions)] state: Res<State<PlayerState>>,
mut birds: Query<(&mut Transform, &mut AngularVelocity, &mut LinearVelocity, &mut Tape), With<Bird>>, mut birds: Query<(&mut Transform, &mut AngularVelocity, &mut LinearVelocity, &mut Tape), With<Bird>>,
mut frames: ResMut<RewindFrames>,
) { ) {
debug_assert!( debug_assert!(
matches!(state.get(), PlayerState::Rewind), matches!(state.get(), PlayerState::Rewind),
@ -316,11 +405,14 @@ fn rewind(
); );
birds.iter_mut().for_each(|(mut transform, mut angular_velocity, mut linear_velocity, mut tape)| { birds.iter_mut().for_each(|(mut transform, mut angular_velocity, mut linear_velocity, mut tape)| {
if let Some(t) = tape.translations.pop() { match (tape.translations.pop(), tape.rotations.pop()) {
(Some(t), Some(r)) => {
transform.translation = t; transform.translation = t;
}
if let Some(r) = tape.rotations.pop() {
transform.rotation = r; 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 // TODO: Only need to set {angular|linear}_velocity at end of Rewind
if let Some(av) = tape.angular_velocities.pop() { if let Some(av) = tape.angular_velocities.pop() {
@ -359,10 +451,18 @@ fn alive_bird(
} }
fn pause_bird( fn pause_bird(
#[cfg(debug_assertions)] state: Res<State<PlayerState>>, state: Res<State<PlayerState>>,
mut bird: Single<&mut RigidBody, With<Bird>>, mut bird: Single<&mut RigidBody, With<Bird>>,
mut deaths: ResMut<Deaths>,
) { ) {
debug_assert!(!matches!(state.get(), PlayerState::Alive)); debug_assert!(!matches!(state.get(), PlayerState::Alive));
// Increment death count
match state.get() {
PlayerState::Stasis => deaths.0 += 1,
_ => (),
}
debug!("Setting bird to Static"); debug!("Setting bird to Static");
**bird = RigidBody::Static; **bird = RigidBody::Static;
} }
@ -373,3 +473,63 @@ fn camera_follow_bird(
) { ) {
camera.translation.x = bird.translation.x; 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)
}
}
// Track number of times player died
#[derive(Resource, Default)]
struct Deaths(usize);
impl Display for Deaths {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
writeln!(f, "Deaths: {}", self.0)
}
}
fn scoring(
mut events: EventReader<CollisionEnded>,
state: Res<State<PlayerState>>,
bird: Query<Entity, With<Bird>>,
hitboxes: Query<Entity, With<Hitbox>>,
mut score: ResMut<Score>,
) {
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??"),
}
})
}

@ -20,7 +20,7 @@ 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 // TODO: Not all, don't want to hide mesh
DebuggingState::On => PhysicsGizmos::all(), DebuggingState::On => PhysicsGizmos::default(),
DebuggingState::Off => PhysicsGizmos::none(), DebuggingState::Off => PhysicsGizmos::none(),
}; };
} }

Loading…
Cancel
Save