You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
640 lines
19 KiB
Rust
640 lines
19 KiB
Rust
// Bevy basically forces "complex types" with Querys
|
|
#![allow(clippy::type_complexity)]
|
|
|
|
use bevy::platform::hash::RandomState;
|
|
use std::hash::BuildHasher;
|
|
use games::physics2d::*;
|
|
use games::*;
|
|
|
|
fn main() {
|
|
App::new()
|
|
.add_plugins((
|
|
BaseGamePlugin {
|
|
name: "flappy bird (with rewind)".into(),
|
|
game_type: GameType::Two,
|
|
},
|
|
Physics2dPlugin,
|
|
))
|
|
.insert_resource(Gravity(Vec2::NEG_Y * 9.8 * 100.0))
|
|
.init_resource::<PipeComponents>()
|
|
.init_resource::<GroundComponents>()
|
|
.init_resource::<Score>()
|
|
.init_resource::<RewindFrames>()
|
|
.init_resource::<Flaps>()
|
|
.init_resource::<Deaths>()
|
|
.init_state::<PlayerState>()
|
|
.add_systems(
|
|
Startup,
|
|
(
|
|
init_bird,
|
|
// init_obstacles,
|
|
init_obstacle_assets,
|
|
init_first_batches.after(init_obstacle_assets),
|
|
init_ui,
|
|
tweak_camera.after(create_camera_2d),
|
|
),
|
|
)
|
|
.add_systems(OnEnter(PlayerState::Alive), alive_bird)
|
|
.add_systems(OnEnter(PlayerState::Pause), pause_bird)
|
|
.add_systems(OnEnter(PlayerState::Stasis), pause_bird)
|
|
.add_systems(OnEnter(PlayerState::Rewind), pause_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_kb.run_if(input_just_pressed(KeyCode::Space)),
|
|
// Rewinding systems
|
|
record.run_if(any_component_changed::<Transform>),
|
|
)
|
|
.run_if(in_state(PlayerState::Alive)),
|
|
// Rewinding systems
|
|
rewind.run_if(in_state(PlayerState::Rewind)),
|
|
// Camera follows when bird moves regardless of player state
|
|
camera_follow_bird.run_if(any_component_changed::<Transform>),
|
|
// Pause when the player presses Escape
|
|
pause_game.run_if(input_just_pressed(KeyCode::Escape)),
|
|
// Transition out of the pause screen if the player presses space
|
|
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)
|
|
.add_observer(populate_batch)
|
|
.add_observer(populate_pipe)
|
|
.add_observer(populate_ground)
|
|
.add_observer(populate_hitbox)
|
|
.run();
|
|
}
|
|
|
|
fn tweak_camera(camera: Single<&mut Camera>) {
|
|
debug!("Tweaking camera");
|
|
let mut c = camera.into_inner();
|
|
c.clear_color = ClearColorConfig::Custom(WHITE.into());
|
|
}
|
|
|
|
#[derive(Component)]
|
|
struct Bird;
|
|
|
|
#[derive(States, Clone, Eq, PartialEq, Debug, Hash, Default, Component)]
|
|
enum PlayerState {
|
|
Alive,
|
|
Rewind,
|
|
Stasis,
|
|
#[default]
|
|
Pause,
|
|
}
|
|
|
|
// A tape tracking the bird's state every frame
|
|
#[derive(Component, Default)]
|
|
struct Tape {
|
|
translations: Vec<Vec3>,
|
|
rotations: Vec<Quat>,
|
|
linear_velocities: Vec<LinearVelocity>,
|
|
angular_velocities: Vec<AngularVelocity>,
|
|
}
|
|
|
|
fn init_bird(
|
|
mut commands: Commands,
|
|
server: Res<AssetServer>,
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
mut materials: ResMut<Assets<ColorMaterial>>,
|
|
) {
|
|
let material = MeshMaterial2d(materials.add(ColorMaterial {
|
|
texture: Some(server.load("kenny.nl/1-bit-platformer-pack/tile_0380.png")),
|
|
color: ORANGE.into(),
|
|
alpha_mode: AlphaMode2d::Blend,
|
|
..default()
|
|
}));
|
|
|
|
let mesh = Mesh2d(meshes.add(Rectangle::new(1.0, 1.0)));
|
|
|
|
let name = Name::new("bird");
|
|
|
|
let t = Transform::from_xyz(0.0, 0.0, 0.0).with_scale(Vec3::splat(100.0));
|
|
|
|
let physics = (
|
|
RigidBody::Static,
|
|
Collider::circle(0.5),
|
|
Mass(10.0),
|
|
ExternalImpulse::default().with_persistence(false),
|
|
);
|
|
|
|
let tape = Tape::default();
|
|
|
|
commands.spawn((
|
|
name,
|
|
mesh,
|
|
material,
|
|
physics,
|
|
t,
|
|
Bird,
|
|
tape,
|
|
CollisionEventsEnabled,
|
|
));
|
|
}
|
|
|
|
#[derive(Component, Clone)]
|
|
struct Ground(isize);
|
|
|
|
#[derive(Component, Clone)]
|
|
enum Pipe {
|
|
Top,
|
|
Bottom,
|
|
}
|
|
|
|
#[derive(Component, Clone)]
|
|
struct Hitbox;
|
|
|
|
#[derive(Component, Clone)]
|
|
struct Batch(usize);
|
|
|
|
fn init_first_batches(
|
|
mut commands: Commands
|
|
) {
|
|
commands.spawn(Batch(0));
|
|
commands.spawn(Batch(1));
|
|
commands.spawn(Batch(2));
|
|
commands.spawn(Batch(3));
|
|
commands.spawn(Batch(4));
|
|
}
|
|
|
|
fn populate_batch(
|
|
trigger: Trigger<OnAdd, Batch>,
|
|
batches: Query<&Batch>,
|
|
mut commands: Commands,
|
|
) {
|
|
let Batch(batch_id) = batches.get(trigger.target()).unwrap();
|
|
commands
|
|
.entity(trigger.target())
|
|
.insert((Transform::from_xyz(500.0 * (*batch_id) as f32, 0.0, 0.0), Visibility::Inherited))
|
|
.with_children(|parent| {
|
|
parent.spawn(Ground(-2));
|
|
parent.spawn(Ground(-1));
|
|
parent.spawn(Ground(0));
|
|
parent.spawn(Ground(1));
|
|
parent.spawn(Ground(2));
|
|
if *batch_id > 0 {
|
|
parent.spawn(Pipe::Top);
|
|
parent.spawn(Pipe::Bottom);
|
|
parent.spawn(Hitbox);
|
|
}
|
|
});
|
|
}
|
|
|
|
fn populate_ground(
|
|
trigger: Trigger<OnAdd, Ground>,
|
|
grounds: Query<&Ground>,
|
|
ground_assets: Res<GroundComponents>,
|
|
mut commands: Commands,
|
|
) {
|
|
let Ground(idx) = grounds.get(trigger.target()).unwrap();
|
|
commands.entity(trigger.target()).insert((
|
|
ground_assets.material.clone(),
|
|
ground_assets.mesh.clone(),
|
|
Name::new("ground"),
|
|
RigidBody::Static, Collider::rectangle(1.0, 1.0),
|
|
Transform::from_xyz(100.0 * (*idx) as f32, -400.0, -1.0).with_scale(Vec3::splat(100.0))
|
|
));
|
|
}
|
|
|
|
fn populate_pipe(
|
|
trigger: Trigger<OnAdd, Pipe>,
|
|
pipes: Query<&Pipe>,
|
|
pipe_assets: Res<PipeComponents>,
|
|
mut commands: Commands,
|
|
) {
|
|
let pipe_t = match pipes.get(trigger.target()).unwrap() {
|
|
Pipe::Top => Transform::from_xyz(0.0, 200.0, -1.0).with_scale(Vec3::splat(100.0)),
|
|
Pipe::Bottom => Transform::from_xyz(0.0, -200.0, -1.0).with_scale(Vec3::splat(100.0)),
|
|
};
|
|
commands.entity(trigger.target()).insert((
|
|
pipe_t,
|
|
pipe_assets.material.clone(),
|
|
pipe_assets.mesh.clone(),
|
|
RigidBody::Static, Collider::rectangle(1.0, 1.0),
|
|
Name::new("pipe"),
|
|
));
|
|
}
|
|
|
|
fn populate_hitbox(
|
|
trigger: Trigger<OnAdd, Hitbox>,
|
|
mut commands: Commands,
|
|
) {
|
|
commands.entity(trigger.target()).insert((
|
|
RigidBody::Static,
|
|
Collider::rectangle(1.0, 10.0),
|
|
Sensor,
|
|
CollisionEventsEnabled,
|
|
Name::new("hitbox"),
|
|
Transform::from_xyz(0.0, 0.0, 0.0).with_scale(Vec3::splat(100.0)),
|
|
));
|
|
}
|
|
|
|
#[derive(Resource, Default)]
|
|
struct GroundComponents {
|
|
material: MeshMaterial2d<ColorMaterial>,
|
|
mesh: Mesh2d,
|
|
}
|
|
|
|
#[derive(Resource, Default)]
|
|
struct PipeComponents {
|
|
material: MeshMaterial2d<ColorMaterial>,
|
|
mesh: Mesh2d,
|
|
}
|
|
|
|
fn init_obstacle_assets(
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
mut materials: ResMut<Assets<ColorMaterial>>,
|
|
mut pipe_assets: ResMut<PipeComponents>,
|
|
mut ground_assets: ResMut<GroundComponents>,
|
|
server: Res<AssetServer>,
|
|
) {
|
|
pipe_assets.material = MeshMaterial2d(materials.add(ColorMaterial {
|
|
texture: Some(server.load("kenny.nl/1-bit-platformer-pack/tile_0247.png")),
|
|
color: GREEN.into(),
|
|
..default()
|
|
}));
|
|
pipe_assets.mesh = Mesh2d(meshes.add(Rectangle::new(1.0, 1.0)));
|
|
|
|
ground_assets.material = MeshMaterial2d(materials.add(ColorMaterial {
|
|
texture: Some(server.load("kenny.nl/1-bit-platformer-pack/tile_0088.png")),
|
|
color: BLACK.into(),
|
|
..default()
|
|
}));
|
|
|
|
ground_assets.mesh = Mesh2d(meshes.add(Rectangle::new(1.0, 1.0)));
|
|
}
|
|
|
|
fn init_ui(mut commands: Commands) {
|
|
commands.spawn((
|
|
Node {
|
|
align_self: AlignSelf::Center,
|
|
justify_self: JustifySelf::Center,
|
|
flex_direction: FlexDirection::Column,
|
|
..default()
|
|
},
|
|
PlayerState::Stasis,
|
|
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>>) {
|
|
next.set(PlayerState::Alive);
|
|
}
|
|
|
|
commands
|
|
.spawn((
|
|
Node {
|
|
align_self: AlignSelf::Center,
|
|
justify_self: JustifySelf::Center,
|
|
flex_direction: FlexDirection::Column,
|
|
..default()
|
|
},
|
|
Button,
|
|
// TODO: Add Pause (basically Stasis) state
|
|
PlayerState::Pause,
|
|
children![Text::new("Go!"),],
|
|
))
|
|
.observe(start_game);
|
|
|
|
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);
|
|
});
|
|
|
|
commands.spawn((
|
|
Node {
|
|
align_self: AlignSelf::Start,
|
|
justify_self: JustifySelf::Center,
|
|
..default()
|
|
},
|
|
BackgroundColor(WHITE.into()),
|
|
SyncResource::<Score>::default(),
|
|
Text::default(),
|
|
TextLayout::new_with_justify(JustifyText::Center),
|
|
));
|
|
}
|
|
|
|
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;
|
|
debug!("Flapping {:?}", e);
|
|
commands.trigger_targets(Flap, e);
|
|
}
|
|
|
|
/// Pause the game when the player presses "Escape"
|
|
fn pause_game(mut next: ResMut<NextState<PlayerState>>) {
|
|
next.set(PlayerState::Pause);
|
|
}
|
|
|
|
fn un_pause_game(mut next: ResMut<NextState<PlayerState>>) {
|
|
next.set(PlayerState::Alive);
|
|
}
|
|
|
|
// 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>,
|
|
) {
|
|
debug!("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_kb(
|
|
#[cfg(debug_assertions)] state: Res<State<PlayerState>>,
|
|
#[cfg(debug_assertions)] keycode: Res<ButtonInput<KeyCode>>,
|
|
birds: Query<Entity, With<Bird>>,
|
|
mut commands: Commands,
|
|
) {
|
|
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"
|
|
);
|
|
|
|
birds.iter().for_each(|e| {
|
|
debug!("Flapping {:?}", e);
|
|
commands.trigger_targets(Flap, e);
|
|
});
|
|
}
|
|
|
|
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.just_pressed(KeyCode::KeyR) {
|
|
debug!("Toggling rewind ON");
|
|
next.set(PlayerState::Rewind)
|
|
} else {
|
|
debug!("Toggling rewind OFF");
|
|
next.set(PlayerState::Alive)
|
|
}
|
|
}
|
|
|
|
fn record(
|
|
#[cfg(debug_assertions)] state: Res<State<PlayerState>>,
|
|
mut birds: Query<(&Transform, &LinearVelocity, &AngularVelocity, &mut Tape), With<Bird>>,
|
|
) {
|
|
debug_assert!(
|
|
matches!(state.get(), PlayerState::Alive),
|
|
"Only record in the alive state"
|
|
);
|
|
|
|
birds
|
|
.iter_mut()
|
|
.for_each(|(transform, linear_velocity, angular_velocity, mut tape)| {
|
|
tape.translations.push(transform.translation);
|
|
tape.rotations.push(transform.rotation);
|
|
tape.linear_velocities.push(*linear_velocity);
|
|
tape.angular_velocities.push(*angular_velocity);
|
|
});
|
|
}
|
|
|
|
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),
|
|
"Only rewind in the rewinding state"
|
|
);
|
|
|
|
birds.iter_mut().for_each(
|
|
|(mut transform, mut angular_velocity, mut linear_velocity, mut tape)| {
|
|
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() {
|
|
*angular_velocity = av;
|
|
}
|
|
if let Some(lv) = tape.linear_velocities.pop() {
|
|
*linear_velocity = lv;
|
|
}
|
|
},
|
|
);
|
|
}
|
|
|
|
// PERF: May run more than necessary, should be event-driven on aabb intersection
|
|
fn detect_dead(
|
|
#[cfg(debug_assertions)] state: Res<State<PlayerState>>,
|
|
bird: Single<&ColliderAabb, With<Bird>>,
|
|
obstacles: Query<&ColliderAabb, Or<(With<Ground>, With<Pipe>)>>,
|
|
mut next: ResMut<NextState<PlayerState>>,
|
|
) {
|
|
debug_assert!(
|
|
matches!(state.get(), PlayerState::Alive),
|
|
"Only check if dead while alive"
|
|
);
|
|
|
|
if obstacles.iter().any(|obstacle| bird.intersects(obstacle)) {
|
|
next.set(PlayerState::Stasis);
|
|
}
|
|
}
|
|
|
|
fn alive_bird(
|
|
#[cfg(debug_assertions)] state: Res<State<PlayerState>>,
|
|
mut bird: Single<&mut RigidBody, With<Bird>>,
|
|
) {
|
|
debug_assert!(matches!(state.get(), PlayerState::Alive));
|
|
debug!("Setting bird to Dynamic");
|
|
**bird = RigidBody::Dynamic;
|
|
}
|
|
|
|
fn pause_bird(
|
|
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
|
|
if state.get() == &PlayerState::Stasis { deaths.0 += 1 }
|
|
|
|
debug!("Setting bird to Static");
|
|
**bird = RigidBody::Static;
|
|
}
|
|
|
|
fn camera_follow_bird(
|
|
bird: Single<&Transform, (With<Bird>, Changed<Transform>)>,
|
|
mut camera: Single<&mut Transform, (With<Camera>, Without<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)
|
|
}
|
|
}
|
|
|
|
// TODO: Scoring is bugged, need to make it correct.
|
|
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(|_| {
|
|
debug!("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),
|
|
PlayerState::Pause | PlayerState::Stasis => (),
|
|
}
|
|
})
|
|
}
|