Compare commits

..

No commits in common. 'f68b052f2d1040d523fa41467d616b2e6d39b3c0' and '9c4b4a79b99ec53120c90e1a50f9acd17fefcb94' have entirely different histories.

@ -26,6 +26,7 @@ fn init_image(
})); }));
let mesh = Mesh2d(meshes.add(Rectangle::from_size(Vec2::splat(10.0)))); let mesh = Mesh2d(meshes.add(Rectangle::from_size(Vec2::splat(10.0))));
// opaque // opaque
// Each sprite should be square with the transparent parts being completely black // Each sprite should be square with the transparent parts being completely black
// The blue sprite should be on top with the white and green one behind it // The blue sprite should be on top with the white and green one behind it
@ -49,4 +50,6 @@ fn init_image(
commands.spawn((name, mesh, material, t)); commands.spawn((name, mesh, material, t));
} }
} }

@ -47,7 +47,6 @@ pub fn toggle_state_visibility<S: States + Component>(
}); });
} }
// TODO: Rename to "create camera"
pub fn setup_camera(mut commands: Commands) { pub fn setup_camera(mut commands: Commands) {
commands.spawn((Camera3d { ..default() }, Camera { ..default() }, AmbientLight::default())); commands.spawn((Camera3d { ..default() }, Camera { ..default() }));
} }

@ -1,74 +1,12 @@
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>,
@ -77,7 +15,7 @@ fn init_bird(
) { ) {
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() ..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 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, RigidBody::Dynamic,
Collider::capsule(1.0, 1.0), Mass(1.0), Collider::capsule(1.0, 1.0),
ExternalForce::default().with_persistence(false), Mass(5.0),
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"
); );
if bird.intersects(*ground) {
next.set(PlayerState::Dead);
}
}
fn alive_bird( commands.spawn((name, mesh, material, mass, t));
#[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;
} }

@ -4,11 +4,15 @@ pub(crate) struct TreesDebugPlugin;
impl Plugin for TreesDebugPlugin { impl Plugin for TreesDebugPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(Startup, init_debug_ui) app.add_systems(
.add_systems( Startup,
init_debug_ui,
).add_systems(
Update, Update,
( (
(spawn_debug_buttons.run_if(on_event::<AssetEvent<Monologue>>),), (
spawn_debug_buttons.run_if(on_event::<AssetEvent<Monologue>>),
),
( (
monologue_asset_tooltip monologue_asset_tooltip
.run_if(on_event::<Pointer<Over>>.or(on_event::<Pointer<Out>>)), .run_if(on_event::<Pointer<Over>>.or(on_event::<Pointer<Out>>)),
@ -17,11 +21,9 @@ impl Plugin for TreesDebugPlugin {
control_menu.run_if(on_event::<Pointer<Over>>.or(on_event::<Pointer<Out>>)), control_menu.run_if(on_event::<Pointer<Over>>.or(on_event::<Pointer<Out>>)),
delete_tree.run_if(on_event::<Pointer<Click>>), delete_tree.run_if(on_event::<Pointer<Click>>),
drag_tree.run_if(on_event::<Pointer<Drag>>), drag_tree.run_if(on_event::<Pointer<Drag>>),
).run_if(in_state(DebuggingState::On)),
) )
.run_if(in_state(DebuggingState::On)), ).add_observer(add_dialog_option);
),
)
.add_observer(add_dialog_option);
} }
} }
@ -34,6 +36,7 @@ struct MonologuesList;
#[derive(Component)] #[derive(Component)]
struct MonologuePreview; struct MonologuePreview;
fn drag_tree( fn drag_tree(
mut events: EventReader<Pointer<Drag>>, mut events: EventReader<Pointer<Drag>>,
state: Res<State<DebuggingState>>, state: Res<State<DebuggingState>>,
@ -44,7 +47,8 @@ fn drag_tree(
debug_assert_eq!(*state.get(), DebuggingState::On); debug_assert_eq!(*state.get(), DebuggingState::On);
events.read().for_each(|event| { events.read().for_each(|event| {
if let Ok(mut t) = query.get_mut(event.target) { if let Ok(mut t) = query.get_mut(event.target)
{
let world_position = window let world_position = window
.cursor_position() .cursor_position()
.and_then(|cursor| camera.0.viewport_to_world(camera.1, cursor).ok()) .and_then(|cursor| camera.0.viewport_to_world(camera.1, cursor).ok())
@ -152,6 +156,7 @@ fn init_debug_ui(mut commands: Commands) {
}); });
} }
// When you pointer goes off of the '+' or any of it's children make the entire menu invisible // When you pointer goes off of the '+' or any of it's children make the entire menu invisible
fn control_menu( fn control_menu(
mut over_events: EventReader<Pointer<Over>>, mut over_events: EventReader<Pointer<Over>>,
@ -222,6 +227,7 @@ fn add_dialog_option(trigger: Trigger<OnAdd, DialogOption>, mut commands: Comman
.observe(hover_dialog_option_out); .observe(hover_dialog_option_out);
} }
fn assign_monologue_event( fn assign_monologue_event(
trigger: Trigger<Pointer<Click>>, trigger: Trigger<Pointer<Click>>,
mut events: EventWriter<AssignMonologue>, mut events: EventWriter<AssignMonologue>,
@ -231,11 +237,13 @@ fn assign_monologue_event(
events.write(AssignMonologue(handle.clone())); events.write(AssignMonologue(handle.clone()));
} }
/// Observer for the "Plant a new tree" button in the debug UI /// Observer for the "Plant a new tree" button in the debug UI
fn spawn_tree(_trigger: Trigger<Pointer<Click>>, mut events: EventWriter<PlantTree>) { fn spawn_tree(_trigger: Trigger<Pointer<Click>>, mut events: EventWriter<PlantTree>) {
events.write(PlantTree(None)); events.write(PlantTree(None));
} }
fn clear_monologue( fn clear_monologue(
mut nodes: Query< mut nodes: Query<
(Entity, &NavState), (Entity, &NavState),
@ -250,6 +258,7 @@ fn clear_monologue(
}); });
} }
// When you pointer over the '+' make the entire menu visible // When you pointer over the '+' make the entire menu visible
fn hide_menu(mut nodes: Query<(&mut Visibility, &NavState), Changed<NavState>>) { fn hide_menu(mut nodes: Query<(&mut Visibility, &NavState), Changed<NavState>>) {
nodes.iter_mut().for_each(|(mut v, n)| { nodes.iter_mut().for_each(|(mut v, n)| {
@ -260,11 +269,7 @@ fn hide_menu(mut nodes: Query<(&mut Visibility, &NavState), Changed<NavState>>)
}); });
} }
fn delete_tree( fn delete_tree(mut events: EventReader<Pointer<Click>>, mut commands: Commands, query: Query<Entity, With<Tree>>) {
mut events: EventReader<Pointer<Click>>,
mut commands: Commands,
query: Query<Entity, With<Tree>>,
) {
events.read().for_each(|event| { events.read().for_each(|event| {
if matches!(event.event.button, PointerButton::Middle) && query.contains(event.target) { if matches!(event.event.button, PointerButton::Middle) && query.contains(event.target) {
debug!("Middle Click -> Despawning {}", event.target); debug!("Middle Click -> Despawning {}", event.target);
@ -407,3 +412,4 @@ fn spawn_debug_buttons(
} }
}); });
} }

@ -3,13 +3,13 @@
#![allow(clippy::too_many_arguments)] #![allow(clippy::too_many_arguments)]
#![feature(trim_prefix_suffix)] #![feature(trim_prefix_suffix)]
mod debug;
mod mono; mod mono;
mod debug;
use bevy::{picking::hover::HoverMap, platform::hash::RandomState}; use bevy::{picking::hover::HoverMap, platform::hash::RandomState};
use debug::*;
use games::*; use games::*;
use mono::*; use mono::*;
use debug::*;
use std::hash::BuildHasher; use std::hash::BuildHasher;
fn main() { fn main() {

@ -16,6 +16,7 @@ impl Monologue {
} }
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub struct MonologueParseError; pub struct MonologueParseError;
@ -152,12 +153,16 @@ mod tests {
let expected = Monologue { let expected = Monologue {
batches: vec![ batches: vec![
MonologueLineBatch { MonologueLineBatch {
lines: vec!["hello".into()], lines: vec![
"hello".into()
],
}, },
MonologueLineBatch { MonologueLineBatch {
lines: vec!["world".into()], lines: vec![
}, "world".into()
], ]
}
]
}; };
assert_eq!(parsed, expected); assert_eq!(parsed, expected);
@ -183,12 +188,16 @@ mod tests {
let expected = Monologue { let expected = Monologue {
batches: vec![ batches: vec![
MonologueLineBatch { MonologueLineBatch {
lines: vec!["hello".into()], lines: vec![
"hello".into()
],
}, },
MonologueLineBatch { MonologueLineBatch {
lines: vec!["world".into()], lines: vec![
}, "world".into()
], ]
}
]
}; };
assert_eq!(parsed, expected); assert_eq!(parsed, expected);
@ -220,21 +229,28 @@ mod tests {
let expected = Monologue { let expected = Monologue {
batches: vec![ batches: vec![
MonologueLineBatch { MonologueLineBatch {
lines: vec!["a".into(), "b".into(), "c".into()], lines: vec![
"a".into(), "b".into(), "c".into()
]
}, },
MonologueLineBatch { MonologueLineBatch {
lines: vec!["d".into(), "e".into()], lines: vec![
"d".into(), "e".into()
]
}, },
MonologueLineBatch { MonologueLineBatch {
lines: vec!["f".into()], lines: vec![
"f".into()
]
}, },
], ]
}; };
assert_eq!(parsed, expected); assert_eq!(parsed, expected);
} }
} }
#[derive(Default)] #[derive(Default)]
struct MonologueLoader; struct MonologueLoader;

@ -390,12 +390,7 @@ fn toggle_physics_debug_render(
) { ) {
let (_, config) = config_store.config_mut::<PhysicsGizmos>(); let (_, config) = config_store.config_mut::<PhysicsGizmos>();
*config = match state.get() { *config = match state.get() {
// TODO: Not all, don't want to hide mesh
DebuggingState::On => PhysicsGizmos::all(), DebuggingState::On => PhysicsGizmos::all(),
DebuggingState::Off => PhysicsGizmos::none(), DebuggingState::Off => PhysicsGizmos::none(),
}; };
} }
pub fn debug_state_changes<S: States + std::fmt::Debug>(s: Res<State<S>>) {
info!("State changed: {:?}", s);
}

@ -9,12 +9,12 @@ mod ui;
mod version; mod version;
// Rust stdlib // Rust stdlib
pub use std::f32::consts::PI;
pub use std::fmt::Display; pub use std::fmt::Display;
pub use std::f32::consts::PI;
// Community libraries // Community libraries
pub use avian3d::prelude::*;
pub use bevy::{ pub use bevy::{
sprite::AlphaMode2d,
asset::{AssetLoader, LoadContext, LoadState, LoadedFolder, io::Reader}, asset::{AssetLoader, LoadContext, LoadState, LoadedFolder, io::Reader},
color::palettes::css::*, color::palettes::css::*,
gizmos::{aabb::AabbGizmoPlugin, light::LightGizmoPlugin}, gizmos::{aabb::AabbGizmoPlugin, light::LightGizmoPlugin},
@ -29,9 +29,9 @@ pub use bevy::{
platform::collections::HashMap, platform::collections::HashMap,
prelude::*, prelude::*,
reflect::TypePath, reflect::TypePath,
sprite::AlphaMode2d,
window::WindowResized, window::WindowResized,
}; };
pub use avian3d::prelude::*;
pub use serde::Deserialize; pub use serde::Deserialize;
pub use thiserror::Error; pub use thiserror::Error;

Loading…
Cancel
Save