Compare commits

...

7 Commits

960
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -39,10 +39,14 @@ path = "bin/editor.rs"
name = "chars" name = "chars"
path = "bin/chars.rs" path = "bin/chars.rs"
[[bin]]
name = "serialize-wtf"
path = "bin/serialize-wtf.rs"
[dependencies] [dependencies]
bevy = "0.11" bevy = "0.11"
bevy_rapier3d = "*" # bevy_rapier3d = "*"
serde = "1" serde = "1"
# From rapier docs # From rapier docs

BIN
assets/models/FlightHelmet.bin (Stored with Git LFS)

Binary file not shown.

BIN
assets/models/FlightHelmet.gltf (Stored with Git LFS)

Binary file not shown.

BIN
assets/models/materials.blend (Stored with Git LFS)

Binary file not shown.

BIN
assets/models/materials.blend1 (Stored with Git LFS)

Binary file not shown.

@ -0,0 +1,100 @@
(
resources: {},
entities: {
1: (
components: {
"bevy_render::view::visibility::Visibility": Inherited,
"bevy_transform::components::transform::Transform": (
translation: (
x: 0.0,
y: 0.0,
z: 0.0,
),
rotation: (0.0, 0.0, 0.0, 1.0),
scale: (
x: 1.0,
y: 1.0,
z: 1.0,
),
),
"bevy_transform::components::global_transform::GlobalTransform": ((
matrix3: (
x_axis: (
x: 1.0,
y: 0.0,
z: 0.0,
),
y_axis: (
x: 0.0,
y: 1.0,
z: 0.0,
),
z_axis: (
x: 0.0,
y: 0.0,
z: 1.0,
),
),
translation: (
x: 0.0,
y: 0.0,
z: 0.0,
),
)),
"editor::LevelRoot": (),
"bevy_hierarchy::components::children::Children": ([
198,
]),
},
),
198: (
components: {
"bevy_render::view::visibility::Visibility": Inherited,
"bevy_transform::components::transform::Transform": (
translation: (
x: 0.0,
y: 0.0,
z: 0.0,
),
rotation: (0.0, 0.0, 0.0, 1.0),
scale: (
x: 1.0,
y: 1.0,
z: 1.0,
),
),
"bevy_transform::components::global_transform::GlobalTransform": ((
matrix3: (
x_axis: (
x: 1.0,
y: 0.0,
z: 0.0,
),
y_axis: (
x: 0.0,
y: 1.0,
z: 0.0,
),
z_axis: (
x: 0.0,
y: 0.0,
z: 1.0,
),
),
translation: (
x: 0.0,
y: 0.0,
z: 0.0,
),
)),
"bevy_hierarchy::components::parent::Parent": (1),
"bevy_hierarchy::components::children::Children": ([
199,
]),
"bevy_asset::handle::Handle<bevy_scene::scene::Scene>": (
id: AssetPathId(((13241355290950327508), (2370051114748836591))),
),
},
),
},
)

@ -3,30 +3,15 @@
// Editor for creating Monologue Trees levels // Editor for creating Monologue Trees levels
// //
// BUGS: // BUGS:
// * Camera order ambiguity
// * Load new GLTF -> Despawn all level entities
// //
// TODO: // TODO:
// * Disable auto start/load // * edit textbox with actions
// * Better handle hide/close monologue
// * (hard) Harden Active Camera
// * (medium) Despawn entire scene when GLTF changed?
// * (medium) Select Font -> "Default Font" Resource
// * (medium) Pre-compute animation target entities
// * (medium) Animation buttons only visible when playable
// * (easy) Clear button to wipe spawned scene
// * (brutal) export level // * (brutal) export level
// * (hard) import level // * (hard) import level
// // * (hard) Harden Active Camera
// Asset types: // * (medium) Pre-compute animation target entities
// * Audios (done) // * make min/max/close buttons into actions not selects
// * Loop individual (done) // * (???) Better handle hide/close monologue
// * Gltfs (doing)
// * Scenes
// * Animations
// * Play/Pause all
// * Fonts (done)
// * Monologues (done)
use bevy::{ use bevy::{
asset::{Asset, AssetLoader, Assets, ChangeWatcher, LoadContext, LoadedAsset}, asset::{Asset, AssetLoader, Assets, ChangeWatcher, LoadContext, LoadedAsset},
@ -39,9 +24,8 @@ use monologue_trees::{debug::*, ui};
const WELCOME_MESSAGES: &'static [&'static str] = &[ const WELCOME_MESSAGES: &'static [&'static str] = &[
"Welcome to the Monologue Trees editor!", "Welcome to the Monologue Trees editor!",
"Import assets by dragging and dropping files or folders into the editor!",
concat!( concat!(
"Import assets by dragging and dropping files into the editor\n",
"\n",
"Supported file types (for now):\n", "Supported file types (for now):\n",
"* 3D: .gltf, .glb\n", "* 3D: .gltf, .glb\n",
"* Audio: .ogg\n", "* Audio: .ogg\n",
@ -67,13 +51,15 @@ fn main() {
..default() ..default()
}), }),
DebugInfoPlugin, DebugInfoPlugin,
ui::GameUiPlugin, ui::GameUiPlugin {
enable_alerts: true,
},
)) ))
.register_type::<LevelRoot>()
.init_resource::<AssetRegistry>() .init_resource::<AssetRegistry>()
.init_resource::<FontInfo>()
.add_asset::<Monologue>() .add_asset::<Monologue>()
.init_asset_loader::<MonologueLoader>() .init_asset_loader::<MonologueLoader>()
.add_event::<CustomAssetEvent<Scene>>()
.add_event::<CustomAssetEvent<AnimationClip>>()
.add_systems(Startup, (initialize_ui, init_texts_ui, welcome_message)) .add_systems(Startup, (initialize_ui, init_texts_ui, welcome_message))
.add_systems( .add_systems(
Update, Update,
@ -89,26 +75,25 @@ fn main() {
Update, Update,
(remove_scenes_ui, add_scenes_ui, control_active_scenes), (remove_scenes_ui, add_scenes_ui, control_active_scenes),
) )
.add_systems(Update, (cameras_ui, manage_active_camera, fallback_camera))
.add_systems(Update, (audio_ui, play_audio, pause_audio))
.add_systems( .add_systems(
Update, Update,
( (
cameras_ui, gltf_ui,
manage_active_camera, texts_ui,
control_active_camera, control_active_gltf,
fallback_camera, show_preview_text,
sync_monologue_font,
), ),
) )
.add_systems(Update, (audio_ui, play_audio)) .add_systems(Update, (fonts_ui, set_active_font))
.add_systems(Startup, reload_assets)
.add_systems( .add_systems(
Update, Update,
( (
import_files, reload_assets.run_if(ui::activated::<ReloadAssets>),
gltf_ui, clear_assets.run_if(ui::activated::<ClearAssets>),
fonts_ui,
texts_ui,
manage_active_gltf,
show_preview_text,
sync_monologue_font,
), ),
) )
.add_systems( .add_systems(
@ -119,23 +104,27 @@ fn main() {
directional_light_force_shadows, directional_light_force_shadows,
), ),
) )
.add_systems(Update, clear_level)
.add_systems(
Update,
(
level_ui,
load_level,
export_level.run_if(ui::activated::<ExportLevel>),
rehydrate_level,
),
)
.run(); .run();
} }
#[derive(Resource, Default)] #[derive(Resource, Default)]
pub struct AssetRegistry(Vec<HandleUntyped>); pub struct AssetRegistry(Vec<HandleUntyped>);
#[derive(Event)]
pub enum CustomAssetEvent<T: Asset> {
Add { handle: Handle<T>, name: String },
Remove { handle: Handle<T> },
Clear,
}
#[derive(Debug, Component)] #[derive(Debug, Component)]
pub struct TabRoot; pub struct TabRoot;
#[derive(Debug, Component)] #[derive(Debug, Component, Reflect, Default)]
#[reflect(Component)]
pub struct LevelRoot; pub struct LevelRoot;
#[derive(Debug, Component)] #[derive(Debug, Component)]
@ -143,7 +132,7 @@ pub struct EditorCamera;
fn initialize_ui(mut commands: Commands) { fn initialize_ui(mut commands: Commands) {
// Empty entity for populating the level being edited // Empty entity for populating the level being edited
commands.spawn((SpatialBundle { ..default() }, LevelRoot)); commands.spawn((SpatialBundle { ..default() }, LevelRoot::default()));
commands.spawn(( commands.spawn((
Camera2dBundle { ..default() }, Camera2dBundle { ..default() },
@ -161,6 +150,15 @@ fn initialize_ui(mut commands: Commands) {
..default() ..default()
}; };
let simple_button = ButtonBundle {
style: Style {
..base_style.clone()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
..default()
};
commands commands
.spawn(NodeBundle { .spawn(NodeBundle {
style: Style { style: Style {
@ -232,6 +230,11 @@ fn initialize_ui(mut commands: Commands) {
parent, parent,
ui::Select::Multi, ui::Select::Multi,
)); ));
content_containers.push(spawn_tab_container::<LevelWidget>(
"Level",
parent,
ui::Select::Single,
));
content_containers.push(spawn_tab_container::<GltfWidget>( content_containers.push(spawn_tab_container::<GltfWidget>(
"Gltf", "Gltf",
parent, parent,
@ -274,24 +277,19 @@ fn initialize_ui(mut commands: Commands) {
ui::Select::Single, ui::Select::Single,
)) ))
.with_children(|parent| { .with_children(|parent| {
let b = ButtonBundle { content_containers.iter().enumerate().for_each(
style: Style { |(i, (name, target))| {
..base_style.clone() parent.spawn((
simple_button.clone(),
ui::Title {
text: name.clone(),
..default()
},
ui::Collapse { target: *target },
ui::Sorting(i as u8),
));
}, },
background_color: Color::WHITE.into(), );
border_color: Color::BLACK.into(),
..default()
};
content_containers.iter().for_each(|(name, target)| {
parent.spawn((
b.clone(),
ui::Title {
text: name.clone(),
..default()
},
ui::Collapse { target: *target },
));
});
}); });
}) })
.id(); .id();
@ -305,6 +303,91 @@ fn initialize_ui(mut commands: Commands) {
ui::Sorting(0), ui::Sorting(0),
)); ));
}); });
commands
.spawn(NodeBundle {
style: Style {
bottom: Val::Px(0.0),
left: Val::Px(0.0),
position_type: PositionType::Absolute,
border: UiRect::all(Val::Px(1.0)),
margin: UiRect::all(Val::Px(1.0)),
padding: UiRect::all(Val::Px(1.0)),
flex_direction: FlexDirection::Column,
overflow: Overflow::clip(),
..default()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
..default()
})
.with_children(|parent| {
let container = parent
.spawn((
NodeBundle {
style: Style {
border: UiRect::all(Val::Px(1.0)),
margin: UiRect::all(Val::Px(1.0)),
padding: UiRect::all(Val::Px(1.0)),
flex_direction: FlexDirection::Column,
overflow: Overflow::clip(),
..default()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
..default()
},
ui::Sorting(99),
ui::Select::Action,
))
.with_children(|parent| {
parent.spawn((
simple_button.clone(),
ReloadAssets,
ui::Sorting(1),
ui::Title {
text: "Reload Assets".into(),
..default()
},
));
parent.spawn((
simple_button.clone(),
ClearAssets,
ui::Sorting(2),
ui::Title {
text: "Clear Assets".into(),
..default()
},
));
parent.spawn((
simple_button.clone(),
ExportLevel,
ui::Sorting(3),
ui::Title {
text: "Export Level".into(),
..default()
},
));
parent.spawn((
simple_button.clone(),
ClearLevel,
ui::Sorting(3),
ui::Title {
text: "Reset Level".into(),
..default()
},
));
})
.id();
parent.spawn((
ui::TitleBarBase::new(Color::WHITE).bundle(),
ui::Title {
text: "Actions".into(),
..default()
},
ui::Minimize { target: container },
ui::Sorting(0),
));
});
} }
fn welcome_message(mut writer: EventWriter<ui::Alert>) { fn welcome_message(mut writer: EventWriter<ui::Alert>) {
@ -345,27 +428,6 @@ fn spawn_tab_container<T: Default + Component>(
) )
} }
fn import_files(
mut events: EventReader<FileDragAndDrop>,
server: Res<AssetServer>,
mut registry: ResMut<AssetRegistry>,
) {
events.iter().for_each(|event| match event {
FileDragAndDrop::DroppedFile { path_buf, .. } => {
registry.0.push(
server.load_untyped(
path_buf
.clone()
.into_os_string()
.into_string()
.expect("Path converts to string"),
),
);
}
_ => (),
})
}
use audio::*; use audio::*;
mod audio { mod audio {
@ -442,29 +504,41 @@ mod audio {
}); });
} }
pub fn play_audio( pub fn play_audio(events: Query<&AudioSink, (With<Button>, Added<ui::Active>)>) {
events: Query<(Entity, &Interaction, &AudioSink), (With<Button>, Changed<Interaction>)>, events.iter().for_each(|sink| {
mut commands: Commands, sink.play();
) { });
}
pub fn pause_audio(mut events: RemovedComponents<ui::Active>, sinks: Query<&AudioSink>) {
events events
.iter() .iter()
.filter(|(_, &interaction, _)| interaction == Interaction::Pressed) .filter_map(|entity| sinks.get(entity).ok())
.for_each(|(entity, _, sink)| { .for_each(|sink| sink.stop());
sink.toggle();
if sink.is_paused() {
commands.entity(entity).remove::<ui::Active>();
} else {
commands.entity(entity).insert(ui::Active);
}
});
} }
} }
use assets::*; use assets::*;
mod assets { mod assets {
use super::*; use super::*;
#[derive(Debug, Component)]
pub struct ReloadAssets;
pub fn reload_assets(
server: Res<AssetServer>,
mut registry: ResMut<AssetRegistry>,
mut writer: EventWriter<ui::Alert>,
) {
match server.load_folder(".") {
Ok(handles) => registry.0 = handles,
Err(e) => writer.send(ui::Alert::Warn(format!(
"Could not find `assets` folder!\n{:?}",
e
))),
}
}
pub fn get_asset_name<T: Asset>(server: &AssetServer, handle: Handle<T>) -> String { pub fn get_asset_name<T: Asset>(server: &AssetServer, handle: Handle<T>) -> String {
if let Some(asset_path) = server.get_handle_path(handle.clone()) { if let Some(asset_path) = server.get_handle_path(handle.clone()) {
if let Some(stem) = asset_path.path().file_stem() { if let Some(stem) = asset_path.path().file_stem() {
@ -578,7 +652,6 @@ mod gltf {
#[derive(Debug, Component, Default)] #[derive(Debug, Component, Default)]
pub struct GltfWidget; pub struct GltfWidget;
// TODO: Mark selected gltf as active ~single exclusive~
pub fn gltf_ui( pub fn gltf_ui(
mut events: EventReader<AssetEvent<Gltf>>, mut events: EventReader<AssetEvent<Gltf>>,
mut commands: Commands, mut commands: Commands,
@ -631,28 +704,17 @@ mod gltf {
}); });
} }
pub fn manage_active_gltf( pub fn control_active_gltf(
events: Query< events: Query<Entity, (With<ui::TargetAsset<Gltf>>, Added<ui::Active>)>,
(Entity, &Interaction, Option<&ui::Active>), root: Query<Entity, With<LevelRoot>>,
(With<Button>, Changed<Interaction>),
>,
mut commands: Commands, mut commands: Commands,
) { ) {
events events.iter().for_each(|_| {
.iter() commands.entity(root.single()).despawn_descendants();
.filter(|(_, &interaction, _)| interaction == Interaction::Pressed) });
.for_each(|(entity, _, active_ish)| match active_ish {
Some(_) => {
commands.entity(entity).remove::<ui::Active>();
}
None => {
commands.entity(entity).insert(ui::Active);
}
});
} }
} }
// TODO: Mark loaded animation as active
use scenes::*; use scenes::*;
mod scenes { mod scenes {
use super::*; use super::*;
@ -746,8 +808,6 @@ mod scenes {
} }
} }
// TODO: Play all animations
// TODO: Mark playing animation as active
use animations::*; use animations::*;
mod animations { mod animations {
use super::*; use super::*;
@ -786,6 +846,7 @@ mod animations {
} }
/// When a new scene is loaded, add any newly compatible animations /// When a new scene is loaded, add any newly compatible animations
/// TODO: Add target entity(s) too
pub fn add_animations_ui( pub fn add_animations_ui(
player_spawned: Query<&Name, Added<AnimationPlayer>>, player_spawned: Query<&Name, Added<AnimationPlayer>>,
widget: Query<Entity, With<AnimationWidget>>, widget: Query<Entity, With<AnimationWidget>>,
@ -822,7 +883,6 @@ mod animations {
// When a scene is de-selected, remove any outdated animation options // When a scene is de-selected, remove any outdated animation options
pub fn remove_animations_ui( pub fn remove_animations_ui(
mut removed_players: RemovedComponents<Handle<Scene>>, mut removed_players: RemovedComponents<Handle<Scene>>,
names: Query<&Name>,
current: Query<(Entity, &ui::TargetAsset<AnimationClip>)>, current: Query<(Entity, &ui::TargetAsset<AnimationClip>)>,
clips: Res<Assets<AnimationClip>>, clips: Res<Assets<AnimationClip>>,
targets: Query<(&AnimationPlayer, &Name)>, targets: Query<(&AnimationPlayer, &Name)>,
@ -921,6 +981,11 @@ mod fonts {
#[derive(Debug, Component, Default)] #[derive(Debug, Component, Default)]
pub struct FontWidget; pub struct FontWidget;
#[derive(Debug, Resource, Default)]
pub struct FontInfo {
pub default: Option<Handle<Font>>,
}
pub fn fonts_ui( pub fn fonts_ui(
mut events: EventReader<AssetEvent<Font>>, mut events: EventReader<AssetEvent<Font>>,
mut commands: Commands, mut commands: Commands,
@ -972,6 +1037,15 @@ mod fonts {
} }
}); });
} }
pub fn set_active_font(
events: Query<&ui::TargetAsset<Font>, Added<ui::Active>>,
mut font: ResMut<FontInfo>,
) {
events
.iter()
.for_each(|ui::TargetAsset { handle }| font.default = Some(handle.clone()));
}
} }
use monologues::*; use monologues::*;
@ -1095,6 +1169,7 @@ mod monologues {
monologues: Res<Assets<Monologue>>, monologues: Res<Assets<Monologue>>,
container: Query<Entity, With<MonologueContainer>>, container: Query<Entity, With<MonologueContainer>>,
mut commands: Commands, mut commands: Commands,
font: Res<FontInfo>,
) { ) {
added added
.iter() .iter()
@ -1131,15 +1206,21 @@ mod monologues {
}, },
ui::Sorting(0), ui::Sorting(0),
)); ));
let style = match &font.default {
Some(handle) => TextStyle {
color: Color::BLACK.into(),
font_size: 16.0,
font: handle.clone(),
..default()
},
None => TextStyle {
color: Color::BLACK.into(),
font_size: 16.0,
..default()
},
};
parent.spawn(( parent.spawn((
TextBundle::from_section( TextBundle::from_section(monologue.text.clone(), style),
monologue.text.clone(),
TextStyle {
color: Color::BLACK.into(),
font_size: 16.0,
..default()
},
),
handle.clone(), handle.clone(),
)); ));
}); });
@ -1149,16 +1230,19 @@ mod monologues {
// TODO: Sync Handle<Monologue> and TextStyle components to automagically generate and sync text // TODO: Sync Handle<Monologue> and TextStyle components to automagically generate and sync text
pub fn sync_monologue_font( pub fn sync_monologue_font(
events: Query<&ui::TargetAsset<Font>, Added<ui::Active>>,
mut texts: Query<&mut Text, With<Handle<Monologue>>>, mut texts: Query<&mut Text, With<Handle<Monologue>>>,
font: Res<FontInfo>,
) { ) {
events.iter().for_each(|ui::TargetAsset { handle }| { if font.is_changed() || font.is_added() {
texts.iter_mut().for_each(|mut text| { texts.iter_mut().for_each(|mut text| {
text.sections.iter_mut().for_each(|section| { text.sections
section.style.font = handle.clone(); .iter_mut()
}); .for_each(|section| match &font.default {
Some(handle) => section.style.font = handle.clone(),
None => section.style.font = Handle::default(),
});
}); });
}); }
} }
} }
@ -1170,8 +1254,9 @@ mod cameras {
pub struct CameraWidget; pub struct CameraWidget;
pub fn cameras_ui( pub fn cameras_ui(
mut added: Query<(Entity, &mut Camera, &Name), (Added<Camera>, Without<EditorCamera>)>, mut added: Query<(Entity, &mut Camera, &Name), Added<Camera>>,
mut removed: RemovedComponents<Camera>, mut removed: RemovedComponents<Camera>,
editor_camera: Query<Entity, With<EditorCamera>>,
widget: Query<Entity, With<CameraWidget>>, widget: Query<Entity, With<CameraWidget>>,
current: Query<(Entity, &ui::TargetEntity)>, current: Query<(Entity, &ui::TargetEntity)>,
mut commands: Commands, mut commands: Commands,
@ -1188,47 +1273,25 @@ mod cameras {
ui::TargetEntity { entity }, ui::TargetEntity { entity },
name.as_str().into(), name.as_str().into(),
); );
camera.is_active = false; camera.is_active = entity == editor_camera.single();
}); });
} }
/// Set the camera active component based on button clicks /// Set the camera active component based on button clicks
pub fn manage_active_camera( pub fn manage_active_camera(
events: Query<(&Interaction, &ui::TargetEntity), Changed<Interaction>>, events: Query<&ui::TargetEntity, Added<ui::Active>>,
cameras: Query<Entity, With<Camera>>, mut cameras: Query<(Entity, &mut Camera)>,
mut commands: Commands,
) { ) {
events events.iter().for_each(|ui::TargetEntity { entity }| {
.iter() cameras.iter_mut().for_each(|(this_entity, mut camera)| {
.filter(|(&interaction, _)| interaction == Interaction::Pressed) if this_entity == *entity {
.for_each(|(_, ui::TargetEntity { entity })| { info!("Marking {:?} as active camera", entity);
cameras.iter().for_each(|this_entity| { camera.is_active = true;
if this_entity == *entity { } else {
info!("Marking {:?} as active camera", entity); info!("Marking {:?} as inactive camera", entity);
commands.entity(this_entity).insert(ui::Active); camera.is_active = false;
} else { }
info!("Marking {:?} as inactive camera", entity);
commands.entity(this_entity).remove::<ui::Active>();
}
});
}); });
}
/// Set the active camera based on the Active marker component
pub fn control_active_camera(
added: Query<Entity, (Added<ui::Active>, With<Camera>)>,
mut removed: RemovedComponents<ui::Active>,
mut cameras: Query<&mut Camera>,
) {
removed.iter().for_each(|entity| {
if let Ok(mut camera) = cameras.get_mut(entity) {
camera.is_active = false;
}
});
added.iter().for_each(|entity| {
if let Ok(mut camera) = cameras.get_mut(entity) {
camera.is_active = true;
}
}); });
} }
@ -1274,3 +1337,226 @@ mod lighting {
}) })
} }
} }
use reset::*;
mod reset {
use super::*;
#[derive(Debug, Component)]
pub struct ClearLevel;
pub fn clear_level(
events: Query<Entity, (With<ClearLevel>, Added<ui::Active>)>,
actives: Query<
Entity,
(
With<ui::Active>,
Or<(
With<ui::TargetEntity>,
With<ui::TargetAsset<Gltf>>,
With<ui::TargetAsset<Scene>>,
With<ui::TargetAsset<AnimationClip>>,
With<ui::TargetAsset<AudioSource>>,
With<ui::TargetAsset<Monologue>>,
With<ui::TargetAsset<Font>>,
)>,
),
>,
root: Query<Entity, With<LevelRoot>>,
mut commands: Commands,
) {
events.iter().for_each(|_| {
actives.iter().for_each(|entity| {
commands.entity(entity).remove::<ui::Active>();
});
commands.entity(root.single()).despawn_descendants();
})
}
#[derive(Debug, Component)]
pub struct ClearAssets;
pub fn clear_assets(
asset_holders: Query<
Entity,
Or<(
With<ui::TargetAsset<Gltf>>,
With<Handle<Gltf>>,
With<ui::TargetAsset<Scene>>,
With<Handle<Scene>>,
With<ui::TargetAsset<AnimationClip>>,
With<Handle<AnimationClip>>,
With<ui::TargetAsset<AudioSource>>,
With<Handle<AudioSource>>,
With<ui::TargetAsset<Monologue>>,
With<Handle<Monologue>>,
With<ui::TargetAsset<Font>>,
With<Handle<Font>>,
)>,
>,
mut registry: ResMut<AssetRegistry>,
mut commands: Commands,
) {
info!("Clearing assets");
// Clear buttons holding asset references
asset_holders
.iter()
.for_each(|entity| commands.entity(entity).despawn_recursive());
// Empty asset registry
registry.0.clear();
}
}
pub use level::*;
mod level {
use std::fs::File;
use std::io::Write;
use bevy::tasks::IoTaskPool;
use super::*;
type Level = DynamicScene;
#[derive(Debug, Component, Default)]
pub struct LevelWidget;
#[derive(Debug, Component)]
pub struct ExportLevel;
pub fn level_ui(
mut events: EventReader<AssetEvent<Level>>,
mut commands: Commands,
widget: Query<Entity, With<LevelWidget>>,
current: Query<(Entity, &ui::TargetAsset<Level>)>,
server: Res<AssetServer>,
) {
events.iter().for_each(|event| match event {
AssetEvent::Created { handle } => {
info!("Asset created! {:?}", event);
create_asset_button(
&widget,
&mut commands,
ui::TargetAsset {
handle: handle.clone(),
},
get_asset_name(&server, handle.clone()),
None,
);
}
AssetEvent::Removed { handle } => {
info!("Asset removed! {:?}", event);
destroy_asset_button(
&current,
&mut commands,
&ui::TargetAsset {
handle: handle.clone(),
},
);
}
AssetEvent::Modified { handle } => {
info!("Asset modified! {:?}", event);
destroy_asset_button(
&current,
&mut commands,
&ui::TargetAsset {
handle: handle.clone(),
},
);
create_asset_button(
&widget,
&mut commands,
ui::TargetAsset {
handle: handle.clone(),
},
get_asset_name(&server, handle.clone()),
None,
);
}
});
}
pub fn load_level(
events: Query<
&ui::TargetAsset<DynamicScene>,
(Added<ui::Active>, With<ui::TargetAsset<DynamicScene>>),
>,
root: Query<Entity, With<LevelRoot>>,
mut commands: Commands,
) {
events.iter().for_each(|ui::TargetAsset { handle }| {
commands.entity(root.single()).despawn_recursive();
commands.spawn(DynamicSceneBundle {
scene: handle.clone(),
..default()
});
});
}
pub fn export_level(
root: Query<Entity, With<LevelRoot>>,
children: Query<&Children>,
world: &World,
) {
let app_type_registry = world.resource::<AppTypeRegistry>().clone();
let mut builder = DynamicSceneBuilder::from_world(world.clone());
builder.deny_all_resources();
// builder.allow_all();
builder.deny::<ComputedVisibility>();
// Level administrivia
builder.allow::<LevelRoot>();
// Scene components
builder.allow::<Handle<Scene>>();
builder.allow::<Visibility>();
builder.allow::<Transform>();
builder.allow::<GlobalTransform>();
// Audio components
builder.allow::<Handle<AudioSource>>();
builder.allow::<PlaybackSettings>();
root.iter().for_each(|r| {
// Extract the level root
builder.extract_entity(r);
match children.get(r) {
Ok(cs) => {
builder.extract_entities(cs.iter().map(|&entity| entity));
}
Err(e) => warn!("Empty level! {:?}", e),
}
});
let scene = builder.build();
let serialized = scene
.serialize_ron(&app_type_registry)
.expect("Serialize scene");
IoTaskPool::get()
.spawn(async move {
// Write the scene RON data to file
File::create(format!("assets/output.scn.ron"))
.and_then(|mut file| file.write(serialized.as_bytes()))
.expect("Error while writing scene to file");
})
.detach();
}
pub fn rehydrate_level(
events: Query<Entity, (Added<Visibility>, Without<ComputedVisibility>)>,
mut commands: Commands,
) {
events.iter().for_each(|entity| {
commands
.entity(entity)
.insert(ComputedVisibility::default());
});
}
}

@ -0,0 +1,105 @@
// TODO:
// * Determine where local assets folder needs to be created
// * Create local assets folder
// * Recusrively laod that folder and watch for changes
// * Copy assets into local folder in leue of importing them
// * Check portability by moving binary + folder to new location
use std::time::Duration;
use bevy::{asset::ChangeWatcher, prelude::*};
use monologue_trees::{debug::*, ui};
fn main() {
App::new()
.add_plugins((
DefaultPlugins
.set(WindowPlugin {
primary_window: Some(Window {
title: "Serialization WTF".into(),
resolution: (640., 480.).into(),
..default()
}),
..default()
})
.set(AssetPlugin {
asset_folder: "assets".into(),
// Tell the asset server to watch for asset changes on disk:
watch_for_changes: ChangeWatcher::with_delay(Duration::from_millis(0)),
..default()
}),
DebugInfoPlugin,
ui::GameUiPlugin { ..default() },
))
.register_type::<LevelRoot>()
.register_type::<Other>()
.register_type::<Choice>()
.add_systems(Startup, init)
.add_systems(Update, serialize_debug.run_if(pending))
.run();
}
#[derive(Debug, Component, Default, Reflect)]
#[reflect(Component)]
struct LevelRoot;
#[derive(Debug, Component, Default, Reflect)]
#[reflect(Component)]
struct Other {
inner: String,
}
#[derive(Debug, Component, Default, Reflect)]
#[reflect(Component)]
enum Choice {
#[reflect(default)]
#[default]
One,
Two,
Three,
}
fn init(mut commands: Commands) {
commands.spawn((
SpatialBundle { ..default() },
LevelRoot::default(),
Name::new("Root Entity"),
Other::default(),
Choice::default(),
));
}
fn serialize_debug(query: Query<Entity, With<LevelRoot>>, world: &World) {
let entities = query.iter().collect();
print!("{}", ser(entities, world));
}
fn pending(query: Query<Entity, With<LevelRoot>>, mut done: Local<bool>) -> bool {
if !*done {
if query.iter().len() > 0 {
*done = true;
} else {
*done = false;
}
return *done;
}
return false;
}
fn ser(entities: Vec<Entity>, world: &World) -> String {
let app_type_registry = world.resource::<AppTypeRegistry>().clone();
let scene = {
let mut builder = DynamicSceneBuilder::from_world(world.clone());
builder
.allow_all_resources()
.allow_all()
.deny::<ComputedVisibility>()
.extract_entities(entities.into_iter());
builder.build()
};
scene
.serialize_ron(&app_type_registry)
.expect("Serialize scene")
}

@ -1,9 +1,5 @@
/// TODO: /// TODO:
/// * Text box w/ "reset" button /// * Text box w/ "reset" button
/// * Generic Minimize/Close Components
/// * Title bar w/ min/close controls
/// * Notice/Warning/Error Popups
/// * Textbox (ReadOnly)
/// * Textbox (ReadWrite) /// * Textbox (ReadWrite)
/// * Move code to submodules /// * Move code to submodules
/// ///
@ -25,13 +21,15 @@ pub struct TargetEntity {
pub entity: Entity, pub entity: Entity,
} }
pub struct GameUiPlugin; /// UI Plugin
#[derive(Default)]
pub struct GameUiPlugin {
pub enable_alerts: bool,
}
impl Plugin for GameUiPlugin { impl Plugin for GameUiPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_event::<Alert>() app.add_systems(PreUpdate, create_titles)
.add_systems(Startup, init_alerts)
.add_systems(PreUpdate, create_titles)
.add_systems( .add_systems(
Update, Update,
( (
@ -44,10 +42,15 @@ impl Plugin for GameUiPlugin {
manage_collapse_active, manage_collapse_active,
show_child_number, show_child_number,
manage_sort, manage_sort,
spawn_alert,
), ),
) )
.add_systems(PostUpdate, close_window); .add_systems(PostUpdate, close_window);
if self.enable_alerts {
app.add_event::<Alert>()
.add_systems(Startup, init_alerts)
.add_systems(Update, spawn_alert);
}
} }
} }
@ -249,14 +252,22 @@ mod title {
events.iter().for_each(|(title, note, children)| { events.iter().for_each(|(title, note, children)| {
children.iter().for_each(|child| { children.iter().for_each(|child| {
if let Ok(mut text) = texts.get_mut(*child) { if let Ok(mut text) = texts.get_mut(*child) {
let style = match &title.font {
Some(handle) => TextStyle {
color: Color::BLACK,
font: handle.clone(),
..default()
},
None => TextStyle {
color: Color::BLACK,
..default()
},
};
*text = Text { *text = Text {
sections: vec![ sections: vec![
TextSection { TextSection {
value: title.text.clone(), value: title.text.clone(),
style: TextStyle { style: style,
color: Color::BLACK,
..default()
},
}, },
TextSection { TextSection {
value: note value: note
@ -423,28 +434,63 @@ mod buttons {
pub enum Select { pub enum Select {
Multi, Multi,
Single, Single,
Action,
} }
pub fn manage_select_active( pub fn manage_select_active(
events: Query<(Entity, &Parent), Added<Active>>, events: Query<
children: Query<(&Select, &Children)>, (Entity, &Parent, Option<&Active>, &Interaction),
(With<Button>, Changed<Interaction>),
>,
selects: Query<(&Select, &Children)>,
mut commands: Commands, mut commands: Commands,
) { ) {
events.iter().for_each(|(entity, parent)| { events
if let Ok((select, childs)) = children.get(parent.get()) { .iter()
match select { .for_each(|(entity, parent, active, interaction)| {
Select::Single => { selects
childs .get(parent.get())
.iter() .iter()
.filter(|&child| *child != entity) .for_each(|(select, children)| match interaction {
.for_each(|&child| { Interaction::Pressed => {
commands.entity(child).remove::<Active>(); match active {
}) Some(_) => {
} commands.entity(entity).remove::<Active>();
Select::Multi => (), }
} None => {
} commands.entity(entity).insert(Active);
}); }
}
match select {
Select::Multi | Select::Action => (),
Select::Single => {
children.iter().filter(|&child| *child != entity).for_each(
|&child| {
commands.entity(child).remove::<Active>();
},
);
}
}
}
// A silly hack to get actions to maintain the active tag for 1+ frames
Interaction::None => match select {
Select::Action => {
commands.entity(entity).remove::<Active>();
}
_ => (),
},
_ => (),
});
});
}
/// run_if helper for simple button->trigger action
pub fn activated<T: Component>(events: Query<Entity, (With<T>, Added<Active>)>) -> bool {
events.iter().any(|_| true)
}
pub fn event<T: Event>(mut events: EventReader<T>) -> bool {
events.iter().len() > 0
} }
} }
@ -478,7 +524,7 @@ mod scroll {
self_hover || child_hover || parent_hover self_hover || child_hover || parent_hover
}) })
.for_each(|(_, _, _, mut style)| { .for_each(|(_, _, _, mut style)| {
let factor = 2.0; let factor = 8.0;
let val = match unit { let val = match unit {
MouseScrollUnit::Line => match style.top { MouseScrollUnit::Line => match style.top {
Val::Px(v) => v + (y * factor), Val::Px(v) => v + (y * factor),
@ -562,8 +608,8 @@ pub mod alert {
pub struct AlertsWidget; pub struct AlertsWidget;
pub fn init_alerts(mut commands: Commands) { pub fn init_alerts(mut commands: Commands) {
commands.spawn(( commands
NodeBundle { .spawn(NodeBundle {
style: Style { style: Style {
top: Val::Px(0.0), top: Val::Px(0.0),
right: Val::Px(0.0), right: Val::Px(0.0),
@ -578,9 +624,35 @@ pub mod alert {
}, },
border_color: Color::WHITE.into(), border_color: Color::WHITE.into(),
..default() ..default()
}, })
AlertsWidget, .with_children(|parent| {
)); let container = parent
.spawn((
NodeBundle {
style: Style {
padding: UiRect::all(Val::Px(1.0)),
margin: UiRect::all(Val::Px(1.0)),
border: UiRect::all(Val::Px(1.0)),
flex_direction: FlexDirection::Column,
justify_items: JustifyItems::Center,
..default()
},
border_color: Color::WHITE.into(),
..default()
},
AlertsWidget,
))
.id();
parent.spawn((
TitleBarBase::new(Color::WHITE).bundle(),
Title {
text: "Alerts".into(),
..default()
},
Minimize { target: container },
Sorting(0),
));
});
} }
pub fn spawn_alert( pub fn spawn_alert(

Loading…
Cancel
Save