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))));
// opaque
// 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
@ -49,4 +50,6 @@ fn init_image(
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) {
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::*;
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));
}

@ -4,11 +4,15 @@ pub(crate) struct TreesDebugPlugin;
impl Plugin for TreesDebugPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, init_debug_ui)
.add_systems(
app.add_systems(
Startup,
init_debug_ui,
).add_systems(
Update,
(
(spawn_debug_buttons.run_if(on_event::<AssetEvent<Monologue>>),),
(
spawn_debug_buttons.run_if(on_event::<AssetEvent<Monologue>>),
),
(
monologue_asset_tooltip
.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>>)),
delete_tree.run_if(on_event::<Pointer<Click>>),
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)]
struct MonologuePreview;
fn drag_tree(
mut events: EventReader<Pointer<Drag>>,
state: Res<State<DebuggingState>>,
@ -44,7 +47,8 @@ fn drag_tree(
debug_assert_eq!(*state.get(), DebuggingState::On);
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
.cursor_position()
.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
fn control_menu(
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);
}
fn assign_monologue_event(
trigger: Trigger<Pointer<Click>>,
mut events: EventWriter<AssignMonologue>,
@ -231,11 +237,13 @@ fn assign_monologue_event(
events.write(AssignMonologue(handle.clone()));
}
/// Observer for the "Plant a new tree" button in the debug UI
fn spawn_tree(_trigger: Trigger<Pointer<Click>>, mut events: EventWriter<PlantTree>) {
events.write(PlantTree(None));
}
fn clear_monologue(
mut nodes: Query<
(Entity, &NavState),
@ -250,6 +258,7 @@ fn clear_monologue(
});
}
// When you pointer over the '+' make the entire menu visible
fn hide_menu(mut nodes: Query<(&mut Visibility, &NavState), Changed<NavState>>) {
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(
mut events: EventReader<Pointer<Click>>,
mut commands: Commands,
query: Query<Entity, With<Tree>>,
) {
fn delete_tree(mut events: EventReader<Pointer<Click>>, mut commands: Commands, query: Query<Entity, With<Tree>>) {
events.read().for_each(|event| {
if matches!(event.event.button, PointerButton::Middle) && query.contains(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)]
#![feature(trim_prefix_suffix)]
mod debug;
mod mono;
mod debug;
use bevy::{picking::hover::HoverMap, platform::hash::RandomState};
use debug::*;
use games::*;
use mono::*;
use debug::*;
use std::hash::BuildHasher;
fn main() {

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

@ -390,12 +390,7 @@ fn toggle_physics_debug_render(
) {
let (_, config) = config_store.config_mut::<PhysicsGizmos>();
*config = match state.get() {
// TODO: Not all, don't want to hide mesh
DebuggingState::On => PhysicsGizmos::all(),
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;
// Rust stdlib
pub use std::f32::consts::PI;
pub use std::fmt::Display;
pub use std::f32::consts::PI;
// Community libraries
pub use avian3d::prelude::*;
pub use bevy::{
sprite::AlphaMode2d,
asset::{AssetLoader, LoadContext, LoadState, LoadedFolder, io::Reader},
color::palettes::css::*,
gizmos::{aabb::AabbGizmoPlugin, light::LightGizmoPlugin},
@ -29,9 +29,9 @@ pub use bevy::{
platform::collections::HashMap,
prelude::*,
reflect::TypePath,
sprite::AlphaMode2d,
window::WindowResized,
};
pub use avian3d::prelude::*;
pub use serde::Deserialize;
pub use thiserror::Error;

Loading…
Cancel
Save