#![allow(clippy::type_complexity)] use crate::{levels::stored::*, AppState}; use bevy::{ input::{keyboard::KeyCode, Input}, prelude::{ shape::{Circle, Quad}, *, }, sprite::Mesh2dHandle, }; use bevy_mod_picking::*; pub struct EditorPlugin; impl Plugin for EditorPlugin { fn build(&self, app: &mut App) { 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(move_system) .with_system(input_control_system) .with_system(follow_ends_system) .with_system(remove_system) .with_system(spawn_system), ); } } // Events struct DragEndEvent(Entity); // Resources struct CharacterList(Vec); // Components #[derive(Component)] struct Platform; #[derive(Component)] struct Ends(Entity, Entity); #[derive(Component)] struct Draggable; #[derive(Component)] struct End(Entity); #[derive(Component)] struct CharacterColor(Color); #[derive(Component)] struct Size(Vec2); #[derive(Component)] struct RotatingFilterAngle(f32); #[derive(Component)] struct AbsorbingFilterColor(Color); #[derive(Component)] struct MeltyPlatformColor(Color); #[derive(Component)] struct Removable; // Bundles #[derive(Bundle)] struct PlatformBundle { #[bundle] mesh: ColorMesh2dBundle, size: Size, platform: Platform, #[bundle] pickable: PickableBundle, removable: Removable, } #[derive(Bundle)] struct EndBundle { #[bundle] mesh: ColorMesh2dBundle, #[bundle] pickable: PickableBundle, draggable: Draggable, end: End, } #[derive(Bundle)] struct CharacterBundle { #[bundle] mesh: ColorMesh2dBundle, color: CharacterColor, #[bundle] pickable: PickableBundle, draggable: Draggable, removable: Removable, } #[derive(Bundle)] struct AbsorbingFilterBundle { #[bundle] mesh: ColorMesh2dBundle, size: Size, color: AbsorbingFilterColor, #[bundle] pickable: PickableBundle, removable: Removable, } #[derive(Bundle)] struct RotatingFilterBundle { #[bundle] mesh: ColorMesh2dBundle, angle: RotatingFilterAngle, #[bundle] pickable: PickableBundle, draggable: Draggable, removable: Removable, } #[derive(Bundle)] struct MeltyPlatformBundle { #[bundle] mesh: ColorMesh2dBundle, color: MeltyPlatformColor, #[bundle] pickable: PickableBundle, draggable: Draggable, removable: Removable, } // Functions fn spawn_platform( commands: &mut Commands, meshes: &mut ResMut>, materials: &mut ResMut>, transform: Transform, size: Vec2, ) { let platform = 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() }, size: Size(size), platform: Platform, pickable: PickableBundle::default(), removable: Removable, }) .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(platform), }) .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(platform), }) .id(), ); 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, ) -> Entity { 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() }, color: CharacterColor(color), pickable: PickableBundle::default(), draggable: Draggable, removable: Removable, }) .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() }); }) .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), pickable: PickableBundle::default(), removable: Removable, }) .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, removable: Removable, }) .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_melty_platform( commands: &mut Commands, meshes: &mut ResMut>, materials: &mut ResMut>, asset_server: &Res, transform: Transform, color: Color, ) -> Entity { let font = asset_server.get_handle("UacariLegacy-Thin.ttf"); commands .spawn_bundle(MeltyPlatformBundle { mesh: ColorMesh2dBundle { mesh: meshes .add(Mesh::from(Quad { size: Vec2 { x: 96., y: 16. }, flip: false, })) .into(), material: materials.add(ColorMaterial::from(color)), transform, ..default() }, color: MeltyPlatformColor(color), pickable: PickableBundle::default(), draggable: Draggable, removable: Removable, }) .with_children(|c| { c.spawn_bundle(Text2dBundle { text: Text::from_section( "MELTY", TextStyle { font: font.clone(), font_size: 16., color: Color::BLACK, }, ) .with_alignment(TextAlignment::CENTER), transform: Transform::from_xyz(0., 0., 1.), ..Default::default() }); }) .id() } fn spawn_stored_level( commands: &mut Commands, meshes: &mut ResMut>, materials: &mut ResMut>, asset_server: &Res, stored_level: &StoredLevel, ) { for platform in stored_level.platforms.iter() { spawn_platform( commands, meshes, materials, Transform::from_xyz(platform.pos.x, platform.pos.y, 0.), platform.size, ); } let mut character_list = Vec::new(); for (i, character) in stored_level.characters.iter().enumerate() { character_list.push(spawn_character( commands, meshes, materials, asset_server, Transform::from_xyz(character.pos.x, character.pos.y, 0.), character.color.into(), i, )); } 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, ); } for melty_platform in stored_level.melty_platforms.iter() { spawn_melty_platform( commands, meshes, materials, asset_server, Transform::from_xyz(melty_platform.pos.x, melty_platform.pos.y, 0.), melty_platform.color.into(), ); } } 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>, absorbing_filter_query: &Query<(&Transform, &Size, &AbsorbingFilterColor), Without>, rotating_filter_query: &Query< (&Transform, &RotatingFilterAngle), (Without, Without), >, melty_platform_query: &Query< (&Transform, &MeltyPlatformColor), (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) { 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.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, }) } stored_level.melty_platforms.clear(); for (transform, color) in melty_platform_query.iter() { stored_level.melty_platforms.push(StoredMeltyPlatform { pos: Vec2 { x: transform.translation.x, y: transform.translation.y, }, color: color.0.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), } } // Systems 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 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>, absorbing_filter_query: Query<(&Transform, &Size, &AbsorbingFilterColor), Without>, rotating_filter_query: Query< (&Transform, &RotatingFilterAngle), (Without, Without), >, melty_platform_query: Query< (&Transform, &MeltyPlatformColor), (Without, Without), >, ) { if keyboard_input.just_pressed(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, &absorbing_filter_query, &rotating_filter_query, &melty_platform_query, ); } } fn move_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 { 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, 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 remove_system( mut commands: Commands, keyboard_input: Res>, mut removable_query: Query<(Entity, &Selection, Option<&Ends>), With>, ) { if keyboard_input.just_released(KeyCode::Delete) { for (entity, selection, ends) in removable_query.iter_mut() { if selection.selected() { commands.entity(entity).despawn_recursive(); if let Some(ends) = ends { commands.entity(ends.0).despawn_recursive(); commands.entity(ends.1).despawn_recursive(); } } } } } fn spawn_system( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, asset_server: Res, camera_query: Query<&Transform, With>, keyboard_input: Res>, mut character_list: ResMut, ) { if keyboard_input.pressed(KeyCode::LControl) || keyboard_input.pressed(KeyCode::RControl) || keyboard_input.pressed(KeyCode::LAlt) || keyboard_input.pressed(KeyCode::RAlt) { return; } let camera_pos = camera_query.single().translation; if keyboard_input.just_released(KeyCode::P) { spawn_platform( &mut commands, &mut meshes, &mut materials, Transform::from_xyz(camera_pos.x, camera_pos.y, 0.), Vec2 { x: 96., y: 16. }, ); } if keyboard_input.just_released(KeyCode::C) { let index = character_list.0.len(); character_list.0.push(spawn_character( &mut commands, &mut meshes, &mut materials, &asset_server, Transform::from_xyz(camera_pos.x, camera_pos.y, 0.), Color::RED, index, )); } if keyboard_input.just_released(KeyCode::A) { spawn_absorbing_filter( &mut commands, &mut meshes, &mut materials, Transform::from_xyz(camera_pos.x, camera_pos.y, 0.), Vec2 { x: 16., y: 96. }, Color::RED, ); } if keyboard_input.just_released(KeyCode::R) { spawn_rotating_filter( &mut commands, &mut meshes, &mut materials, &asset_server, Transform::from_xyz(camera_pos.x, camera_pos.y, 0.), 90., ); } if keyboard_input.just_released(KeyCode::M) { spawn_melty_platform( &mut commands, &mut meshes, &mut materials, &asset_server, Transform::from_xyz(camera_pos.x, camera_pos.y, 0.), Color::RED, ); } } fn follow_ends_system( mut meshes: ResMut>, 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, 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: size.0, flip: false, })) .into(); } } } }