bevyjam/src/editor.rs

814 lines
19 KiB
Rust

#![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::<DragEndEvent>()
.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<Entity>);
// 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<Assets<Mesh>>,
materials: &mut ResMut<Assets<ColorMaterial>>,
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<Assets<Mesh>>,
materials: &mut ResMut<Assets<ColorMaterial>>,
asset_server: &Res<AssetServer>,
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<Assets<Mesh>>,
materials: &mut ResMut<Assets<ColorMaterial>>,
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<Assets<Mesh>>,
materials: &mut ResMut<Assets<ColorMaterial>>,
asset_server: &Res<AssetServer>,
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<Assets<Mesh>>,
materials: &mut ResMut<Assets<ColorMaterial>>,
asset_server: &Res<AssetServer>,
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<Assets<Mesh>>,
materials: &mut ResMut<Assets<ColorMaterial>>,
asset_server: &Res<AssetServer>,
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<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>>,
absorbing_filter_query: &Query<(&Transform, &Size, &AbsorbingFilterColor), Without<Platform>>,
rotating_filter_query: &Query<
(&Transform, &RotatingFilterAngle),
(Without<Platform>, Without<CharacterColor>),
>,
melty_platform_query: &Query<
(&Transform, &MeltyPlatformColor),
(Without<Platform>, Without<CharacterColor>),
>,
) {
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<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
camera_query: Query<Entity, With<Camera>>,
level_id: Res<crate::game::FirstLevel>,
stored_levels_assets: Res<Assets<StoredLevels>>,
stored_levels_handle: Res<Handle<StoredLevels>>,
asset_server: Res<AssetServer>,
) {
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<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>>,
absorbing_filter_query: Query<(&Transform, &Size, &AbsorbingFilterColor), Without<Platform>>,
rotating_filter_query: Query<
(&Transform, &RotatingFilterAngle),
(Without<Platform>, Without<CharacterColor>),
>,
melty_platform_query: Query<
(&Transform, &MeltyPlatformColor),
(Without<Platform>, Without<CharacterColor>),
>,
) {
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<Input<KeyCode>>,
mut camera_query: Query<&mut Transform, (With<Camera>, Without<Draggable>)>,
mut drag_query: Query<(&mut Transform, &Selection, Option<&End>), With<Draggable>>,
mut drag_end_event: EventWriter<DragEndEvent>,
) {
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<Input<KeyCode>>,
mut removable_query: Query<(Entity, &Selection, Option<&Ends>), With<Removable>>,
) {
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<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
asset_server: Res<AssetServer>,
camera_query: Query<&Transform, With<Camera>>,
keyboard_input: Res<Input<KeyCode>>,
mut character_list: ResMut<CharacterList>,
) {
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<Assets<Mesh>>,
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, 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();
}
}
}
}