|
|
|
@ -1,44 +1,98 @@
|
|
|
|
|
|
|
|
use bevy::{input::common_conditions::input_just_released, render::view::VisibleEntities};
|
|
|
|
use games::*;
|
|
|
|
use games::*;
|
|
|
|
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
fn main() {
|
|
|
|
App::new()
|
|
|
|
App::new()
|
|
|
|
.add_plugins(BaseGamePlugin { name: "flappy bird (with rewind)".into() })
|
|
|
|
.add_plugins(BaseGamePlugin {
|
|
|
|
.add_systems(Startup, init_bird)
|
|
|
|
name: "flappy bird (with rewind)".into(),
|
|
|
|
.add_systems(Update, flap)
|
|
|
|
})
|
|
|
|
|
|
|
|
.init_state::<PlayerState>()
|
|
|
|
|
|
|
|
.add_systems(Startup, (init_bird, init_ui))
|
|
|
|
|
|
|
|
.add_systems(OnEnter(PlayerState::Dead), kill_bird)
|
|
|
|
|
|
|
|
.add_systems(OnEnter(PlayerState::Alive), alive_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.run_if(input_just_pressed(KeyCode::Space)),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
.run_if(in_state(PlayerState::Alive)),
|
|
|
|
|
|
|
|
// Rewinding systems
|
|
|
|
|
|
|
|
rewind.run_if(in_state(PlayerState::Rewind)),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
)
|
|
|
|
.run();
|
|
|
|
.run();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Component)]
|
|
|
|
#[derive(Component)]
|
|
|
|
struct Bird;
|
|
|
|
struct Bird;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(States, Clone, Eq, PartialEq, Debug, Hash, Default, Component)]
|
|
|
|
|
|
|
|
enum PlayerState {
|
|
|
|
|
|
|
|
#[default]
|
|
|
|
|
|
|
|
Alive,
|
|
|
|
|
|
|
|
Rewind,
|
|
|
|
|
|
|
|
Dead,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn init_bird(
|
|
|
|
fn init_bird(
|
|
|
|
mut commands: Commands,
|
|
|
|
mut commands: Commands,
|
|
|
|
server: Res<AssetServer>,
|
|
|
|
server: Res<AssetServer>,
|
|
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
|
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
|
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
|
|
) {
|
|
|
|
) {
|
|
|
|
let material = MeshMaterial3d(materials.add(StandardMaterial {
|
|
|
|
let material = MeshMaterial3d(materials.add(StandardMaterial {
|
|
|
|
base_color_texture: Some(server.load("flappy/bevy.png")),
|
|
|
|
base_color_texture: Some(server.load("flappy/bevy.png")),
|
|
|
|
base_color: WHITE.with_alpha(0.9).into(),
|
|
|
|
base_color: WHITE.with_alpha(0.9).into(),
|
|
|
|
alpha_mode: AlphaMode::Blend,
|
|
|
|
alpha_mode: AlphaMode::Blend,
|
|
|
|
..default()
|
|
|
|
..default()
|
|
|
|
}));
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
let mesh = Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(1.0))));
|
|
|
|
let mesh = Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(1.0))));
|
|
|
|
|
|
|
|
|
|
|
|
let name = Name::new("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, -10.0).with_rotation(Quat::from_rotation_x(PI / 2.0));
|
|
|
|
|
|
|
|
|
|
|
|
let mass = (
|
|
|
|
let mass = (RigidBody::Dynamic, Collider::capsule(1.0, 1.0), Mass(1.0));
|
|
|
|
RigidBody::Dynamic,
|
|
|
|
|
|
|
|
Collider::capsule(1.0, 1.0),
|
|
|
|
|
|
|
|
Mass(1.0),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let force = ExternalForce::default().with_persistence(false);
|
|
|
|
let force = ExternalForce::default().with_persistence(false);
|
|
|
|
|
|
|
|
|
|
|
|
commands.spawn((name, mesh, material, mass, t, Bird, force));
|
|
|
|
commands.spawn((name, mesh, material, mass, t, Bird, force));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn init_ui(
|
|
|
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
commands.spawn((
|
|
|
|
|
|
|
|
Node {
|
|
|
|
|
|
|
|
align_self: AlignSelf::Center,
|
|
|
|
|
|
|
|
justify_self: JustifySelf::Center,
|
|
|
|
|
|
|
|
flex_direction: FlexDirection::Column,
|
|
|
|
|
|
|
|
..default()
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
PlayerState::Dead,
|
|
|
|
|
|
|
|
children![
|
|
|
|
|
|
|
|
Text::new("You Died"),
|
|
|
|
|
|
|
|
Text::new("Press R to Rewind"),
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: Create floor (and ceiling?)
|
|
|
|
// TODO: Create floor (and ceiling?)
|
|
|
|
@ -46,12 +100,98 @@ fn init_bird(
|
|
|
|
// TODO: Obstacles
|
|
|
|
// TODO: Obstacles
|
|
|
|
|
|
|
|
|
|
|
|
fn flap(
|
|
|
|
fn flap(
|
|
|
|
keyboard: Res<ButtonInput<KeyCode>>,
|
|
|
|
#[cfg(debug_assertions)] state: Res<State<PlayerState>>,
|
|
|
|
|
|
|
|
#[cfg(debug_assertions)] keycode: Res<ButtonInput<KeyCode>>,
|
|
|
|
mut bird: Query<(&Transform, &mut ExternalForce), With<Bird>>,
|
|
|
|
mut bird: Query<(&Transform, &mut ExternalForce), With<Bird>>,
|
|
|
|
) {
|
|
|
|
) {
|
|
|
|
if keyboard.just_pressed(KeyCode::Space) {
|
|
|
|
debug_assert!(
|
|
|
|
bird.iter_mut().for_each(|(t, mut f)| {
|
|
|
|
matches!(state.get(), PlayerState::Alive),
|
|
|
|
f.apply_force(t.rotation * Vec3::NEG_Z * 1000.0);
|
|
|
|
"Only flap when playing"
|
|
|
|
});
|
|
|
|
);
|
|
|
|
|
|
|
|
debug_assert!(
|
|
|
|
|
|
|
|
keycode.just_pressed(KeyCode::Space),
|
|
|
|
|
|
|
|
"Only flap when space is just pressed"
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bird.iter_mut().for_each(|(t, mut f)| {
|
|
|
|
|
|
|
|
f.apply_force(t.rotation * Vec3::NEG_Z * 1000.0);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.pressed(KeyCode::KeyR) {
|
|
|
|
|
|
|
|
info!("Toggling rewind ON");
|
|
|
|
|
|
|
|
next.set(PlayerState::Rewind)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
info!("Toggling rewind OFF");
|
|
|
|
|
|
|
|
next.set(PlayerState::Alive)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn rewind(
|
|
|
|
|
|
|
|
#[cfg(debug_assertions)] state: Res<State<PlayerState>>,
|
|
|
|
|
|
|
|
birds: Query<&Transform, With<Bird>>,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
debug_assert!(
|
|
|
|
|
|
|
|
matches!(state.get(), PlayerState::Rewind),
|
|
|
|
|
|
|
|
"Only rewind in the rewinding state"
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
birds.iter().for_each(|bird| {
|
|
|
|
|
|
|
|
info!("Position: {:?}", *bird);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// PERF: Runs more than it needs, should only execute when bird enters/exit frame
|
|
|
|
|
|
|
|
fn detect_dead(
|
|
|
|
|
|
|
|
#[cfg(debug_assertions)] state: Res<State<PlayerState>>,
|
|
|
|
|
|
|
|
bird: Single<Entity, With<Bird>>,
|
|
|
|
|
|
|
|
visible: Query<&VisibleEntities, With<Camera>>,
|
|
|
|
|
|
|
|
mut next: ResMut<NextState<PlayerState>>,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
debug_assert!(
|
|
|
|
|
|
|
|
matches!(state.get(), PlayerState::Alive),
|
|
|
|
|
|
|
|
"Only check if dead while alive"
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
visible.iter().for_each(|ve| {
|
|
|
|
|
|
|
|
if ve.entities.iter().len() > 0 {
|
|
|
|
|
|
|
|
let bird_visible = ve
|
|
|
|
|
|
|
|
.entities
|
|
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
|
|
.any(|(_type_id, list)| list.contains(&(*bird)));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if bird_visible {
|
|
|
|
|
|
|
|
info!("Bird is visible, making alive");
|
|
|
|
|
|
|
|
next.set(PlayerState::Alive);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
info!("Bird is not visible, making dead");
|
|
|
|
|
|
|
|
next.set(PlayerState::Dead);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn alive_bird(
|
|
|
|
|
|
|
|
#[cfg(debug_assertions)] state: Res<State<PlayerState>>,
|
|
|
|
|
|
|
|
bird: Single<Entity, With<Bird>>,
|
|
|
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
debug_assert!(!matches!(state.get(), PlayerState::Dead));
|
|
|
|
|
|
|
|
debug!("Aliving bird");
|
|
|
|
|
|
|
|
commands.entity(*bird).insert(RigidBody::Dynamic);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn kill_bird(
|
|
|
|
|
|
|
|
#[cfg(debug_assertions)] state: Res<State<PlayerState>>,
|
|
|
|
|
|
|
|
bird: Single<Entity, With<Bird>>,
|
|
|
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
debug_assert!(matches!(state.get(), PlayerState::Dead));
|
|
|
|
|
|
|
|
debug!("Killing bird");
|
|
|
|
|
|
|
|
commands.entity(*bird).remove::<RigidBody>();
|
|
|
|
|
|
|
|
}
|
|
|
|
|