diff --git a/Cargo.toml b/Cargo.toml index 92afd69..13606e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,10 @@ path = "bin/debug-info.rs" name = "audio-inspect" path = "bin/audio-inspect.rs" +[[bin]] +name = "editor" +path = "bin/editor.rs" + [dependencies] bevy = "0.11" diff --git a/NOTES.md b/NOTES.md index fd0cd71..73b324d 100644 --- a/NOTES.md +++ b/NOTES.md @@ -11,3 +11,12 @@ In principle (these are just 1s and 0s) we can spawn these all in their own worl There is [at least one] RFC for Bevy to support a "Universe" with multiple worlds, but nothing implemented. +--- + +The workaround for this was to change the gltf inspector from loading _all_ assets to just loading assets which are dragged+dropped into the window. + +Buuut I think the workaround is to have one camera per scene and update it to either render ot a preview or render to the window. + +I kinda wish I could make entities visible to certain cameras, or put them in a different "dimension layer". +The render target I don't think would actually solve the problem since it's still in the same world and therefore can still see the other scenes. + diff --git a/bin/TODO.txt b/bin/TODO.txt index 9237602..3eed7a6 100644 --- a/bin/TODO.txt +++ b/bin/TODO.txt @@ -1,8 +1,32 @@ - [x] basic gltf inspector - [x] with animation previews - - [ ] inspect specific models - - [ ] Use gltf camera + - [x] inspect specific models + - [x] Use gltf camera - [x] basic text inspector - [x] with simple text animation -- [ ] audio inspector +- [x] audio inspector - [x] debug info (FPS) + +The Big Kahuna: +> Game editor for creating levels +- [ ] Drag and Drop to import: + - [ ] Gltf scenes + - [ ] Audio assets + - [ ] Scripts (format TBD) +- [ ] UI + - [ ] Navigate GLTFs + - [ ] Scenes + - [ ] Cameras + - [ ] Animations +- [ ] Monologue Scripting + - [ ] Create/edit scripts +- [ ] Model tweaking + - [ ] Toggle multiple GLTF assets + - [ ] Move/Rotate/Resize scene +- [ ] Level creation + - [ ] Import assets 1+ times + - [ ] "Use" imported asset + - [ ] "Delete" imported asset +- [ ] Preview a level +- [ ] Export level +- [ ] Import level diff --git a/bin/editor.rs b/bin/editor.rs new file mode 100644 index 0000000..fdab4be --- /dev/null +++ b/bin/editor.rs @@ -0,0 +1,142 @@ +// Monologue Trees Editor +// +// Editor for creating Monologue Trees levels + +use bevy::{ + input::{keyboard::KeyboardInput, ButtonState}, + prelude::*, +}; +use monologue_trees::{debug::*, ui::*}; + +fn main() { + App::new() + .add_plugins(( + DefaultPlugins.set(WindowPlugin { + primary_window: Some(Window { + title: "Monologue Trees Editor".into(), + resolution: (640., 480.).into(), + ..default() + }), + ..default() + }), + DebugInfoPlugin, + GameUiPlugin, + )) + .add_systems(Startup, (initialize_ui,)) + .add_systems( + Update, + ( + load_gltf, + unload_gltf, + load_audio, + unload_audio, + spawn_scene, + play_audio, + export_level, + import_level, + load_bogus, + ), + ) + .run(); +} + +/// UI: +/// * GLTFs +/// * Scenes +/// * Cameras +/// * Animations +/// * Audios +fn initialize_ui(mut commands: Commands) { + commands.spawn(( + Camera2dBundle { ..default() }, + UiCameraConfig { show_ui: true }, + )); + + commands + .spawn(NodeBundle { + style: Style { + width: Val::Percent(100.0), + height: Val::Percent(100.0), + ..default() + }, + ..default() + }) + .with_children(|parent| { + parent.spawn((GameUiList("GLTFs"), NodeBundle { ..default() }, GltfsUi)); + parent.spawn((GameUiList("Scenes"), NodeBundle { ..default() }, ScenesUi)); + parent.spawn((GameUiList("Cameras"), NodeBundle { ..default() }, CamerasUi)); + parent.spawn(( + GameUiList("Animations"), + NodeBundle { ..default() }, + AnimationsUi, + )); + parent.spawn(( + GameUiSet("Audio Clips"), + NodeBundle { ..default() }, + AudioClipsUi, + )); + }); +} + +fn load_bogus( + mut events: EventReader, + root: Query>, + mut commands: Commands, +) { + events + .iter() + .filter( + |&KeyboardInput { + key_code, state, .. + }| *key_code == Some(KeyCode::Space) && *state == ButtonState::Pressed, + ) + .for_each(|_| { + commands + .spawn((GameUiButton("bogus"), NodeBundle { ..default() })) + .set_parent(root.single()); + }) +} + +/// Component marking UI for loaded Gltf assets +#[derive(Component)] +struct GltfsUi; + +/// Component marking UI for Scene assets +#[derive(Component)] +struct ScenesUi; + +/// Component marking UI for Camera assets +#[derive(Component)] +struct CamerasUi; + +/// Component marking UI for Animation assets +#[derive(Component)] +struct AnimationsUi; + +/// Drag+Drop import GLTF to editor +fn load_gltf() {} + +/// Remove gltf from editor +fn unload_gltf() {} + +/// Component marking UI for Audio Clip assets +#[derive(Component)] +struct AudioClipsUi; + +/// Drag+Drop import Audio to editor +fn load_audio() {} + +/// Remove audio from editor +fn unload_audio() {} + +/// Spawn Scene +fn spawn_scene() {} + +/// Play/Loop Audio +fn play_audio() {} + +/// Export level +fn export_level() {} + +/// Import Level +fn import_level() {} diff --git a/src/lib.rs b/src/lib.rs index 5670b85..5884986 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ pub mod debug; pub mod text; + +pub mod ui; diff --git a/src/ui.rs b/src/ui.rs new file mode 100644 index 0000000..bbc4717 --- /dev/null +++ b/src/ui.rs @@ -0,0 +1,117 @@ +use bevy::{prelude::*, window::PrimaryWindow}; + +pub struct GameUiPlugin; + +impl Plugin for GameUiPlugin { + fn build(&self, app: &mut App) { + app.add_systems( + Update, + ( + manage_ui_list, + manage_ui_set, + manage_ui_button, + manage_cursor, + ), + ); + } +} + +/// GameUiList for holding ordered collections of objects +#[derive(Debug, Component)] +pub struct GameUiList(pub &'static str); + +/// Manage UI Lists: lists of UI entities. +fn manage_ui_list(events: Query<(Entity, &GameUiList), Added>, mut commands: Commands) { + events.iter().for_each(|(entity, ui_list)| { + info!("Expanding UI List {:?}", ui_list); + commands.entity(entity).insert(NodeBundle { + style: Style { + width: Val::Px(100.0), + margin: UiRect::all(Val::Px(2.0)), + padding: UiRect::all(Val::Px(2.0)), + border: UiRect::all(Val::Px(2.0)), + flex_direction: FlexDirection::Column, + align_items: AlignItems::Stretch, + justify_items: JustifyItems::Center, + align_content: AlignContent::FlexStart, + ..default() + }, + background_color: BackgroundColor(Color::RED), + border_color: BorderColor(Color::BLACK), + ..default() + }); + }); +} + +/// GameUiSet Component for holding collections of objects +#[derive(Debug, Component)] +pub struct GameUiSet(pub &'static str); + +/// Manage UI Sets: collections of UI entities. +fn manage_ui_set(events: Query<(Entity, &GameUiSet), Added>, mut commands: Commands) { + events.iter().for_each(|(entity, ui_set)| { + info!("Expanding UI Set {:?}", ui_set); + commands.entity(entity).insert(NodeBundle { + style: Style { + width: Val::Px(100.0), + margin: UiRect::all(Val::Px(2.0)), + padding: UiRect::all(Val::Px(2.0)), + border: UiRect::all(Val::Px(2.0)), + align_items: AlignItems::FlexStart, + align_content: AlignContent::FlexStart, + flex_direction: FlexDirection::Row, + flex_wrap: FlexWrap::Wrap, + ..default() + }, + background_color: BackgroundColor(Color::BLUE), + border_color: BorderColor(Color::BLACK), + ..default() + }); + }); +} + +/// GameUiButton for interactive elements +#[derive(Debug, Component)] +pub struct GameUiButton(pub &'static str); + +/// Manage UI Buttons. interactive buttons. +fn manage_ui_button( + events: Query<(Entity, &GameUiButton), Added>, + mut commands: Commands, +) { + events.iter().for_each(|(entity, ui_button)| { + info!("Expanding UI Button {:?}", ui_button); + commands + .entity(entity) + .insert(ButtonBundle { + style: Style { + margin: UiRect::all(Val::Px(2.0)), + padding: UiRect::all(Val::Px(2.0)), + border: UiRect::all(Val::Px(2.0)), + justify_content: JustifyContent::Center, + ..default() + }, + background_color: BackgroundColor(Color::GREEN), + border_color: BorderColor(Color::BLACK), + ..default() + }) + .with_children(|parent| { + parent.spawn(TextBundle::from_section(ui_button.0, TextStyle::default())); + }); + }); +} + +/// Manage the cursor icon for better immersion +fn manage_cursor( + mut primary_window: Query<&mut Window, With>, + events: Query<&Interaction, With>, +) { + events.iter().for_each(|event| { + let mut window = primary_window.single_mut(); + window.cursor.icon = match event { + Interaction::Pressed => CursorIcon::Grabbing, + Interaction::Hovered => CursorIcon::Hand, + Interaction::None => CursorIcon::Default, + } + }); +}