diff --git a/Cargo.lock b/Cargo.lock index f26df36..8c52cf9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -588,6 +588,25 @@ dependencies = [ "glam", ] +[[package]] +name = "bevy_mod_picking" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f97d740fcd9d089a768399e902e741f45f8d671e756c939d2f1ce8ad14d63a" +dependencies = [ + "bevy", + "bevy_mod_raycast", +] + +[[package]] +name = "bevy_mod_raycast" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aead49a20f5e694f4fb59c7312f9a1813b65a2a0ac2c385d53d40f25cae896f" +dependencies = [ + "bevy", +] + [[package]] name = "bevy_pbr" version = "0.8.1" @@ -911,6 +930,7 @@ dependencies = [ "bevy", "bevy-inspector-egui", "bevy_common_assets", + "bevy_mod_picking", "bevy_rapier2d", "cpal 0.14.0", "crossbeam-channel", @@ -1562,9 +1582,9 @@ dependencies = [ [[package]] name = "erased-serde" -version = "0.3.22" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "003000e712ad0f95857bd4d2ef8d1890069e06554101697d12050668b2f6f020" +checksum = "54558e0ba96fbe24280072642eceb9d7d442e32c7ec0ea9e7ecd7b4ea2cf4e11" dependencies = [ "serde", ] diff --git a/Cargo.toml b/Cargo.toml index df12e56..9b47967 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,6 @@ edition = "2021" [dependencies] bevy = "0.8.1" bevy_common_assets = { version = "0.3.0", features = ["json"] } -bevy-inspector-egui = "0.12.1" bevy_rapier2d = "0.16.2" crossbeam-channel = "0.5.6" rand = "0.8.5" @@ -17,6 +16,8 @@ rapier2d = "0.14.0" serde = { version = "1.0.144", features = ["derive"] } [target."cfg(not(target_arch = \"wasm32\"))".dependencies] +bevy-inspector-egui = "0.12.1" +bevy_mod_picking = "0.8.2" cpal = "0.14.0" hexodsp = { git = "https://github.com/WeirdConstructor/HexoDSP", default-features = false } ticktock = "0.8.0" diff --git a/README.md b/README.md index 17112e6..70716d5 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,12 @@ This game uses [HexoDSP](https://github.com/WeirdConstructor/HexoDSP) for audio The synthetizer matrix can be edited using [HexoSynth](https://github.com/WeirdConstructor/HexoSynth) visual editor. +## Develop + +Skip to level `N: u32` with the command `bevyjam `. + +Edit the level `N: u32` with the command `bevyjam e`. + ## License GNU AGPL v3, CopyLeft 2022 Pascal Engélibert, Nixon Cheng diff --git a/build-wasm.sh b/build-wasm.sh index b9643dc..0a2d039 100644 --- a/build-wasm.sh +++ b/build-wasm.sh @@ -1,2 +1,3 @@ -cargo build --release --target wasm32-unknown-unknown -wasm-bindgen --out-name bevyjam --out-dir target --target web target/wasm32-unknown-unknown/release/bevyjam.wasm +cargo build --release --target wasm32-unknown-unknown || exit 1 +echo "==> wasm-bindgen..." +wasm-bindgen --out-name bevyjam --out-dir target --target web target/wasm32-unknown-unknown/release/bevyjam.wasm || exit 1 diff --git a/src/editor.rs b/src/editor.rs new file mode 100644 index 0000000..9d1749a --- /dev/null +++ b/src/editor.rs @@ -0,0 +1,174 @@ +use crate::{levels::stored::*, AppState}; + +use bevy::{ + input::{keyboard::KeyCode, Input}, + prelude::{ + shape::{Circle, Quad}, + *, + }, +}; +use bevy_mod_picking::*; + +pub struct EditorPlugin; + +impl Plugin for EditorPlugin { + fn build(&self, app: &mut App) { + app.add_plugins(DefaultPickingPlugins) + .add_system_set(SystemSet::on_enter(AppState::Editor).with_system(setup)) + .add_system_set( + SystemSet::on_update(AppState::Editor).with_system(keyboard_input_system), + ); + } +} + +#[derive(Component)] +struct Platform; + +#[derive(Component)] +struct Draggable; + +#[derive(Component)] +struct End; + +#[derive(Bundle)] +struct PlatformBundle { + #[bundle] + mesh: ColorMesh2dBundle, + platform: Platform, +} + +#[derive(Bundle)] +struct PlatformEndBundle { + #[bundle] + mesh: ColorMesh2dBundle, + #[bundle] + pickable: PickableBundle, + draggable: Draggable, + end: End, +} + +fn spawn_platform( + commands: &mut Commands, + meshes: &mut ResMut>, + materials: &mut ResMut>, + + transform: Transform, + size: Vec2, +) { + commands + .spawn_bundle(PlatformBundle { + mesh: ColorMesh2dBundle { + mesh: meshes.add(Mesh::from(Quad { size, flip: false })).into(), + material: materials.add(ColorMaterial::from(Color::GRAY)), + transform, + ..default() + }, + platform: Platform, + }) + .with_children(|c| { + c.spawn_bundle(PlatformEndBundle { + mesh: ColorMesh2dBundle { + mesh: meshes + .add(Mesh::from(Circle { + radius: 8., + vertices: 12, + })) + .into(), + material: materials.add(ColorMaterial::from(Color::rgba(1., 1., 0., 0.7))), + transform: Transform::from_xyz(-size.x / 2., -size.y / 2., 0.5), + ..default() + }, + pickable: PickableBundle::default(), + draggable: Draggable, + end: End, + }); + c.spawn_bundle(PlatformEndBundle { + mesh: ColorMesh2dBundle { + mesh: meshes + .add(Mesh::from(Circle { + radius: 8., + vertices: 12, + })) + .into(), + material: materials.add(ColorMaterial::from(Color::rgba(1., 1., 0., 0.7))), + transform: Transform::from_xyz(size.x / 2., size.y / 2., 0.5), + ..default() + }, + pickable: PickableBundle::default(), + draggable: Draggable, + end: End, + }); + }); +} + +pub fn spawn_stored_level( + commands: &mut Commands, + meshes: &mut ResMut>, + materials: &mut ResMut>, + asset_server: &Res, + + stored_level: &StoredLevel, +) { + let _font = asset_server.get_handle::("UacariLegacy-Thin.ttf"); + + for platform in stored_level.platforms.iter() { + spawn_platform( + commands, + meshes, + materials, + Transform::from_xyz(platform.pos.x, platform.pos.y, 0.), + platform.size, + ); + } +} + +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, + camera_query: Query>, + level_id: Res, + stored_levels_assets: Res>, + stored_levels_handle: Res>, + asset_server: Res, +) { + commands + .entity(camera_query.single()) + .insert_bundle(PickingCameraBundle::default()); + + if let Some(stored_level) = stored_levels_assets + .get(&stored_levels_handle) + .unwrap() + .levels + .get(level_id.0 .0 as usize) + { + spawn_stored_level( + &mut commands, + &mut meshes, + &mut materials, + &asset_server, + stored_level, + ); + } +} + +fn keyboard_input_system( + keyboard_input: Res>, + mut drag_query: Query<(&mut Transform, &Selection), With>, +) { + let drag = 8. + * Vec3 { + x: (keyboard_input.pressed(KeyCode::Right) as i8 + - keyboard_input.pressed(KeyCode::Left) as i8) as _, + y: (keyboard_input.pressed(KeyCode::Up) as i8 + - keyboard_input.pressed(KeyCode::Down) as i8) as _, + z: 0., + }; + if drag != Vec3::ZERO { + for (mut transform, selection) in drag_query.iter_mut() { + if selection.selected() { + transform.translation += drag; + } + } + } +} diff --git a/src/levels.rs b/src/levels.rs index c131e63..f99b429 100644 --- a/src/levels.rs +++ b/src/levels.rs @@ -1,5 +1,7 @@ #![allow(clippy::too_many_arguments)] +pub use stored::*; + use crate::game::*; use bevy::{prelude::*, reflect::TypeUuid}; @@ -55,60 +57,6 @@ pub fn post_setup_level( } } -#[derive(Deserialize, Serialize, TypeUuid)] -#[uuid = "1fbba930-644b-0d62-2514-4b302b945327"] -pub struct StoredLevels { - levels: Vec, -} - -#[derive(Deserialize, Serialize, TypeUuid)] -#[uuid = "a1464a30-1f57-a654-d56c-ded41032af0b"] -pub struct StoredLevel { - pub comment: String, - pub characters: Vec, - pub platforms: Vec, - pub absorbing_filters: Vec, - pub rotating_filters: Vec, - pub texts: Vec, -} - -#[derive(Deserialize, Serialize, TypeUuid)] -#[uuid = "1c798f8c-ef15-c528-693e-76becdef6b10"] -pub struct StoredCharacter { - pub pos: Vec2, - pub color: Vec4, -} - -#[derive(Deserialize, Serialize, TypeUuid)] -#[uuid = "31696095-59de-93be-b5e9-333c2afbc900"] -pub struct StoredPlatform { - pub pos: Vec2, - pub size: Vec2, -} - -#[derive(Deserialize, Serialize, TypeUuid)] -#[uuid = "bcad7fff-0605-c4e3-3cd4-42d5bbaad926"] -pub struct StoredAbsorbingFilter { - pub pos: Vec2, - pub size: Vec2, - pub color: Vec4, -} - -#[derive(Deserialize, Serialize, TypeUuid)] -#[uuid = "fa2843f2-6e34-601b-6c46-4827b0370b3f"] -pub struct StoredRotatingFilter { - pub pos: Vec2, - pub angle: f32, -} - -#[derive(Deserialize, Serialize, TypeUuid)] -#[uuid = "72f6321a-f01f-6eea-9b17-3159837a2fd3"] -pub struct StoredText { - pub pos: Vec2, - pub font_size: f32, - pub text: String, -} - pub fn spawn_stored_level( commands: &mut Commands, character_meshes: &Res, @@ -179,3 +127,61 @@ pub fn spawn_stored_level( .insert(Level); } } + +pub mod stored { + use super::*; + + #[derive(Deserialize, Serialize, TypeUuid)] + #[uuid = "1fbba930-644b-0d62-2514-4b302b945327"] + pub struct StoredLevels { + pub levels: Vec, + } + + #[derive(Deserialize, Serialize, TypeUuid)] + #[uuid = "a1464a30-1f57-a654-d56c-ded41032af0b"] + pub struct StoredLevel { + pub comment: String, + pub characters: Vec, + pub platforms: Vec, + pub absorbing_filters: Vec, + pub rotating_filters: Vec, + pub texts: Vec, + } + + #[derive(Deserialize, Serialize, TypeUuid)] + #[uuid = "1c798f8c-ef15-c528-693e-76becdef6b10"] + pub struct StoredCharacter { + pub pos: Vec2, + pub color: Vec4, + } + + #[derive(Deserialize, Serialize, TypeUuid)] + #[uuid = "31696095-59de-93be-b5e9-333c2afbc900"] + pub struct StoredPlatform { + pub pos: Vec2, + pub size: Vec2, + } + + #[derive(Deserialize, Serialize, TypeUuid)] + #[uuid = "bcad7fff-0605-c4e3-3cd4-42d5bbaad926"] + pub struct StoredAbsorbingFilter { + pub pos: Vec2, + pub size: Vec2, + pub color: Vec4, + } + + #[derive(Deserialize, Serialize, TypeUuid)] + #[uuid = "fa2843f2-6e34-601b-6c46-4827b0370b3f"] + pub struct StoredRotatingFilter { + pub pos: Vec2, + pub angle: f32, + } + + #[derive(Deserialize, Serialize, TypeUuid)] + #[uuid = "72f6321a-f01f-6eea-9b17-3159837a2fd3"] + pub struct StoredText { + pub pos: Vec2, + pub font_size: f32, + pub text: String, + } +} diff --git a/src/main.rs b/src/main.rs index 410492a..7976caa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,9 @@ +#![allow(clippy::too_many_arguments)] + #[cfg(not(target_arch = "wasm32"))] mod audio; +#[cfg(not(target_arch = "wasm32"))] +mod editor; mod filters; mod game; mod levels; @@ -7,6 +11,7 @@ mod menu; mod particle_effect; use bevy::{ + asset::{Asset, HandleId, LoadState}, prelude::*, window::{WindowId, WindowMode}, }; @@ -15,9 +20,22 @@ use bevy_rapier2d::prelude::*; #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] enum AppState { + Loading, Menu, Game, Win, + Editor, +} + +struct UseEditor(bool); + +struct LoadingAssets(Vec); + +impl LoadingAssets { + fn add(&mut self, handle: Handle) -> Handle { + self.0.push(handle.id); + handle + } } fn main() { @@ -28,32 +46,45 @@ fn main() { std::thread::spawn(move || audio::setup(audio_event_receiver)); #[cfg(not(target_arch = "wasm32"))] - let first_level = game::LevelId( - std::env::args() - .nth(1) - .map_or(0, |s| s.parse().unwrap_or(0)), - ); + let (first_level, use_editor) = { + let mut args = std::env::args().skip(1); + ( + game::LevelId(args.next().map_or(0, |s| s.parse().unwrap_or(0))), + args.next().map_or(false, |s| s == "e"), + ) + }; #[cfg(target_arch = "wasm32")] - let first_level = game::LevelId(0); + let (first_level, use_editor) = (game::LevelId(0), false); - App::new() - .insert_resource(Msaa { samples: 4 }) + let mut app = App::new(); + app.insert_resource(Msaa { samples: 4 }) .insert_resource(audio_event_sender) - .add_state(AppState::Menu) + .insert_resource(UseEditor(use_editor)) + .add_state(AppState::Loading) .insert_resource(game::FirstLevel(first_level)) .insert_resource(ClearColor(Color::BLACK)) .add_plugins(DefaultPlugins) + //.add_plugin(RapierDebugRenderPlugin::default()) + //.add_plugin(bevy_inspector_egui::WorldInspectorPlugin::new()) .add_plugin(JsonAssetPlugin::::new(&[ "levels.json", - ])) - .add_plugin(RapierPhysicsPlugin::::pixels_per_meter(64.0)) - //.add_plugin(RapierDebugRenderPlugin::default()) - .add_plugin(menu::MenuPlugin) - .add_plugin(game::GamePlugin) - .add_plugin(particle_effect::ParticleEffectPlugin) - //.add_plugin(bevy_inspector_egui::WorldInspectorPlugin::new()) - .add_system(keyboard_util_system) + ])); + + if !use_editor { + app.add_plugin(RapierPhysicsPlugin::::pixels_per_meter(64.0)) + .add_plugin(menu::MenuPlugin) + .add_plugin(game::GamePlugin) + .add_plugin(particle_effect::ParticleEffectPlugin); + } + + #[cfg(not(target_arch = "wasm32"))] + if use_editor { + app.add_plugin(editor::EditorPlugin); + } + + app.add_system(keyboard_util_system) .add_startup_system(setup) + .add_system_set(SystemSet::on_update(AppState::Loading).with_system(loading_system)) .run(); } @@ -63,9 +94,13 @@ fn setup(mut commands: Commands, mut windows: ResMut, asset_server: Res .unwrap() .set_title(String::from("Bevyjam")); - commands.insert_resource(asset_server.load::("game.levels.json")); - commands.insert_resource(asset_server.load::("UacariLegacy-Thin.ttf")); - commands.insert_resource(asset_server.load::("bevy.png")); + let mut assets = LoadingAssets(Vec::new()); + commands.insert_resource( + assets.add(asset_server.load::("game.levels.json")), + ); + commands.insert_resource(assets.add(asset_server.load::("UacariLegacy-Thin.ttf"))); + commands.insert_resource(assets.add(asset_server.load::("bevy.png"))); + commands.insert_resource(assets); commands.spawn_bundle(Camera2dBundle::default()); commands.insert_resource(AmbientLight { @@ -74,6 +109,23 @@ fn setup(mut commands: Commands, mut windows: ResMut, asset_server: Res }); } +fn loading_system( + asset_server: Res, + use_editor: Res, + assets: Res, + mut app_state: ResMut>, +) { + if asset_server.get_group_load_state(assets.0.iter().copied()) == LoadState::Loaded { + app_state + .replace(if use_editor.0 { + AppState::Editor + } else { + AppState::Menu + }) + .ok(); + } +} + fn keyboard_util_system(keyboard_input: Res>, mut windows: ResMut) { #[cfg(not(target_arch = "wasm32"))] { diff --git a/src/particle_effect.rs b/src/particle_effect.rs index c1ee094..b711548 100644 --- a/src/particle_effect.rs +++ b/src/particle_effect.rs @@ -44,7 +44,6 @@ impl FromWorld for ParticleMesh { } } -#[derive(bevy_inspector_egui::Inspectable)] pub struct ParticleEffectResource { pub translation: Vec3, pub prev_translation: Vec3,