Compare commits
No commits in common. 'f68b052f2d1040d523fa41467d616b2e6d39b3c0' and '9c4b4a79b99ec53120c90e1a50f9acd17fefcb94' have entirely different histories.
f68b052f2d
...
9c4b4a79b9
@ -1,249 +1,36 @@
|
||||
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>,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
let material = MeshMaterial3d(materials.add(StandardMaterial {
|
||||
base_color_texture: Some(server.load("flappy/bevy.png")),
|
||||
base_color: WHITE.into(),
|
||||
alpha_mode: AlphaMode::Blend,
|
||||
..default()
|
||||
}));
|
||||
|
||||
let mesh = Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(1.0))));
|
||||
|
||||
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 physics = (
|
||||
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,
|
||||
let material = MeshMaterial3d(materials.add(StandardMaterial {
|
||||
base_color_texture: Some(server.load("flappy/bevy.png")),
|
||||
base_color: WHITE.with_alpha(0.9).into(),
|
||||
alpha_mode: AlphaMode::Blend,
|
||||
..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
|
||||
let mesh = Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(1.0))));
|
||||
|
||||
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"
|
||||
);
|
||||
let name = Name::new("bird");
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
let t = Transform::from_xyz(0.0, 0.0, -10.0).with_rotation(Quat::from_rotation_x(PI / 2.0));
|
||||
|
||||
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"
|
||||
);
|
||||
let mass = (
|
||||
RigidBody::Dynamic,
|
||||
Collider::capsule(1.0, 1.0),
|
||||
Mass(5.0),
|
||||
);
|
||||
|
||||
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"
|
||||
);
|
||||
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));
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue