editor: save

This commit is contained in:
Pascal Engélibert 2022-08-26 14:57:53 +02:00
parent da5f9b0820
commit 7d2c68a055
Signed by: tuxmain
GPG key ID: 3504BC6D362F7DCA
5 changed files with 458 additions and 133 deletions

1
Cargo.lock generated
View file

@ -939,6 +939,7 @@ dependencies = [
"rand_distr", "rand_distr",
"rapier2d", "rapier2d",
"serde", "serde",
"serde_json",
"ticktock", "ticktock",
] ]

View file

@ -20,6 +20,7 @@ bevy-inspector-egui = "0.12.1"
bevy_mod_picking = "0.8.2" bevy_mod_picking = "0.8.2"
cpal = "0.14.0" cpal = "0.14.0"
hexodsp = { git = "https://github.com/WeirdConstructor/HexoDSP", default-features = false } hexodsp = { git = "https://github.com/WeirdConstructor/HexoDSP", default-features = false }
serde_json = "1.0.85"
ticktock = "0.8.0" ticktock = "0.8.0"
[target."cfg(target_arch = \"wasm32\")".dependencies] [target."cfg(target_arch = \"wasm32\")".dependencies]

View file

@ -53,9 +53,10 @@ Edit the level `N: u32` with the command `bevyjam <N> e`.
### Editor controls ### Editor controls
* **Select handles**: left click to select, click in void to deselect, CTRL+click to select many, CTRL+A to select all * **Select**: 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 selection**: arrows to move one step, Shift+arrows to move continuously
* **Move camera**: CTRL+arrows * **Move camera**: CTRL+arrows
* **Save**: CTRL+S
## License ## License

View file

@ -1,119 +1,346 @@
{ {
"levels": [ "levels": [
{ {
"comment": "Movement tutorial", "comment": "Movement tutorial",
"platforms": [ "characters": [
{"pos": [0, -256], "size": [800, 16]} {
], "pos": [
"characters": [ 0.0,
{"pos": [0, -192], "color": [1,0,0,1]}, -192.0
{"pos": [-128, -192], "color": [0,1,0,1]}, ],
{"pos": [128, -192], "color": [0,0,1,1]} "color": [
], 1.0,
"absorbing_filters": [], 0.0,
"rotating_filters": [], 0.0,
"texts": [ 1.0
{ ]
"pos": [0, 0], },
"font_size": 32, {
"text": "Combine the colors to synthetize a white light.\nUse arrows to move." "pos": [
} -128.0,
] -192.0
}, ],
{ "color": [
"comment": "Switch tutorial", 0.0,
"platforms": [ 1.0,
{"pos": [0, -256], "size": [800, 16]}, 0.0,
{"pos": [128, 256], "size": [96, 16]} 1.0
], ]
"characters": [ },
{"pos": [0, -192], "color": [0,1,0,1]}, {
{"pos": [-128, -192], "color": [1,0,0,1]}, "pos": [
{"pos": [128, 320], "color": [0,0,1,1]} 128.0,
], -192.0
"absorbing_filters": [], ],
"rotating_filters": [], "color": [
"texts": [ 0.0,
{ 0.0,
"pos": [0, 0], 1.0,
"font_size": 32, 1.0
"text": "Press Tab to switch." ]
} }
] ],
}, "platforms": [
{ {
"comment": "Absorbing filter tutorial", "pos": [
"platforms": [ 0.0,
{"pos": [0, -256], "size": [800, 16]}, -256.0
{"pos": [0, -128], "size": [800, 16]} ],
], "size": [
"characters": [ 800.0,
{"pos": [-128, -192], "color": [1,0.64,0,1]}, 16.0
{"pos": [128, -192], "color": [0,0.37,1,1]} ]
], }
"absorbing_filters": [ ],
{ "absorbing_filters": [],
"pos": [0, -192], "rotating_filters": [],
"size": [16, 112], "texts": [
"color": [1,0,0,1] {
} "pos": [
], 0.0,
"rotating_filters": [], 0.0
"texts": [ ],
{ "font_size": 32.0,
"pos": [0, 0], "text": "Combine the colors to synthetize a white light.\nUse arrows to move."
"font_size": 32, }
"text": "Press R to reset." ]
} },
] {
}, "comment": "Switch tutorial",
{ "characters": [
"comment": "Rotating filter tutorial", {
"platforms": [ "pos": [
{"pos": [0, -256], "size": [800, 16]} 0.0,
], -192.0
"characters": [ ],
{"pos": [0, -192], "color": [1,0,0,1]}, "color": [
{"pos": [-128, -192], "color": [1,0,0,1]}, 0.0,
{"pos": [128, -192], "color": [1,0,0,1]} 1.0,
], 0.0,
"absorbing_filters": [], 1.0
"rotating_filters": [ ]
{ },
"pos": [0, -64], {
"angle": 45 "pos": [
} -128.0,
], -192.0
"texts": [ ],
{ "color": [
"pos": [0, 0], 1.0,
"font_size": 32, 0.0,
"text": "Let's rotate the hue!" 0.0,
} 1.0
] ]
}, },
{ {
"comment": "Game over", "pos": [
"platforms": [ 128.0,
{"pos": [0, -256], "size": [800, 16]} 320.0
], ],
"characters": [ "color": [
{"pos": [0, -64], "color": [1,0,0,1]} 0.0,
], 0.0,
"absorbing_filters": [], 1.0,
"rotating_filters": [], 1.0
"texts": [ ]
{ }
"pos": [0, 128], ],
"font_size": 48, "platforms": [
"text": "Thank you for playing!" {
}, "pos": [
{ 0.0,
"pos": [0, 0], -256.0
"font_size": 32, ],
"text": "There is no more light to combine." "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."
}
]
}
]
} }

View file

@ -21,6 +21,7 @@ impl Plugin for EditorPlugin {
.add_system_set( .add_system_set(
SystemSet::on_update(AppState::Editor) SystemSet::on_update(AppState::Editor)
.with_system(move_system) .with_system(move_system)
.with_system(input_control_system)
.with_system(follow_ends_system), .with_system(follow_ends_system),
); );
} }
@ -32,10 +33,15 @@ struct DragEndEvent(Entity);
// Resources // Resources
struct CharacterList(Vec<Entity>);
// Components // Components
#[derive(Component)] #[derive(Component)]
struct Platform(Entity, Entity); struct Platform;
#[derive(Component)]
struct Ends(Entity, Entity);
#[derive(Component)] #[derive(Component)]
struct Draggable; struct Draggable;
@ -43,12 +49,20 @@ struct Draggable;
#[derive(Component)] #[derive(Component)]
struct End(Entity); struct End(Entity);
#[derive(Component)]
struct CharacterColor(Color);
#[derive(Component)]
struct Size(Vec2);
// Bundles // Bundles
#[derive(Bundle)] #[derive(Bundle)]
struct PlatformBundle { struct PlatformBundle {
#[bundle] #[bundle]
mesh: ColorMesh2dBundle, mesh: ColorMesh2dBundle,
size: Size,
platform: Platform,
} }
#[derive(Bundle)] #[derive(Bundle)]
@ -65,6 +79,7 @@ struct PlatformEndBundle {
struct CharacterBundle { struct CharacterBundle {
#[bundle] #[bundle]
mesh: ColorMesh2dBundle, mesh: ColorMesh2dBundle,
color: CharacterColor,
#[bundle] #[bundle]
pickable: PickableBundle, pickable: PickableBundle,
draggable: Draggable, draggable: Draggable,
@ -88,9 +103,11 @@ fn spawn_platform(
transform, transform,
..default() ..default()
}, },
size: Size(size),
platform: Platform,
}) })
.id(); .id();
let ends = Platform( let ends = Ends(
commands commands
.spawn_bundle(PlatformEndBundle { .spawn_bundle(PlatformEndBundle {
mesh: ColorMesh2dBundle { mesh: ColorMesh2dBundle {
@ -148,7 +165,7 @@ fn spawn_character(
transform: Transform, transform: Transform,
color: Color, color: Color,
index: usize, index: usize,
) { ) -> Entity {
let font = asset_server.get_handle("UacariLegacy-Thin.ttf"); let font = asset_server.get_handle("UacariLegacy-Thin.ttf");
commands commands
.spawn_bundle(CharacterBundle { .spawn_bundle(CharacterBundle {
@ -163,6 +180,7 @@ fn spawn_character(
transform, transform,
..default() ..default()
}, },
color: CharacterColor(color),
pickable: PickableBundle::default(), pickable: PickableBundle::default(),
draggable: Draggable, draggable: Draggable,
}) })
@ -180,10 +198,11 @@ fn spawn_character(
transform: Transform::from_xyz(0., 0., 1.), transform: Transform::from_xyz(0., 0., 1.),
..Default::default() ..Default::default()
}); });
}); })
.id()
} }
pub fn spawn_stored_level( fn spawn_stored_level(
commands: &mut Commands, commands: &mut Commands,
meshes: &mut ResMut<Assets<Mesh>>, meshes: &mut ResMut<Assets<Mesh>>,
materials: &mut ResMut<Assets<ColorMaterial>>, 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() { for (i, character) in stored_level.characters.iter().enumerate() {
spawn_character( character_list.push(spawn_character(
commands, commands,
meshes, meshes,
materials, materials,
@ -210,7 +230,57 @@ pub fn spawn_stored_level(
Transform::from_xyz(character.pos.x, character.pos.y, 0.), Transform::from_xyz(character.pos.x, character.pos.y, 0.),
character.color.into(), character.color.into(),
i, 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( fn move_system(
keyboard_input: Res<Input<KeyCode>>, keyboard_input: Res<Input<KeyCode>>,
mut camera_query: Query<&mut Transform, (With<Camera>, Without<Draggable>)>, mut camera_query: Query<&mut Transform, (With<Camera>, Without<Draggable>)>,
@ -297,22 +390,24 @@ fn move_system(
fn follow_ends_system( fn follow_ends_system(
mut meshes: ResMut<Assets<Mesh>>, mut meshes: ResMut<Assets<Mesh>>,
mut platform_query: Query<(&mut Transform, &mut Mesh2dHandle, &Platform)>, mut follower_query: Query<(&mut Transform, &mut Mesh2dHandle, &mut Size, &Ends)>,
end_query: Query<&Transform, Without<Platform>>, end_query: Query<&Transform, Without<Ends>>,
mut drag_end_event: EventReader<DragEndEvent>, mut drag_end_event: EventReader<DragEndEvent>,
) { ) {
for DragEndEvent(entity) in drag_end_event.iter() { 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)) { 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.x = (end1.translation.x + end2.translation.x) / 2.;
transform.translation.y = (end1.translation.y + end2.translation.y) / 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 *mesh = meshes
.add(Mesh::from(Quad { .add(Mesh::from(Quad {
size: Vec2 { size: size.0,
x: (end2.translation.x - end1.translation.x).abs(),
y: (end2.translation.y - end1.translation.y).abs(),
},
flip: false, flip: false,
})) }))
.into(); .into();