From 7d2c68a0556114f304e66ba63c7cc7e42be95a15 Mon Sep 17 00:00:00 2001 From: tuxmain Date: Fri, 26 Aug 2022 14:57:53 +0200 Subject: [PATCH] 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();