From 7d2c68a0556114f304e66ba63c7cc7e42be95a15 Mon Sep 17 00:00:00 2001
From: tuxmain <tuxmain@zettascript.org>
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 <N> 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<Entity>);
+
// 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<Assets<Mesh>>,
materials: &mut ResMut<Assets<ColorMaterial>>,
@@ -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<crate::game::FirstLevel>,
+ stored_levels_assets: &mut ResMut<Assets<StoredLevels>>,
+ stored_levels_handle: &Res<Handle<StoredLevels>>,
+ character_list: &Res<CharacterList>,
+ character_query: &Query<(&Transform, &CharacterColor), Without<Platform>>,
+ platform_query: &Query<(&Transform, &Size), With<Platform>>,
+) {
+ 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<Input<KeyCode>>,
+ level_id: Res<crate::game::FirstLevel>,
+ mut stored_levels_assets: ResMut<Assets<StoredLevels>>,
+ stored_levels_handle: Res<Handle<StoredLevels>>,
+ character_list: Res<CharacterList>,
+ character_query: Query<(&Transform, &CharacterColor), Without<Platform>>,
+ platform_query: Query<(&Transform, &Size), With<Platform>>,
+) {
+ 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<Input<KeyCode>>,
mut camera_query: Query<&mut Transform, (With<Camera>, Without<Draggable>)>,
@@ -297,22 +390,24 @@ fn move_system(
fn follow_ends_system(
mut meshes: ResMut<Assets<Mesh>>,
- mut platform_query: Query<(&mut Transform, &mut Mesh2dHandle, &Platform)>,
- end_query: Query<&Transform, Without<Platform>>,
+ mut follower_query: Query<(&mut Transform, &mut Mesh2dHandle, &mut Size, &Ends)>,
+ end_query: Query<&Transform, Without<Ends>>,
mut drag_end_event: EventReader<DragEndEvent>,
) {
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();