417 lines
10 KiB
Rust
417 lines
10 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),
|
|
);
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
|
|
// Bundles
|
|
|
|
#[derive(Bundle)]
|
|
struct PlatformBundle {
|
|
#[bundle]
|
|
mesh: ColorMesh2dBundle,
|
|
size: Size,
|
|
platform: Platform,
|
|
}
|
|
|
|
#[derive(Bundle)]
|
|
struct PlatformEndBundle {
|
|
#[bundle]
|
|
mesh: ColorMesh2dBundle,
|
|
#[bundle]
|
|
pickable: PickableBundle,
|
|
draggable: Draggable,
|
|
end: End,
|
|
}
|
|
|
|
#[derive(Bundle)]
|
|
struct CharacterBundle {
|
|
#[bundle]
|
|
mesh: ColorMesh2dBundle,
|
|
color: CharacterColor,
|
|
#[bundle]
|
|
pickable: PickableBundle,
|
|
draggable: Draggable,
|
|
}
|
|
|
|
// 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,
|
|
})
|
|
.id();
|
|
let ends = Ends(
|
|
commands
|
|
.spawn_bundle(PlatformEndBundle {
|
|
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(PlatformEndBundle {
|
|
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,
|
|
})
|
|
.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_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));
|
|
}
|
|
|
|
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),
|
|
}
|
|
}
|
|
|
|
// 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>>,
|
|
) {
|
|
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>)>,
|
|
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 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();
|
|
}
|
|
}
|
|
}
|
|
}
|