From 09138229ca9554b2e86586368ace1182ad610a84 Mon Sep 17 00:00:00 2001 From: tuxmain Date: Fri, 26 Aug 2022 00:33:44 +0200 Subject: [PATCH 1/8] Editor: first steps --- Cargo.lock | 24 +++++- Cargo.toml | 3 +- README.md | 6 ++ build-wasm.sh | 5 +- src/editor.rs | 174 +++++++++++++++++++++++++++++++++++++++++ src/levels.rs | 114 ++++++++++++++------------- src/main.rs | 92 +++++++++++++++++----- src/particle_effect.rs | 1 - 8 files changed, 339 insertions(+), 80 deletions(-) create mode 100644 src/editor.rs 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, From 02b29bd093dfefd4609099b602ce13346cf68f3b Mon Sep 17 00:00:00 2001 From: tuxmain Date: Fri, 26 Aug 2022 10:05:32 +0200 Subject: [PATCH 2/8] editor: move platforms --- src/editor.rs | 114 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 92 insertions(+), 22 deletions(-) diff --git a/src/editor.rs b/src/editor.rs index 9d1749a..08ebb43 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -1,3 +1,4 @@ +#![allow(clippy::type_complexity)] use crate::{levels::stored::*, AppState}; use bevy::{ @@ -6,6 +7,7 @@ use bevy::{ shape::{Circle, Quad}, *, }, + sprite::Mesh2dHandle, }; use bevy_mod_picking::*; @@ -13,28 +15,39 @@ pub struct EditorPlugin; impl Plugin for EditorPlugin { fn build(&self, app: &mut App) { - app.add_plugins(DefaultPickingPlugins) + app.add_event::() + .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), + SystemSet::on_update(AppState::Editor) + .with_system(keyboard_input_system) + .with_system(follow_ends_system), ); } } +// Events + +struct DragEndEvent(Entity); + +// Components + #[derive(Component)] -struct Platform; +struct Platform(Entity, Entity); #[derive(Component)] struct Draggable; #[derive(Component)] -struct End; +struct End(Entity); + +// Bundles #[derive(Bundle)] struct PlatformBundle { #[bundle] mesh: ColorMesh2dBundle, - platform: Platform, + //platform: Platform, } #[derive(Bundle)] @@ -47,6 +60,8 @@ struct PlatformEndBundle { end: End, } +// Functions + fn spawn_platform( commands: &mut Commands, meshes: &mut ResMut>, @@ -55,7 +70,7 @@ fn spawn_platform( transform: Transform, size: Vec2, ) { - commands + let platform = commands .spawn_bundle(PlatformBundle { mesh: ColorMesh2dBundle { mesh: meshes.add(Mesh::from(Quad { size, flip: false })).into(), @@ -63,10 +78,11 @@ fn spawn_platform( transform, ..default() }, - platform: Platform, }) - .with_children(|c| { - c.spawn_bundle(PlatformEndBundle { + .id(); + let ends = Platform( + commands + .spawn_bundle(PlatformEndBundle { mesh: ColorMesh2dBundle { mesh: meshes .add(Mesh::from(Circle { @@ -75,14 +91,20 @@ fn spawn_platform( })) .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), + transform: Transform::from_xyz( + transform.translation.x - size.x / 2., + transform.translation.y - size.y / 2., + 0.5, + ), ..default() }, pickable: PickableBundle::default(), draggable: Draggable, - end: End, - }); - c.spawn_bundle(PlatformEndBundle { + end: End(platform), + }) + .id(), + commands + .spawn_bundle(PlatformEndBundle { mesh: ColorMesh2dBundle { mesh: meshes .add(Mesh::from(Circle { @@ -91,14 +113,20 @@ fn spawn_platform( })) .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), + transform: Transform::from_xyz( + transform.translation.x + size.x / 2., + transform.translation.y + size.y / 2., + 0.5, + ), ..default() }, pickable: PickableBundle::default(), draggable: Draggable, - end: End, - }); - }); + end: End(platform), + }) + .id(), + ); + commands.entity(platform).insert(ends); } pub fn spawn_stored_level( @@ -122,6 +150,8 @@ pub fn spawn_stored_level( } } +// Systems + fn setup( mut commands: Commands, mut meshes: ResMut>, @@ -154,20 +184,60 @@ fn setup( fn keyboard_input_system( keyboard_input: Res>, - mut drag_query: Query<(&mut Transform, &Selection), With>, + mut drag_query: Query<(&mut Transform, &Selection, Option<&End>), With>, + mut drag_end_event: EventWriter, ) { - let drag = 8. - * Vec3 { + let drag = if keyboard_input.pressed(KeyCode::LShift) || keyboard_input.pressed(KeyCode::RShift) + { + 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., - }; + } + } else { + Vec3 { + x: (keyboard_input.just_pressed(KeyCode::Right) as i8 + - keyboard_input.just_pressed(KeyCode::Left) as i8) as _, + y: (keyboard_input.just_pressed(KeyCode::Up) as i8 + - keyboard_input.just_pressed(KeyCode::Down) as i8) as _, + z: 0., + } + } * 8.; if drag != Vec3::ZERO { - for (mut transform, selection) in drag_query.iter_mut() { + for (mut transform, selection, end) in drag_query.iter_mut() { if selection.selected() { transform.translation += drag; + if let Some(End(entity)) = end { + drag_end_event.send(DragEndEvent(*entity)); + } + } + } + } +} + +fn follow_ends_system( + mut meshes: ResMut>, + mut platform_query: Query<(&mut Transform, &mut Mesh2dHandle, &Platform)>, + end_query: Query<&Transform, Without>, + mut drag_end_event: EventReader, +) { + for DragEndEvent(entity) in drag_end_event.iter() { + if let Ok((mut transform, mut mesh, Platform(end1, end2))) = platform_query.get_mut(*entity) + { + if let (Ok(end1), Ok(end2)) = (end_query.get(*end1), end_query.get(*end2)) { + transform.translation.x = (end1.translation.x + end2.translation.x) / 2.; + transform.translation.y = (end1.translation.y + end2.translation.y) / 2.; + *mesh = meshes + .add(Mesh::from(Quad { + size: Vec2 { + x: (end2.translation.x - end1.translation.x).abs(), + y: (end2.translation.y - end1.translation.y).abs(), + }, + flip: false, + })) + .into(); } } } From 5d2289d1c0eeab5610d10a7af35dbbb6fbb0474a Mon Sep 17 00:00:00 2001 From: tuxmain Date: Fri, 26 Aug 2022 10:16:40 +0200 Subject: [PATCH 3/8] editor: move camera --- README.md | 6 ++++++ src/editor.rs | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/README.md b/README.md index 70716d5..c29d372 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,12 @@ Skip to level `N: u32` with the command `bevyjam `. Edit the level `N: u32` with the command `bevyjam e`. +### Editor controls + +* **Select handles**: left click to select, click in void to deselect, CTRL+click to select many, CTRL+A to select all +* **Move handles**: arrows to move one step, Shift+arrows to move continuously +* **Move camera**: CTRL+arrows + ## License GNU AGPL v3, CopyLeft 2022 Pascal Engélibert, Nixon Cheng diff --git a/src/editor.rs b/src/editor.rs index 08ebb43..5404140 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -184,9 +184,23 @@ fn setup( fn keyboard_input_system( keyboard_input: Res>, + mut camera_query: Query<&mut Transform, (With, Without)>, mut drag_query: Query<(&mut Transform, &Selection, Option<&End>), With>, mut drag_end_event: EventWriter, ) { + if keyboard_input.pressed(KeyCode::LControl) || keyboard_input.pressed(KeyCode::RControl) { + let mut transform = camera_query.single_mut(); + let drag = 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., + } * 8.; + transform.translation += drag; + return; + } + let drag = if keyboard_input.pressed(KeyCode::LShift) || keyboard_input.pressed(KeyCode::RShift) { Vec3 { From da5f9b0820a15de142b8cd7106c69825a7c9807b Mon Sep 17 00:00:00 2001 From: tuxmain Date: Fri, 26 Aug 2022 12:00:36 +0200 Subject: [PATCH 4/8] editor: characters --- src/editor.rs | 74 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 69 insertions(+), 5 deletions(-) diff --git a/src/editor.rs b/src/editor.rs index 5404140..382b585 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -20,7 +20,7 @@ impl Plugin for EditorPlugin { .add_system_set(SystemSet::on_enter(AppState::Editor).with_system(setup)) .add_system_set( SystemSet::on_update(AppState::Editor) - .with_system(keyboard_input_system) + .with_system(move_system) .with_system(follow_ends_system), ); } @@ -30,6 +30,8 @@ impl Plugin for EditorPlugin { struct DragEndEvent(Entity); +// Resources + // Components #[derive(Component)] @@ -47,7 +49,6 @@ struct End(Entity); struct PlatformBundle { #[bundle] mesh: ColorMesh2dBundle, - //platform: Platform, } #[derive(Bundle)] @@ -60,6 +61,15 @@ struct PlatformEndBundle { end: End, } +#[derive(Bundle)] +struct CharacterBundle { + #[bundle] + mesh: ColorMesh2dBundle, + #[bundle] + pickable: PickableBundle, + draggable: Draggable, +} + // Functions fn spawn_platform( @@ -129,6 +139,50 @@ fn spawn_platform( commands.entity(platform).insert(ends); } +fn spawn_character( + commands: &mut Commands, + meshes: &mut ResMut>, + materials: &mut ResMut>, + asset_server: &Res, + + transform: Transform, + color: Color, + index: usize, +) { + let font = asset_server.get_handle("UacariLegacy-Thin.ttf"); + commands + .spawn_bundle(CharacterBundle { + mesh: ColorMesh2dBundle { + mesh: meshes + .add(Mesh::from(Quad { + size: Vec2 { x: 64., y: 64. }, + flip: false, + })) + .into(), + material: materials.add(ColorMaterial::from(color)), + transform, + ..default() + }, + pickable: PickableBundle::default(), + draggable: Draggable, + }) + .with_children(|c| { + c.spawn_bundle(Text2dBundle { + text: Text::from_section( + &index.to_string(), + TextStyle { + font: font.clone(), + font_size: 32., + color: Color::WHITE, + }, + ) + .with_alignment(TextAlignment::CENTER), + transform: Transform::from_xyz(0., 0., 1.), + ..Default::default() + }); + }); +} + pub fn spawn_stored_level( commands: &mut Commands, meshes: &mut ResMut>, @@ -137,8 +191,6 @@ pub fn spawn_stored_level( stored_level: &StoredLevel, ) { - let _font = asset_server.get_handle::("UacariLegacy-Thin.ttf"); - for platform in stored_level.platforms.iter() { spawn_platform( commands, @@ -148,6 +200,18 @@ pub fn spawn_stored_level( platform.size, ); } + + for (i, character) in stored_level.characters.iter().enumerate() { + spawn_character( + commands, + meshes, + materials, + asset_server, + Transform::from_xyz(character.pos.x, character.pos.y, 0.), + character.color.into(), + i, + ); + } } // Systems @@ -182,7 +246,7 @@ fn setup( } } -fn keyboard_input_system( +fn move_system( keyboard_input: Res>, mut camera_query: Query<&mut Transform, (With, Without)>, mut drag_query: Query<(&mut Transform, &Selection, Option<&End>), With>, From 7d2c68a0556114f304e66ba63c7cc7e42be95a15 Mon Sep 17 00:00:00 2001 From: tuxmain Date: Fri, 26 Aug 2022 14:57:53 +0200 Subject: [PATCH 5/8] editor: save --- Cargo.lock | 1 + Cargo.toml | 1 + README.md | 5 +- assets/game.levels.json | 461 ++++++++++++++++++++++++++++++---------- src/editor.rs | 123 +++++++++-- 5 files changed, 458 insertions(+), 133 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8c52cf9..91bb56d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -939,6 +939,7 @@ dependencies = [ "rand_distr", "rapier2d", "serde", + "serde_json", "ticktock", ] diff --git a/Cargo.toml b/Cargo.toml index 9b47967..22905a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ 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 } +serde_json = "1.0.85" ticktock = "0.8.0" [target."cfg(target_arch = \"wasm32\")".dependencies] diff --git a/README.md b/README.md index c29d372..c452b76 100644 --- a/README.md +++ b/README.md @@ -53,9 +53,10 @@ Edit the level `N: u32` with the command `bevyjam e`. ### Editor controls -* **Select handles**: left click to select, click in void to deselect, CTRL+click to select many, CTRL+A to select all -* **Move handles**: arrows to move one step, Shift+arrows to move continuously +* **Select**: left click to select, click in void to deselect, CTRL+click to select many, CTRL+A to select all +* **Move selection**: arrows to move one step, Shift+arrows to move continuously * **Move camera**: CTRL+arrows +* **Save**: CTRL+S ## License diff --git a/assets/game.levels.json b/assets/game.levels.json index 549c17b..bca10de 100644 --- a/assets/game.levels.json +++ b/assets/game.levels.json @@ -1,119 +1,346 @@ { - "levels": [ - { - "comment": "Movement tutorial", - "platforms": [ - {"pos": [0, -256], "size": [800, 16]} - ], - "characters": [ - {"pos": [0, -192], "color": [1,0,0,1]}, - {"pos": [-128, -192], "color": [0,1,0,1]}, - {"pos": [128, -192], "color": [0,0,1,1]} - ], - "absorbing_filters": [], - "rotating_filters": [], - "texts": [ - { - "pos": [0, 0], - "font_size": 32, - "text": "Combine the colors to synthetize a white light.\nUse arrows to move." - } - ] - }, - { - "comment": "Switch tutorial", - "platforms": [ - {"pos": [0, -256], "size": [800, 16]}, - {"pos": [128, 256], "size": [96, 16]} - ], - "characters": [ - {"pos": [0, -192], "color": [0,1,0,1]}, - {"pos": [-128, -192], "color": [1,0,0,1]}, - {"pos": [128, 320], "color": [0,0,1,1]} - ], - "absorbing_filters": [], - "rotating_filters": [], - "texts": [ - { - "pos": [0, 0], - "font_size": 32, - "text": "Press Tab to switch." - } - ] - }, - { - "comment": "Absorbing filter tutorial", - "platforms": [ - {"pos": [0, -256], "size": [800, 16]}, - {"pos": [0, -128], "size": [800, 16]} - ], - "characters": [ - {"pos": [-128, -192], "color": [1,0.64,0,1]}, - {"pos": [128, -192], "color": [0,0.37,1,1]} - ], - "absorbing_filters": [ - { - "pos": [0, -192], - "size": [16, 112], - "color": [1,0,0,1] - } - ], - "rotating_filters": [], - "texts": [ - { - "pos": [0, 0], - "font_size": 32, - "text": "Press R to reset." - } - ] - }, - { - "comment": "Rotating filter tutorial", - "platforms": [ - {"pos": [0, -256], "size": [800, 16]} - ], - "characters": [ - {"pos": [0, -192], "color": [1,0,0,1]}, - {"pos": [-128, -192], "color": [1,0,0,1]}, - {"pos": [128, -192], "color": [1,0,0,1]} - ], - "absorbing_filters": [], - "rotating_filters": [ - { - "pos": [0, -64], - "angle": 45 - } - ], - "texts": [ - { - "pos": [0, 0], - "font_size": 32, - "text": "Let's rotate the hue!" - } - ] - }, - { - "comment": "Game over", - "platforms": [ - {"pos": [0, -256], "size": [800, 16]} - ], - "characters": [ - {"pos": [0, -64], "color": [1,0,0,1]} - ], - "absorbing_filters": [], - "rotating_filters": [], - "texts": [ - { - "pos": [0, 128], - "font_size": 48, - "text": "Thank you for playing!" - }, - { - "pos": [0, 0], - "font_size": 32, - "text": "There is no more light to combine." - } - ] - } - ] + "levels": [ + { + "comment": "Movement tutorial", + "characters": [ + { + "pos": [ + 0.0, + -192.0 + ], + "color": [ + 1.0, + 0.0, + 0.0, + 1.0 + ] + }, + { + "pos": [ + -128.0, + -192.0 + ], + "color": [ + 0.0, + 1.0, + 0.0, + 1.0 + ] + }, + { + "pos": [ + 128.0, + -192.0 + ], + "color": [ + 0.0, + 0.0, + 1.0, + 1.0 + ] + } + ], + "platforms": [ + { + "pos": [ + 0.0, + -256.0 + ], + "size": [ + 800.0, + 16.0 + ] + } + ], + "absorbing_filters": [], + "rotating_filters": [], + "texts": [ + { + "pos": [ + 0.0, + 0.0 + ], + "font_size": 32.0, + "text": "Combine the colors to synthetize a white light.\nUse arrows to move." + } + ] + }, + { + "comment": "Switch tutorial", + "characters": [ + { + "pos": [ + 0.0, + -192.0 + ], + "color": [ + 0.0, + 1.0, + 0.0, + 1.0 + ] + }, + { + "pos": [ + -128.0, + -192.0 + ], + "color": [ + 1.0, + 0.0, + 0.0, + 1.0 + ] + }, + { + "pos": [ + 128.0, + 320.0 + ], + "color": [ + 0.0, + 0.0, + 1.0, + 1.0 + ] + } + ], + "platforms": [ + { + "pos": [ + 0.0, + -256.0 + ], + "size": [ + 800.0, + 16.0 + ] + }, + { + "pos": [ + 128.0, + 256.0 + ], + "size": [ + 96.0, + 16.0 + ] + } + ], + "absorbing_filters": [], + "rotating_filters": [], + "texts": [ + { + "pos": [ + 0.0, + 0.0 + ], + "font_size": 32.0, + "text": "Press Tab to switch." + } + ] + }, + { + "comment": "Absorbing filter tutorial", + "characters": [ + { + "pos": [ + -128.0, + -192.0 + ], + "color": [ + 1.0, + 0.64, + 0.0, + 1.0 + ] + }, + { + "pos": [ + 128.0, + -192.0 + ], + "color": [ + 0.0, + 0.37, + 1.0, + 1.0 + ] + } + ], + "platforms": [ + { + "pos": [ + 0.0, + -256.0 + ], + "size": [ + 800.0, + 16.0 + ] + }, + { + "pos": [ + 0.0, + -128.0 + ], + "size": [ + 800.0, + 16.0 + ] + } + ], + "absorbing_filters": [ + { + "pos": [ + 0.0, + -192.0 + ], + "size": [ + 16.0, + 112.0 + ], + "color": [ + 1.0, + 0.0, + 0.0, + 1.0 + ] + } + ], + "rotating_filters": [], + "texts": [ + { + "pos": [ + 0.0, + 0.0 + ], + "font_size": 32.0, + "text": "Press R to reset." + } + ] + }, + { + "comment": "Rotating filter tutorial", + "characters": [ + { + "pos": [ + 0.0, + -192.0 + ], + "color": [ + 1.0, + 0.0, + 0.0, + 1.0 + ] + }, + { + "pos": [ + -128.0, + -192.0 + ], + "color": [ + 1.0, + 0.0, + 0.0, + 1.0 + ] + }, + { + "pos": [ + 128.0, + -192.0 + ], + "color": [ + 1.0, + 0.0, + 0.0, + 1.0 + ] + } + ], + "platforms": [ + { + "pos": [ + 0.0, + -256.0 + ], + "size": [ + 800.0, + 16.0 + ] + } + ], + "absorbing_filters": [], + "rotating_filters": [ + { + "pos": [ + 0.0, + -64.0 + ], + "angle": 45.0 + } + ], + "texts": [ + { + "pos": [ + 0.0, + 0.0 + ], + "font_size": 32.0, + "text": "Let's rotate the hue!" + } + ] + }, + { + "comment": "Game over", + "characters": [ + { + "pos": [ + 0.0, + -64.0 + ], + "color": [ + 1.0, + 0.0, + 0.0, + 1.0 + ] + } + ], + "platforms": [ + { + "pos": [ + 0.0, + -256.0 + ], + "size": [ + 800.0, + 16.0 + ] + } + ], + "absorbing_filters": [], + "rotating_filters": [], + "texts": [ + { + "pos": [ + 0.0, + 128.0 + ], + "font_size": 48.0, + "text": "Thank you for playing!" + }, + { + "pos": [ + 0.0, + 0.0 + ], + "font_size": 32.0, + "text": "There is no more light to combine." + } + ] + } + ] } \ No newline at end of file diff --git a/src/editor.rs b/src/editor.rs index 382b585..91c24fe 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -21,6 +21,7 @@ impl Plugin for EditorPlugin { .add_system_set( SystemSet::on_update(AppState::Editor) .with_system(move_system) + .with_system(input_control_system) .with_system(follow_ends_system), ); } @@ -32,10 +33,15 @@ struct DragEndEvent(Entity); // Resources +struct CharacterList(Vec); + // Components #[derive(Component)] -struct Platform(Entity, Entity); +struct Platform; + +#[derive(Component)] +struct Ends(Entity, Entity); #[derive(Component)] struct Draggable; @@ -43,12 +49,20 @@ struct Draggable; #[derive(Component)] struct End(Entity); +#[derive(Component)] +struct CharacterColor(Color); + +#[derive(Component)] +struct Size(Vec2); + // Bundles #[derive(Bundle)] struct PlatformBundle { #[bundle] mesh: ColorMesh2dBundle, + size: Size, + platform: Platform, } #[derive(Bundle)] @@ -65,6 +79,7 @@ struct PlatformEndBundle { struct CharacterBundle { #[bundle] mesh: ColorMesh2dBundle, + color: CharacterColor, #[bundle] pickable: PickableBundle, draggable: Draggable, @@ -88,9 +103,11 @@ fn spawn_platform( transform, ..default() }, + size: Size(size), + platform: Platform, }) .id(); - let ends = Platform( + let ends = Ends( commands .spawn_bundle(PlatformEndBundle { mesh: ColorMesh2dBundle { @@ -148,7 +165,7 @@ fn spawn_character( transform: Transform, color: Color, index: usize, -) { +) -> Entity { let font = asset_server.get_handle("UacariLegacy-Thin.ttf"); commands .spawn_bundle(CharacterBundle { @@ -163,6 +180,7 @@ fn spawn_character( transform, ..default() }, + color: CharacterColor(color), pickable: PickableBundle::default(), draggable: Draggable, }) @@ -180,10 +198,11 @@ fn spawn_character( transform: Transform::from_xyz(0., 0., 1.), ..Default::default() }); - }); + }) + .id() } -pub fn spawn_stored_level( +fn spawn_stored_level( commands: &mut Commands, meshes: &mut ResMut>, materials: &mut ResMut>, @@ -201,8 +220,9 @@ pub fn spawn_stored_level( ); } + let mut character_list = Vec::new(); for (i, character) in stored_level.characters.iter().enumerate() { - spawn_character( + character_list.push(spawn_character( commands, meshes, materials, @@ -210,7 +230,57 @@ pub fn spawn_stored_level( Transform::from_xyz(character.pos.x, character.pos.y, 0.), character.color.into(), i, - ); + )); + } + commands.insert_resource(CharacterList(character_list)); +} + +fn save_level( + level_id: &Res, + stored_levels_assets: &mut ResMut>, + stored_levels_handle: &Res>, + character_list: &Res, + character_query: &Query<(&Transform, &CharacterColor), Without>, + platform_query: &Query<(&Transform, &Size), With>, +) { + let stored_levels = stored_levels_assets.get_mut(stored_levels_handle).unwrap(); + if let Some(stored_level) = stored_levels.levels.get_mut(level_id.0 .0 as usize) { + stored_level.platforms.clear(); + for (transform, size) in platform_query.iter() { + stored_level.platforms.push(StoredPlatform { + pos: Vec2 { + x: transform.translation.x, + y: transform.translation.y, + }, + size: size.0, + }) + } + + stored_level.characters.clear(); + for entity in character_list.0.iter() { + if let Ok((transform, color)) = character_query.get(*entity) { + stored_level.characters.push(StoredCharacter { + pos: Vec2 { + x: transform.translation.x, + y: transform.translation.y, + }, + color: color.0.as_rgba_f32().into(), + }) + } + } + } else { + return; + } + match std::fs::OpenOptions::new() + .write(true) + .truncate(true) + .open("assets/game.levels.json") + { + Ok(mut file) => { + serde_json::to_writer_pretty(&mut file, stored_levels).unwrap(); + println!("Saved!"); + } + Err(e) => eprintln!("Error writing levels file: {:?}", e), } } @@ -246,6 +316,29 @@ fn setup( } } +fn input_control_system( + keyboard_input: Res>, + level_id: Res, + mut stored_levels_assets: ResMut>, + stored_levels_handle: Res>, + character_list: Res, + character_query: Query<(&Transform, &CharacterColor), Without>, + platform_query: Query<(&Transform, &Size), With>, +) { + if keyboard_input.just_released(KeyCode::S) + && (keyboard_input.pressed(KeyCode::LControl) || keyboard_input.pressed(KeyCode::RControl)) + { + save_level( + &level_id, + &mut stored_levels_assets, + &stored_levels_handle, + &character_list, + &character_query, + &platform_query, + ); + } +} + fn move_system( keyboard_input: Res>, mut camera_query: Query<&mut Transform, (With, Without)>, @@ -297,22 +390,24 @@ fn move_system( fn follow_ends_system( mut meshes: ResMut>, - mut platform_query: Query<(&mut Transform, &mut Mesh2dHandle, &Platform)>, - end_query: Query<&Transform, Without>, + mut follower_query: Query<(&mut Transform, &mut Mesh2dHandle, &mut Size, &Ends)>, + end_query: Query<&Transform, Without>, mut drag_end_event: EventReader, ) { for DragEndEvent(entity) in drag_end_event.iter() { - if let Ok((mut transform, mut mesh, Platform(end1, end2))) = platform_query.get_mut(*entity) + if let Ok((mut transform, mut mesh, mut size, Ends(end1, end2))) = + follower_query.get_mut(*entity) { if let (Ok(end1), Ok(end2)) = (end_query.get(*end1), end_query.get(*end2)) { transform.translation.x = (end1.translation.x + end2.translation.x) / 2.; transform.translation.y = (end1.translation.y + end2.translation.y) / 2.; + size.0 = Vec2 { + x: (end2.translation.x - end1.translation.x).abs(), + y: (end2.translation.y - end1.translation.y).abs(), + }; *mesh = meshes .add(Mesh::from(Quad { - size: Vec2 { - x: (end2.translation.x - end1.translation.x).abs(), - y: (end2.translation.y - end1.translation.y).abs(), - }, + size: size.0, flip: false, })) .into(); From f20979f86dc3558f01ffdfe066ee46fa5b04effc Mon Sep 17 00:00:00 2001 From: Nixon Date: Fri, 26 Aug 2022 17:08:49 +0800 Subject: [PATCH 6/8] uses target translation for camera movement (prevent hard hit when moving beyond lower margin) --- Cargo.lock | 1 + Cargo.toml | 1 + src/game.rs | 8 +++++--- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 91bb56d..2396d32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1333,6 +1333,7 @@ dependencies = [ "parking_lot 0.12.1", "stdweb", "thiserror", + "wasm-bindgen", "web-sys", "windows", ] diff --git a/Cargo.toml b/Cargo.toml index 22905a7..c0de684 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ serde_json = "1.0.85" ticktock = "0.8.0" [target."cfg(target_arch = \"wasm32\")".dependencies] +cpal = { version = "0.14.0", features = ["wasm-bindgen"] } [profile.dev.package."*"] opt-level = 3 diff --git a/src/game.rs b/src/game.rs index eba3d44..102a44e 100644 --- a/src/game.rs +++ b/src/game.rs @@ -471,13 +471,15 @@ fn move_camera( let size: Vec2 = camera.logical_viewport_size().unwrap(); let half_height: f32 = size.y * 0.5; + let mut target_translation = character_transform.translation; + // prevent camera from going too low + target_translation.y = target_translation.y.max(half_height - MARGIN); + camera_transform.translation = camera_transform.translation.lerp( - character_transform.translation, + target_translation, time.delta_seconds() * FOLLOW_SPEED, ); - // prevent camera from going too low - camera_transform.translation.y = camera_transform.translation.y.max(half_height - MARGIN); // always make sure that camera is away from the object in order to render them camera_transform.translation.z = 999.0; } From 1e2ecd76c7dba8536aac78025b2f1cde30dec33c Mon Sep 17 00:00:00 2001 From: Nixon Date: Fri, 26 Aug 2022 18:27:08 +0800 Subject: [PATCH 7/8] directly host server after building with run-wasm.sh script --- run-wasm.sh | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 run-wasm.sh diff --git a/run-wasm.sh b/run-wasm.sh new file mode 100644 index 0000000..417b6b0 --- /dev/null +++ b/run-wasm.sh @@ -0,0 +1,2 @@ +source build-wasm.sh +python -m http.server \ No newline at end of file From 99084066234e94531c1919347b0a56fa4e772953 Mon Sep 17 00:00:00 2001 From: tuxmain Date: Fri, 26 Aug 2022 15:38:13 +0200 Subject: [PATCH 8/8] editor: filters --- Cargo.lock | 5 +- Cargo.toml | 3 +- src/editor.rs | 204 +++++++++++++++++++++++++++++++++++++++++++++++++- src/game.rs | 9 +-- 4 files changed, 207 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2396d32..2e34d56 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -590,9 +590,9 @@ dependencies = [ [[package]] name = "bevy_mod_picking" -version = "0.8.2" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f97d740fcd9d089a768399e902e741f45f8d671e756c939d2f1ce8ad14d63a" +checksum = "db42ac84b1409452bbfa696d9071b9a7a2505c73620c939b758b5bf23573976a" dependencies = [ "bevy", "bevy_mod_raycast", @@ -1333,7 +1333,6 @@ dependencies = [ "parking_lot 0.12.1", "stdweb", "thiserror", - "wasm-bindgen", "web-sys", "windows", ] diff --git a/Cargo.toml b/Cargo.toml index c0de684..742804f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,14 +17,13 @@ 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" +bevy_mod_picking = "0.9.0" cpal = "0.14.0" hexodsp = { git = "https://github.com/WeirdConstructor/HexoDSP", default-features = false } serde_json = "1.0.85" ticktock = "0.8.0" [target."cfg(target_arch = \"wasm32\")".dependencies] -cpal = { version = "0.14.0", features = ["wasm-bindgen"] } [profile.dev.package."*"] opt-level = 3 diff --git a/src/editor.rs b/src/editor.rs index 91c24fe..55c1592 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -55,6 +55,12 @@ struct CharacterColor(Color); #[derive(Component)] struct Size(Vec2); +#[derive(Component)] +struct RotatingFilterAngle(f32); + +#[derive(Component)] +struct AbsorbingFilterColor(Color); + // Bundles #[derive(Bundle)] @@ -66,7 +72,7 @@ struct PlatformBundle { } #[derive(Bundle)] -struct PlatformEndBundle { +struct EndBundle { #[bundle] mesh: ColorMesh2dBundle, #[bundle] @@ -85,6 +91,24 @@ struct CharacterBundle { draggable: Draggable, } +#[derive(Bundle)] +struct AbsorbingFilterBundle { + #[bundle] + mesh: ColorMesh2dBundle, + size: Size, + color: AbsorbingFilterColor, +} + +#[derive(Bundle)] +struct RotatingFilterBundle { + #[bundle] + mesh: ColorMesh2dBundle, + angle: RotatingFilterAngle, + #[bundle] + pickable: PickableBundle, + draggable: Draggable, +} + // Functions fn spawn_platform( @@ -109,7 +133,7 @@ fn spawn_platform( .id(); let ends = Ends( commands - .spawn_bundle(PlatformEndBundle { + .spawn_bundle(EndBundle { mesh: ColorMesh2dBundle { mesh: meshes .add(Mesh::from(Circle { @@ -131,7 +155,7 @@ fn spawn_platform( }) .id(), commands - .spawn_bundle(PlatformEndBundle { + .spawn_bundle(EndBundle { mesh: ColorMesh2dBundle { mesh: meshes .add(Mesh::from(Circle { @@ -202,6 +226,121 @@ fn spawn_character( .id() } +fn spawn_absorbing_filter( + commands: &mut Commands, + meshes: &mut ResMut>, + materials: &mut ResMut>, + + transform: Transform, + size: Vec2, + color: Color, +) { + let absorbing_filter = commands + .spawn_bundle(AbsorbingFilterBundle { + mesh: ColorMesh2dBundle { + mesh: meshes.add(Mesh::from(Quad { size, flip: false })).into(), + material: materials.add(ColorMaterial::from(color)), + transform, + ..default() + }, + size: Size(size), + color: AbsorbingFilterColor(color), + }) + .id(); + let ends = Ends( + commands + .spawn_bundle(EndBundle { + 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( + transform.translation.x - size.x / 2., + transform.translation.y - size.y / 2., + 0.5, + ), + ..default() + }, + pickable: PickableBundle::default(), + draggable: Draggable, + end: End(absorbing_filter), + }) + .id(), + commands + .spawn_bundle(EndBundle { + 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( + transform.translation.x + size.x / 2., + transform.translation.y + size.y / 2., + 0.5, + ), + ..default() + }, + pickable: PickableBundle::default(), + draggable: Draggable, + end: End(absorbing_filter), + }) + .id(), + ); + commands.entity(absorbing_filter).insert(ends); +} + +fn spawn_rotating_filter( + commands: &mut Commands, + meshes: &mut ResMut>, + materials: &mut ResMut>, + asset_server: &Res, + + transform: Transform, + angle: f32, +) -> Entity { + let font = asset_server.get_handle("UacariLegacy-Thin.ttf"); + commands + .spawn_bundle(RotatingFilterBundle { + mesh: ColorMesh2dBundle { + mesh: meshes + .add(Mesh::from(Circle { + radius: 32., + vertices: 36, + })) + .into(), + material: materials.add(ColorMaterial::from(Color::WHITE)), + transform, + ..default() + }, + angle: RotatingFilterAngle(angle), + pickable: PickableBundle::default(), + draggable: Draggable, + }) + .with_children(|c| { + c.spawn_bundle(Text2dBundle { + text: Text::from_section( + &angle.to_string(), + TextStyle { + font: font.clone(), + font_size: 32., + color: Color::RED, + }, + ) + .with_alignment(TextAlignment::CENTER), + transform: Transform::from_xyz(0., 0., 1.), + ..Default::default() + }); + }) + .id() +} + fn spawn_stored_level( commands: &mut Commands, meshes: &mut ResMut>, @@ -233,6 +372,28 @@ fn spawn_stored_level( )); } commands.insert_resource(CharacterList(character_list)); + + for absorbing_filter in stored_level.absorbing_filters.iter() { + spawn_absorbing_filter( + commands, + meshes, + materials, + Transform::from_xyz(absorbing_filter.pos.x, absorbing_filter.pos.y, 0.), + absorbing_filter.size, + absorbing_filter.color.into(), + ); + } + + for rotating_filter in stored_level.rotating_filters.iter() { + spawn_rotating_filter( + commands, + meshes, + materials, + asset_server, + Transform::from_xyz(rotating_filter.pos.x, rotating_filter.pos.y, 0.), + rotating_filter.angle, + ); + } } fn save_level( @@ -242,6 +403,11 @@ fn save_level( character_list: &Res, character_query: &Query<(&Transform, &CharacterColor), Without>, platform_query: &Query<(&Transform, &Size), With>, + absorbing_filter_query: &Query<(&Transform, &Size, &AbsorbingFilterColor), Without>, + rotating_filter_query: &Query< + (&Transform, &RotatingFilterAngle), + (Without, Without), + >, ) { let stored_levels = stored_levels_assets.get_mut(stored_levels_handle).unwrap(); if let Some(stored_level) = stored_levels.levels.get_mut(level_id.0 .0 as usize) { @@ -264,10 +430,33 @@ fn save_level( x: transform.translation.x, y: transform.translation.y, }, - color: color.0.as_rgba_f32().into(), + color: color.0.into(), }) } } + + stored_level.absorbing_filters.clear(); + for (transform, size, color) in absorbing_filter_query.iter() { + stored_level.absorbing_filters.push(StoredAbsorbingFilter { + pos: Vec2 { + x: transform.translation.x, + y: transform.translation.y, + }, + size: size.0, + color: color.0.into(), + }) + } + + stored_level.rotating_filters.clear(); + for (transform, angle) in rotating_filter_query.iter() { + stored_level.rotating_filters.push(StoredRotatingFilter { + pos: Vec2 { + x: transform.translation.x, + y: transform.translation.y, + }, + angle: angle.0, + }) + } } else { return; } @@ -324,6 +513,11 @@ fn input_control_system( character_list: Res, character_query: Query<(&Transform, &CharacterColor), Without>, platform_query: Query<(&Transform, &Size), With>, + absorbing_filter_query: Query<(&Transform, &Size, &AbsorbingFilterColor), Without>, + rotating_filter_query: Query< + (&Transform, &RotatingFilterAngle), + (Without, Without), + >, ) { if keyboard_input.just_released(KeyCode::S) && (keyboard_input.pressed(KeyCode::LControl) || keyboard_input.pressed(KeyCode::RControl)) @@ -335,6 +529,8 @@ fn input_control_system( &character_list, &character_query, &platform_query, + &absorbing_filter_query, + &rotating_filter_query, ); } } diff --git a/src/game.rs b/src/game.rs index 102a44e..32f3bb0 100644 --- a/src/game.rs +++ b/src/game.rs @@ -471,14 +471,13 @@ fn move_camera( let size: Vec2 = camera.logical_viewport_size().unwrap(); let half_height: f32 = size.y * 0.5; - let mut target_translation = character_transform.translation; + let mut target_translation = character_transform.translation; // prevent camera from going too low target_translation.y = target_translation.y.max(half_height - MARGIN); - camera_transform.translation = camera_transform.translation.lerp( - target_translation, - time.delta_seconds() * FOLLOW_SPEED, - ); + camera_transform.translation = camera_transform + .translation + .lerp(target_translation, time.delta_seconds() * FOLLOW_SPEED); // always make sure that camera is away from the object in order to render them camera_transform.translation.z = 999.0;