From 8d891e74d0d79b85c3f68b18ef97237a8f5a783d Mon Sep 17 00:00:00 2001 From: Elijah Voigt Date: Mon, 28 Jul 2025 10:40:49 -0700 Subject: [PATCH] Break some components out into 2d/3d variants for import --- Cargo.lock | 74 +++++++++++++++++++++++++ Cargo.toml | 3 + examples/sensors.rs | 2 +- src/base_game.rs | 31 +++++++++-- src/bin/flappy/main.rs | 122 ++++++++++++++++++++++------------------- src/bin/hum/main.rs | 5 +- src/bin/tetris/main.rs | 1 + src/bin/trees/main.rs | 3 +- src/debug.rs | 17 ------ src/lib.rs | 5 +- src/physics2d.rs | 26 +++++++++ src/physics3d.rs | 27 +++++++++ 12 files changed, 231 insertions(+), 85 deletions(-) create mode 100644 src/physics2d.rs create mode 100644 src/physics3d.rs diff --git a/Cargo.lock b/Cargo.lock index c6bd730..6aedcab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -322,6 +322,27 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "avian2d" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7133547d9cc068d527d91fdcb8c8017c89b26ce05dbd30daef9f1ca64824495d" +dependencies = [ + "arrayvec", + "avian_derive", + "bevy", + "bevy_heavy", + "bevy_math", + "bevy_transform_interpolation", + "bitflags 2.9.1", + "derive_more", + "itertools 0.13.0", + "nalgebra", + "parry2d", + "parry2d-f64", + "thread_local", +] + [[package]] name = "avian3d" version = "0.3.1" @@ -2248,6 +2269,7 @@ dependencies = [ name = "games" version = "0.1.0" dependencies = [ + "avian2d", "avian3d", "bevy", "chrono", @@ -3526,6 +3548,58 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "parry2d" +version = "0.17.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87edd53b1639e011e4765eecfceb0fa2c486da696dcdcfbc9a38bfc3574fb7e0" +dependencies = [ + "approx", + "arrayvec", + "bitflags 2.9.1", + "downcast-rs 1.2.1", + "either", + "ena", + "log", + "nalgebra", + "num-derive", + "num-traits", + "ordered-float", + "rayon", + "rustc-hash 2.1.1", + "simba", + "slab", + "smallvec", + "spade", + "thiserror 1.0.69", +] + +[[package]] +name = "parry2d-f64" +version = "0.17.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42dadff562001ff51eed809d7c75ac6f185d8cffc575d7b45a8bdc6ea6f1bf30" +dependencies = [ + "approx", + "arrayvec", + "bitflags 2.9.1", + "downcast-rs 1.2.1", + "either", + "ena", + "log", + "nalgebra", + "num-derive", + "num-traits", + "ordered-float", + "rayon", + "rustc-hash 2.1.1", + "simba", + "slab", + "smallvec", + "spade", + "thiserror 1.0.69", +] + [[package]] name = "parry3d" version = "0.17.6" diff --git a/Cargo.toml b/Cargo.toml index 5ef10f3..22e0551 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,9 @@ features = ["derive"] [dependencies.avian3d] version = "0.3.1" +[dependencies.avian2d] +version = "0.3.1" + [dependencies.bevy] version = "0.16.1" features = ["wayland", "dynamic_linking"] diff --git a/examples/sensors.rs b/examples/sensors.rs index 709d92b..1a7806f 100644 --- a/examples/sensors.rs +++ b/examples/sensors.rs @@ -18,7 +18,7 @@ fn main() { ( setup_physics_scene, setup_ui, - position_camera.after(setup_camera), + position_camera.after(create_camera_3d), ), ) .add_systems( diff --git a/src/base_game.rs b/src/base_game.rs index a9e3ed8..2a60c77 100644 --- a/src/base_game.rs +++ b/src/base_game.rs @@ -3,12 +3,19 @@ use super::*; /// A good starting place for creating a game building on top of the base Bevy app pub struct BaseGamePlugin { pub name: String, + pub game_type: GameType, +} + +pub enum GameType { + Three, + Two, } impl Default for BaseGamePlugin { fn default() -> Self { BaseGamePlugin { name: "mygame".into(), + game_type: GameType::Three, } } } @@ -25,10 +32,13 @@ impl Plugin for BaseGamePlugin { })) .add_plugins(DebuggingPlugin) .add_plugins(MeshPickingPlugin) - .add_plugins(PhysicsPlugins::default()) .add_plugins(LoadingPlugin) - .add_plugins(BaseUiPlugin) - .add_systems(Startup, setup_camera); + .add_plugins(BaseUiPlugin); + + match self.game_type { + GameType::Two => app.add_systems(Startup, create_camera_2d), + GameType::Three => app.add_systems(Startup, create_camera_3d), + }; } } @@ -47,7 +57,16 @@ pub fn toggle_state_visibility( }); } -// TODO: Rename to "create camera" -pub fn setup_camera(mut commands: Commands) { - commands.spawn((Camera3d { ..default() }, Camera { ..default() }, AmbientLight::default())); +pub fn create_camera_3d(mut commands: Commands) { + // 3d camera + commands.spawn(( + Camera3d { ..default() }, + Camera { ..default() }, + AmbientLight::default(), + )); +} + +pub fn create_camera_2d(mut commands: Commands) { + // 2d camera + commands.spawn((Camera2d, Camera { ..default() })); } diff --git a/src/bin/flappy/main.rs b/src/bin/flappy/main.rs index e94e5dc..affcc82 100644 --- a/src/bin/flappy/main.rs +++ b/src/bin/flappy/main.rs @@ -1,16 +1,28 @@ // Bevy basically forces "complex types" with Querys #![allow(clippy::type_complexity)] -use bevy::input::common_conditions::input_just_released; +use games::physics3d::*; use games::*; fn main() { App::new() - .add_plugins(BaseGamePlugin { - name: "flappy bird (with rewind)".into(), - }) + .add_plugins(( + BaseGamePlugin { + name: "flappy bird (with rewind)".into(), + ..default() + }, + Physics3dPlugin, + )) .init_state::() - .add_systems(Startup, (init_bird, init_obstacles, init_ui, tweak_camera.after(setup_camera))) + .add_systems( + Startup, + ( + init_bird, + init_obstacles, + init_ui, + tweak_camera.after(create_camera_3d), + ), + ) .add_systems(OnEnter(PlayerState::Alive), alive_bird) .add_systems(OnEnter(PlayerState::Pause), pause_bird) .add_systems(OnEnter(PlayerState::Stasis), pause_bird) @@ -24,7 +36,8 @@ fn main() { debug_state_changes::, // Toggle (UI) elements when the player dies/alives toggle_state_visibility::, - ).run_if(state_changed::), + ) + .run_if(state_changed::), // 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)), @@ -47,16 +60,15 @@ fn main() { // Pause when the player presses Escape pause_game.run_if(input_just_pressed(KeyCode::Escape)), // Transition out of the pause screen if the player presses space - un_pause_game.run_if(input_just_pressed(KeyCode::Space)) + un_pause_game + .run_if(input_just_pressed(KeyCode::Space)) .run_if(in_state(PlayerState::Pause)), ), ) .run(); } -fn tweak_camera( - mut camera: Query<(&mut Camera, &mut AmbientLight, &mut Transform), With>, -) { +fn tweak_camera(mut camera: Query<(&mut Camera, &mut AmbientLight, &mut Transform), With>) { camera.iter_mut().for_each(|(mut c, mut al, mut t)| { c.clear_color = ClearColorConfig::Custom(WHITE.into()); al.brightness = 100.0; @@ -74,7 +86,7 @@ enum PlayerState { Rewind, Stasis, #[default] - Pause + Pause, } // A tape tracking the bird's state every frame @@ -105,7 +117,8 @@ fn init_bird( let physics = ( RigidBody::Static, - Collider::capsule(1.0, 1.0), Mass(1.0), + Collider::capsule(1.0, 1.0), + Mass(1.0), ExternalForce::new(Vec3::X * 1.0).with_persistence(true), ExternalImpulse::default().with_persistence(false), LockedAxes::ROTATION_LOCKED.lock_translation_z(), @@ -151,7 +164,13 @@ fn init_obstacles( let physics = (RigidBody::Static, Collider::cuboid(1.0, 3.0, 1.0)); let name = Name::new("pipe"); - (name.clone(), mesh.clone(), material.clone(), physics.clone(), Pipe) + ( + name.clone(), + mesh.clone(), + material.clone(), + physics.clone(), + Pipe, + ) }; // TODO: Instead of spawning infinite floor/pipes, we should instead spawn enough for 1-3 a few @@ -168,12 +187,9 @@ fn init_obstacles( commands.spawn((pipe.clone(), below)); commands.spawn((ground.clone(), floor)); }); - } -fn init_ui( - mut commands: Commands, -) { +fn init_ui(mut commands: Commands) { commands.spawn(( Node { align_self: AlignSelf::Center, @@ -182,30 +198,27 @@ fn init_ui( ..default() }, PlayerState::Stasis, - children![ - Text::new("You Died"), - Text::new("Press R to Rewind"), - ], + children![Text::new("You Died"), Text::new("Press R to Rewind"),], )); fn start_game(_trigger: Trigger>, mut next: ResMut>) { next.set(PlayerState::Alive); } - commands.spawn(( - Node { - align_self: AlignSelf::Center, - justify_self: JustifySelf::Center, - flex_direction: FlexDirection::Column, - ..default() - }, - Button, - // TODO: Add Pause (basically Stasis) state - PlayerState::Pause, - children![ - Text::new("Go!"), - ], - )).observe(start_game); + commands + .spawn(( + Node { + align_self: AlignSelf::Center, + justify_self: JustifySelf::Center, + flex_direction: FlexDirection::Column, + ..default() + }, + Button, + // TODO: Add Pause (basically Stasis) state + PlayerState::Pause, + children![Text::new("Go!"),], + )) + .observe(start_game); fn start_rewind(trigger: Trigger>, mut next: ResMut>) { next.set(PlayerState::Rewind); @@ -215,30 +228,27 @@ fn init_ui( next.set(PlayerState::Alive); } - commands.spawn(( - Node { - align_self: AlignSelf::End, - justify_self: JustifySelf::Center, - flex_direction: FlexDirection::Column, - ..default() - }, - Button, - children![ - Text::new("Rewind!"), - ], - )).observe(start_rewind).observe(end_rewind); + commands + .spawn(( + Node { + align_self: AlignSelf::End, + justify_self: JustifySelf::Center, + flex_direction: FlexDirection::Column, + ..default() + }, + Button, + children![Text::new("Rewind!"),], + )) + .observe(start_rewind) + .observe(end_rewind); } /// Pause the game when the player presses "Escape" -fn pause_game( - mut next: ResMut>, -) { +fn pause_game(mut next: ResMut>) { next.set(PlayerState::Pause); } -fn un_pause_game( - mut next: ResMut>, -) { +fn un_pause_game(mut next: ResMut>) { next.set(PlayerState::Alive); } @@ -326,9 +336,7 @@ fn detect_dead( "Only check if dead while alive" ); - if obstacles.iter().any(|obstacle| { - bird.intersects(obstacle) - }) { + if obstacles.iter().any(|obstacle| bird.intersects(obstacle)) { next.set(PlayerState::Stasis); } } @@ -353,7 +361,7 @@ fn pause_bird( fn camera_follow_bird( bird: Single<&Transform, (With, Changed)>, - mut camera: Single<&mut Transform, (With, Without)> + mut camera: Single<&mut Transform, (With, Without)>, ) { camera.translation.x = bird.translation.x; } diff --git a/src/bin/hum/main.rs b/src/bin/hum/main.rs index 6be0ce8..4ea5bf0 100644 --- a/src/bin/hum/main.rs +++ b/src/bin/hum/main.rs @@ -2,6 +2,9 @@ use games::*; fn main() { App::new() - .add_plugins(BaseGamePlugin { name: "hum".into() }) + .add_plugins(BaseGamePlugin { + name: "hum".into(), + ..default() + }) .run(); } diff --git a/src/bin/tetris/main.rs b/src/bin/tetris/main.rs index baa1daf..21e8ec7 100644 --- a/src/bin/tetris/main.rs +++ b/src/bin/tetris/main.rs @@ -4,6 +4,7 @@ fn main() { App::new() .add_plugins(BaseGamePlugin { name: "tetris-rpg".into(), + ..default() }) .run(); } diff --git a/src/bin/trees/main.rs b/src/bin/trees/main.rs index 92c0798..b386ef3 100644 --- a/src/bin/trees/main.rs +++ b/src/bin/trees/main.rs @@ -16,6 +16,7 @@ fn main() { App::new() .add_plugins(BaseGamePlugin { name: "trees".into(), + ..default() }) .add_plugins(MonologueAssetsPlugin) .add_plugins(TreesDebugPlugin) @@ -30,7 +31,7 @@ fn main() { init_trees, init_ui, load_monologues, - position_camera.after(setup_camera), + position_camera.after(create_camera_3d), ), ) // When we're done loading, plant forest diff --git a/src/debug.rs b/src/debug.rs index 351397c..a710e90 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -12,7 +12,6 @@ impl Plugin for DebuggingPlugin { .init_resource::() .init_resource::() .init_resource::() - .add_plugins(PhysicsDebugPlugin::default()) .add_systems(Startup, init_debug_ui) .add_systems( Update, @@ -44,10 +43,6 @@ impl Plugin for DebuggingPlugin { global: false, default_color: MAGENTA.into(), }) - .add_systems( - Update, - toggle_physics_debug_render.run_if(state_changed::), - ) .add_systems(OnEnter(DebuggingState::On), enable_wireframe) .add_systems(OnExit(DebuggingState::On), disable_wireframe); } @@ -384,18 +379,6 @@ fn close_on_click(trigger: Trigger>, mut query: Query<&mut Visibi } } -fn toggle_physics_debug_render( - state: Res>, - mut config_store: ResMut, -) { - let (_, config) = config_store.config_mut::(); - *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: Res>) { info!("State changed: {:?}", s); } diff --git a/src/lib.rs b/src/lib.rs index 9830614..c26b2d2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,8 @@ mod base_game; mod debug; mod loading; +pub mod physics2d; +pub mod physics3d; mod scheduling; mod ui; mod version; @@ -13,14 +15,13 @@ pub use std::f32::consts::PI; pub use std::fmt::Display; // Community libraries -pub use avian3d::prelude::*; pub use bevy::{ asset::{AssetLoader, LoadContext, LoadState, LoadedFolder, io::Reader}, color::palettes::css::*, gizmos::{aabb::AabbGizmoPlugin, light::LightGizmoPlugin}, input::{ ButtonState, - common_conditions::{input_just_pressed, input_pressed}, + common_conditions::{input_just_pressed, input_just_released, input_pressed}, keyboard::KeyboardInput, mouse::MouseMotion, mouse::{MouseScrollUnit, MouseWheel}, diff --git a/src/physics2d.rs b/src/physics2d.rs new file mode 100644 index 0000000..1fdde13 --- /dev/null +++ b/src/physics2d.rs @@ -0,0 +1,26 @@ +use super::*; +pub use avian2d::prelude::*; + +/// 2D Physics systems, resources, events, etc. +pub struct Physics2dPlugin; + +impl Plugin for Physics2dPlugin { + fn build(&self, app: &mut App) { + app.add_plugins(PhysicsDebugPlugin::default()).add_systems( + Update, + toggle_physics_debug_render.run_if(state_changed::), + ); + } +} + +fn toggle_physics_debug_render( + state: Res>, + mut config_store: ResMut, +) { + let (_, config) = config_store.config_mut::(); + *config = match state.get() { + // TODO: Not all, don't want to hide mesh + DebuggingState::On => PhysicsGizmos::all(), + DebuggingState::Off => PhysicsGizmos::none(), + }; +} diff --git a/src/physics3d.rs b/src/physics3d.rs new file mode 100644 index 0000000..ff7a2c4 --- /dev/null +++ b/src/physics3d.rs @@ -0,0 +1,27 @@ +use super::*; +pub use avian3d::prelude::*; + +/// 3D Physics systems, resources, events, etc. +pub struct Physics3dPlugin; + +impl Plugin for Physics3dPlugin { + fn build(&self, app: &mut App) { + app.add_plugins((PhysicsDebugPlugin::default(), PhysicsPlugins::default())) + .add_systems( + Update, + toggle_physics_debug_render.run_if(state_changed::), + ); + } +} + +fn toggle_physics_debug_render( + state: Res>, + mut config_store: ResMut, +) { + let (_, config) = config_store.config_mut::(); + *config = match state.get() { + // TODO: Not all, don't want to hide mesh + DebuggingState::On => PhysicsGizmos::all(), + DebuggingState::Off => PhysicsGizmos::none(), + }; +}