|
|
|
|
@ -1,74 +1,12 @@
|
|
|
|
|
use bevy::{input::common_conditions::input_just_released, render::view::VisibleEntities};
|
|
|
|
|
use games::*;
|
|
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
|
App::new()
|
|
|
|
|
.add_plugins(BaseGamePlugin {
|
|
|
|
|
name: "flappy bird (with rewind)".into(),
|
|
|
|
|
})
|
|
|
|
|
.init_state::<PlayerState>()
|
|
|
|
|
.add_systems(Startup, (init_bird, init_ground, init_ui, tweak_camera.after(setup_camera)))
|
|
|
|
|
.add_systems(OnEnter(PlayerState::Alive), alive_bird)
|
|
|
|
|
.add_systems(OnExit(PlayerState::Alive), kill_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)),
|
|
|
|
|
// Rewinding systems
|
|
|
|
|
record.run_if(any_component_changed::<Transform>),
|
|
|
|
|
)
|
|
|
|
|
.run_if(in_state(PlayerState::Alive)),
|
|
|
|
|
// Rewinding systems
|
|
|
|
|
rewind.run_if(in_state(PlayerState::Rewind)),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.add_plugins(BaseGamePlugin { name: "flappy bird (with rewind)".into() })
|
|
|
|
|
.add_systems(Startup, init_bird)
|
|
|
|
|
.run();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn tweak_camera(
|
|
|
|
|
mut camera: Query<(&mut Camera, &mut AmbientLight), With<Camera>>,
|
|
|
|
|
) {
|
|
|
|
|
camera.iter_mut().for_each(|(mut c, mut al)| {
|
|
|
|
|
c.clear_color = ClearColorConfig::Custom(WHITE.into());
|
|
|
|
|
al.brightness = 100.0;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Component)]
|
|
|
|
|
struct Bird;
|
|
|
|
|
|
|
|
|
|
#[derive(States, Clone, Eq, PartialEq, Debug, Hash, Default, Component)]
|
|
|
|
|
enum PlayerState {
|
|
|
|
|
#[default]
|
|
|
|
|
Alive,
|
|
|
|
|
Rewind,
|
|
|
|
|
Dead,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A tape tracking the bird's state every frame
|
|
|
|
|
#[derive(Component, Default)]
|
|
|
|
|
struct Tape {
|
|
|
|
|
translations: Vec<Vec3>,
|
|
|
|
|
rotations: Vec<Quat>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn init_bird(
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
server: Res<AssetServer>,
|
|
|
|
|
@ -77,7 +15,7 @@ fn init_bird(
|
|
|
|
|
) {
|
|
|
|
|
let material = MeshMaterial3d(materials.add(StandardMaterial {
|
|
|
|
|
base_color_texture: Some(server.load("flappy/bevy.png")),
|
|
|
|
|
base_color: WHITE.into(),
|
|
|
|
|
base_color: WHITE.with_alpha(0.9).into(),
|
|
|
|
|
alpha_mode: AlphaMode::Blend,
|
|
|
|
|
..default()
|
|
|
|
|
}));
|
|
|
|
|
@ -88,162 +26,11 @@ fn init_bird(
|
|
|
|
|
|
|
|
|
|
let t = Transform::from_xyz(0.0, 0.0, -10.0).with_rotation(Quat::from_rotation_x(PI / 2.0));
|
|
|
|
|
|
|
|
|
|
let physics = (
|
|
|
|
|
let mass = (
|
|
|
|
|
RigidBody::Dynamic,
|
|
|
|
|
Collider::capsule(1.0, 1.0), Mass(1.0),
|
|
|
|
|
ExternalForce::default().with_persistence(false),
|
|
|
|
|
LockedAxes::ROTATION_LOCKED.lock_translation_z(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let tape = Tape::default();
|
|
|
|
|
|
|
|
|
|
commands.spawn((name, mesh, material, physics, t, Bird, tape));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Component)]
|
|
|
|
|
struct Ground;
|
|
|
|
|
|
|
|
|
|
fn init_ground(
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
|
|
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
|
|
|
) {
|
|
|
|
|
let material = MeshMaterial3d(materials.add(StandardMaterial {
|
|
|
|
|
base_color: GREEN.into(),
|
|
|
|
|
..default()
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
let mesh = Mesh3d(meshes.add(Cuboid::new(5.0, 1.0, 1.0)));
|
|
|
|
|
|
|
|
|
|
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));
|
|
|
|
|
|
|
|
|
|
commands.spawn((name, mesh, material, physics, t, Ground));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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?)
|
|
|
|
|
// Q: Move bird + camera or move world around bird & camera?
|
|
|
|
|
// TODO: Obstacles
|
|
|
|
|
|
|
|
|
|
fn flap(
|
|
|
|
|
#[cfg(debug_assertions)] state: Res<State<PlayerState>>,
|
|
|
|
|
#[cfg(debug_assertions)] keycode: Res<ButtonInput<KeyCode>>,
|
|
|
|
|
mut bird: Query<(&Transform, &mut ExternalForce), With<Bird>>,
|
|
|
|
|
) {
|
|
|
|
|
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"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
bird.iter_mut().for_each(|(t, mut f)| {
|
|
|
|
|
f.apply_force(t.rotation * Vec3::NEG_Z * 500.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.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, &mut Tape), With<Bird>>,
|
|
|
|
|
) {
|
|
|
|
|
debug_assert!(
|
|
|
|
|
matches!(state.get(), PlayerState::Alive),
|
|
|
|
|
"Only record in the alive state"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
birds.iter_mut().for_each(|(transform, mut tape)| {
|
|
|
|
|
tape.translations.push(transform.translation);
|
|
|
|
|
tape.rotations.push(transform.rotation);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn rewind(
|
|
|
|
|
#[cfg(debug_assertions)] state: Res<State<PlayerState>>,
|
|
|
|
|
mut birds: Query<(&mut Transform, &mut Tape), With<Bird>>,
|
|
|
|
|
) {
|
|
|
|
|
debug_assert!(
|
|
|
|
|
matches!(state.get(), PlayerState::Rewind),
|
|
|
|
|
"Only rewind in the rewinding state"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
birds.iter_mut().for_each(|(mut transform, mut tape)| {
|
|
|
|
|
if let Some(t) = tape.translations.pop() {
|
|
|
|
|
transform.translation = t;
|
|
|
|
|
}
|
|
|
|
|
if let Some(r) = tape.rotations.pop() {
|
|
|
|
|
transform.rotation = r;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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<&ColliderAabb, With<Bird>>,
|
|
|
|
|
ground: Single<&ColliderAabb, With<Ground>>,
|
|
|
|
|
mut next: ResMut<NextState<PlayerState>>,
|
|
|
|
|
) {
|
|
|
|
|
debug_assert!(
|
|
|
|
|
matches!(state.get(), PlayerState::Alive),
|
|
|
|
|
"Only check if dead while alive"
|
|
|
|
|
Collider::capsule(1.0, 1.0),
|
|
|
|
|
Mass(5.0),
|
|
|
|
|
);
|
|
|
|
|
if bird.intersects(*ground) {
|
|
|
|
|
next.set(PlayerState::Dead);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn alive_bird(
|
|
|
|
|
#[cfg(debug_assertions)] state: Res<State<PlayerState>>,
|
|
|
|
|
mut bird: Single<&mut RigidBody, With<Bird>>,
|
|
|
|
|
) {
|
|
|
|
|
debug_assert!(!matches!(state.get(), PlayerState::Dead));
|
|
|
|
|
debug!("Aliving bird");
|
|
|
|
|
**bird = RigidBody::Dynamic;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn kill_bird(
|
|
|
|
|
#[cfg(debug_assertions)] state: Res<State<PlayerState>>,
|
|
|
|
|
mut bird: Single<&mut RigidBody, With<Bird>>,
|
|
|
|
|
) {
|
|
|
|
|
debug_assert!(!matches!(state.get(), PlayerState::Alive));
|
|
|
|
|
debug!("Killing bird");
|
|
|
|
|
**bird = RigidBody::Static;
|
|
|
|
|
commands.spawn((name, mesh, material, mass, t));
|
|
|
|
|
}
|
|
|
|
|
|