Compare commits

..

2 Commits

@ -14,6 +14,10 @@ fn main() {
Physics2dPlugin,
))
.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>()
.add_systems(
Startup,
@ -49,7 +53,7 @@ fn main() {
// Systems to run in the "play" state
(
// 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
record.run_if(any_component_changed::<Transform>),
)
@ -64,8 +68,17 @@ 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::<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();
}
@ -124,7 +137,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 +146,9 @@ struct Ground;
#[derive(Component, Clone)]
struct Pipe;
#[derive(Component, Clone)]
struct Hitbox;
fn init_obstacles(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
@ -170,6 +186,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 +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));
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 +236,14 @@ 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::<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>>) {
@ -219,27 +265,50 @@ fn init_ui(mut commands: Commands) {
))
.observe(start_game);
fn start_rewind(trigger: Trigger<Pointer<Pressed>>, mut next: ResMut<NextState<PlayerState>>) {
next.set(PlayerState::Rewind);
}
commands.spawn(
Node {
align_self: AlignSelf::End,
justify_self: JustifySelf::Center,
flex_direction: FlexDirection::Row,
..default()
},
).with_children(|parent| {
parent
.spawn((
Node {
..default()
},
Button,
children![Text::new("Rewind!"),],
))
.observe(start_rewind)
.observe(end_rewind);
parent
.spawn((
Node {
..default()
},
Button,
children![Text::new("Flap!"),],
))
.observe(flap_button);
});
}
fn end_rewind(trigger: Trigger<Pointer<Released>>, mut next: ResMut<NextState<PlayerState>>) {
next.set(PlayerState::Alive);
}
fn start_rewind(_trigger: Trigger<Pointer<Pressed>>, mut next: ResMut<NextState<PlayerState>>) {
next.set(PlayerState::Rewind);
}
commands
.spawn((
Node {
align_self: AlignSelf::End,
justify_self: JustifySelf::Center,
flex_direction: FlexDirection::Column,
..default()
},
Button,
children![Text::new("Rewind!"),],
))
.observe(start_rewind)
.observe(end_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"
@ -254,11 +323,29 @@ fn un_pause_game(mut next: ResMut<NextState<PlayerState>>) {
// TODO: Create floor (and ceiling?)
// Q: Move bird + camera or move world around bird & camera?
// 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)] keycode: Res<ButtonInput<KeyCode>>,
mut bird: Query<(&Transform, &mut ExternalImpulse), With<Bird>>,
birds: Query<Entity, With<Bird>>,
mut commands: Commands,
) {
debug_assert!(
matches!(state.get(), PlayerState::Alive),
@ -269,8 +356,9 @@ fn flap(
"Only flap when space is just pressed"
);
bird.iter_mut().for_each(|(_, mut f)| {
f.apply_impulse(Vec2::Y * 5000.0 + Vec2::X * 1000.0);
birds.iter().for_each(|e| {
info!("Flapping {:?}", e);
commands.trigger_targets(Flap, e);
});
}
@ -309,6 +397,7 @@ fn record(
fn rewind(
#[cfg(debug_assertions)] state: Res<State<PlayerState>>,
mut birds: Query<(&mut Transform, &mut AngularVelocity, &mut LinearVelocity, &mut Tape), With<Bird>>,
mut frames: ResMut<RewindFrames>,
) {
debug_assert!(
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)| {
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() {
@ -359,10 +451,18 @@ fn alive_bird(
}
fn pause_bird(
#[cfg(debug_assertions)] state: Res<State<PlayerState>>,
state: Res<State<PlayerState>>,
mut bird: Single<&mut RigidBody, With<Bird>>,
mut deaths: ResMut<Deaths>,
) {
debug_assert!(!matches!(state.get(), PlayerState::Alive));
// Increment death count
match state.get() {
PlayerState::Stasis => deaths.0 += 1,
_ => (),
}
debug!("Setting bird to Static");
**bird = RigidBody::Static;
}
@ -373,3 +473,63 @@ 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)
}
}
// 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>();
*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(),
};
}

Loading…
Cancel
Save