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::*;
|
use games::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins(BaseGamePlugin {
|
.add_plugins(BaseGamePlugin { name: "flappy bird (with rewind)".into() })
|
||||||
name: "flappy bird (with rewind)".into(),
|
.add_systems(Startup, init_bird)
|
||||||
})
|
|
||||||
.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)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.run();
|
.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(
|
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.into(),
|
base_color: WHITE.with_alpha(0.9).into(),
|
||||||
alpha_mode: AlphaMode::Blend,
|
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,
|
|
||||||
..default()
|
..default()
|
||||||
},
|
}));
|
||||||
PlayerState::Dead,
|
|
||||||
children![
|
|
||||||
Text::new("You Died"),
|
|
||||||
Text::new("Press R to Rewind"),
|
|
||||||
],
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Create floor (and ceiling?)
|
let mesh = Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(1.0))));
|
||||||
// Q: Move bird + camera or move world around bird & camera?
|
|
||||||
// TODO: Obstacles
|
|
||||||
|
|
||||||
fn flap(
|
let name = Name::new("bird");
|
||||||
#[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)| {
|
let t = Transform::from_xyz(0.0, 0.0, -10.0).with_rotation(Quat::from_rotation_x(PI / 2.0));
|
||||||
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(
|
let mass = (
|
||||||
#[cfg(debug_assertions)] state: Res<State<PlayerState>>,
|
RigidBody::Dynamic,
|
||||||
mut birds: Query<(&Transform, &mut Tape), With<Bird>>,
|
Collider::capsule(1.0, 1.0),
|
||||||
) {
|
Mass(5.0),
|
||||||
debug_assert!(
|
);
|
||||||
matches!(state.get(), PlayerState::Alive),
|
|
||||||
"Only record in the alive state"
|
|
||||||
);
|
|
||||||
|
|
||||||
birds.iter_mut().for_each(|(transform, mut tape)| {
|
commands.spawn((name, mesh, material, mass, t));
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue