Compare commits

..

4 Commits

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

Binary file not shown.

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

Binary file not shown.

BIN
assets/models/inspect.glb (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.

@ -4,12 +4,16 @@
// //
// BUGS: // BUGS:
// * Camera order ambiguity // * Camera order ambiguity
// * Multi-GLTF UX is bad. // * Load new GLTF -> Despawn all level entities
// * Consider GLTF hierarchy (GLTF1 > Scene1a/Scene1b, GlTF2 > Scene2a/Scene2b, etc)
// * Easy despawn when de-selecting gltf
// //
// TODO: // TODO:
// * (easy) Play all animations // * Disable auto start/load
// * 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 // * (easy) Clear button to wipe spawned scene
// * (brutal) export level // * (brutal) export level
// * (hard) import level // * (hard) import level
@ -25,12 +29,11 @@
// * Monologues (done) // * Monologues (done)
use bevy::{ use bevy::{
asset::{Asset, Assets}, asset::{Asset, AssetLoader, Assets, ChangeWatcher, LoadContext, LoadedAsset},
asset::{AssetLoader, LoadContext, LoadedAsset},
audio::PlaybackMode, audio::PlaybackMode,
gltf::Gltf, gltf::Gltf,
prelude::*, prelude::*,
utils::BoxedFuture, utils::{BoxedFuture, Duration},
}; };
use monologue_trees::{debug::*, ui}; use monologue_trees::{debug::*, ui};
@ -50,14 +53,19 @@ const WELCOME_MESSAGES: &'static [&'static str] = &[
fn main() { fn main() {
App::new() App::new()
.add_plugins(( .add_plugins((
DefaultPlugins.set(WindowPlugin { DefaultPlugins
primary_window: Some(Window { .set(WindowPlugin {
title: "Monologue Trees Editor".into(), primary_window: Some(Window {
resolution: (640., 480.).into(), title: "Monologue Trees Editor".into(),
resolution: (640., 480.).into(),
..default()
}),
..default()
})
.set(AssetPlugin {
watch_for_changes: ChangeWatcher::with_delay(Duration::from_millis(200)),
..default() ..default()
}), }),
..default()
}),
DebugInfoPlugin, DebugInfoPlugin,
ui::GameUiPlugin, ui::GameUiPlugin,
)) ))
@ -70,16 +78,16 @@ fn main() {
.add_systems( .add_systems(
Update, Update,
( (
manage_gltf_animation_ui,
init_animations_ui, init_animations_ui,
animations_ui, remove_animations_ui,
add_animations_ui,
play_all_animations, play_all_animations,
play_animation, play_animation,
), ),
) )
.add_systems( .add_systems(
Update, Update,
(manage_gltf_scene_ui, scenes_ui, control_active_scenes), (remove_scenes_ui, add_scenes_ui, control_active_scenes),
) )
.add_systems( .add_systems(
Update, Update,
@ -103,6 +111,14 @@ fn main() {
sync_monologue_font, sync_monologue_font,
), ),
) )
.add_systems(
Update,
(
point_light_force_shadows,
spot_light_force_shadows,
directional_light_force_shadows,
),
)
.run(); .run();
} }
@ -224,18 +240,18 @@ fn initialize_ui(mut commands: Commands) {
content_containers.push(spawn_tab_container::<SceneWidget>( content_containers.push(spawn_tab_container::<SceneWidget>(
"Scene", "Scene",
parent, parent,
ui::Select::Multi, ui::Select::Single,
));
content_containers.push(spawn_tab_container::<AnimationWidget>(
"Animation",
parent,
ui::Select::Multi,
)); ));
content_containers.push(spawn_tab_container::<CameraWidget>( content_containers.push(spawn_tab_container::<CameraWidget>(
"Camera", "Camera",
parent, parent,
ui::Select::Single, ui::Select::Single,
)); ));
content_containers.push(spawn_tab_container::<AnimationWidget>(
"Animation",
parent,
ui::Select::Multi,
));
}); });
// Container for tabs that open/close containers // Container for tabs that open/close containers
@ -553,22 +569,6 @@ mod assets {
commands.entity(entity).despawn_recursive(); commands.entity(entity).despawn_recursive();
} }
} }
pub fn has_extensions<T: Asset>(
server: &AssetServer,
handle: Handle<T>,
extensions: &[&'static str],
) -> bool {
if let Some(asset_path) = server.get_handle_path(handle.clone()) {
if let Some(extension) = asset_path.path().extension() {
extensions.iter().any(|&check| check == extension)
} else {
false
}
} else {
false
}
}
} }
use gltf::*; use gltf::*;
@ -650,103 +650,6 @@ mod gltf {
} }
}); });
} }
pub fn manage_gltf_animation_ui(
added: Query<Entity, (With<Button>, Added<ui::Active>)>,
mut removed: RemovedComponents<ui::Active>,
targets_gltf: Query<&ui::TargetAsset<Gltf>>,
gltfs: Res<Assets<Gltf>>,
mut animation_clip_events: EventWriter<CustomAssetEvent<AnimationClip>>,
) {
removed
.iter()
.filter_map(|entity| {
if let Ok(ui::TargetAsset { handle }) = targets_gltf.get(entity) {
gltfs.get(handle)
} else {
None
}
})
.for_each(|gltf| {
gltf.named_animations
.iter()
.for_each(|(animation_name, animation_handle)| {
info!("Named animation: {:?}", animation_name);
animation_clip_events.send(CustomAssetEvent::Remove {
handle: animation_handle.clone(),
});
});
});
added
.iter()
.filter_map(|entity| {
if let Ok(ui::TargetAsset { handle }) = targets_gltf.get(entity) {
gltfs.get(handle)
} else {
None
}
})
.for_each(|gltf| {
// Populate animations tab
gltf.named_animations
.iter()
.for_each(|(animation_name, animation_handle)| {
animation_clip_events.send(CustomAssetEvent::Add {
name: animation_name.clone(),
handle: animation_handle.clone(),
})
});
});
}
pub fn manage_gltf_scene_ui(
added: Query<Entity, (With<Button>, Added<ui::Active>)>,
mut removed: RemovedComponents<ui::Active>,
targets_gltf: Query<&ui::TargetAsset<Gltf>>,
gltfs: Res<Assets<Gltf>>,
mut scene_events: EventWriter<CustomAssetEvent<Scene>>,
) {
removed
.iter()
.filter_map(|entity| {
if let Ok(ui::TargetAsset { handle }) = targets_gltf.get(entity) {
gltfs.get(handle)
} else {
None
}
})
.for_each(|gltf| {
gltf.named_scenes
.iter()
.for_each(|(scene_name, scene_handle)| {
info!("Named scene: {:?}", scene_name);
scene_events.send(CustomAssetEvent::Remove {
handle: scene_handle.clone(),
});
});
});
added
.iter()
.filter_map(|entity| {
if let Ok(ui::TargetAsset { handle }) = targets_gltf.get(entity) {
gltfs.get(handle)
} else {
None
}
})
.for_each(|gltf| {
// Populate scenes tab
gltf.named_scenes
.iter()
.for_each(|(scene_name, scene_handle)| {
info!("Named scene: {:?}", scene_name);
scene_events.send(CustomAssetEvent::Add {
name: scene_name.clone(),
handle: scene_handle.clone(),
})
})
});
}
} }
// TODO: Mark loaded animation as active // TODO: Mark loaded animation as active
@ -757,20 +660,16 @@ mod scenes {
#[derive(Debug, Component, Default)] #[derive(Debug, Component, Default)]
pub struct SceneWidget; pub struct SceneWidget;
pub fn scenes_ui( pub fn add_scenes_ui(
mut events: EventReader<CustomAssetEvent<Scene>>, gltf_selected: Query<&ui::TargetAsset<Gltf>, Added<ui::Active>>,
mut commands: Commands, mut commands: Commands,
gltfs: Res<Assets<Gltf>>,
widget: Query<Entity, With<SceneWidget>>, widget: Query<Entity, With<SceneWidget>>,
current: Query<(Entity, &ui::TargetAsset<Scene>)>,
) { ) {
events.iter().for_each(|event| { gltf_selected.iter().for_each(|ui::TargetAsset { handle }| {
let empty = current.iter().len() == 0; if let Some(gltf) = gltfs.get(&handle.clone()) {
gltf.named_scenes.iter().for_each(|(name, handle)| {
match event { create_asset_button(
CustomAssetEvent::Add { name, handle } => {
info!("Asset loading! {:?}({:?})", name, handle);
// Spawn new tree
let e = create_asset_button(
&widget, &widget,
&mut commands, &mut commands,
ui::TargetAsset { ui::TargetAsset {
@ -779,12 +678,24 @@ mod scenes {
name.clone(), name.clone(),
None, None,
); );
// If this is the first scene being added, set it as active })
if empty { }
commands.entity(e).insert(ui::Active); });
} }
}
CustomAssetEvent::Remove { handle } => { pub fn remove_scenes_ui(
mut gltf_unselected: RemovedComponents<ui::Active>,
target_assets: Query<&ui::TargetAsset<Gltf>>,
current: Query<(Entity, &ui::TargetAsset<Scene>)>,
gltfs: Res<Assets<Gltf>>,
mut commands: Commands,
) {
gltf_unselected
.iter()
.filter_map(|entity| target_assets.get(entity).ok())
.filter_map(|ui::TargetAsset { handle }| gltfs.get(handle))
.for_each(|gltf| {
gltf.scenes.iter().for_each(|handle| {
destroy_asset_button( destroy_asset_button(
&current, &current,
&mut commands, &mut commands,
@ -792,12 +703,8 @@ mod scenes {
handle: handle.clone(), handle: handle.clone(),
}, },
); );
} });
CustomAssetEvent::Clear => { });
commands.entity(widget.single()).despawn_descendants();
}
}
});
} }
pub fn control_active_scenes( pub fn control_active_scenes(
@ -878,28 +785,64 @@ mod animations {
}) })
} }
pub fn animations_ui( /// When a new scene is loaded, add any newly compatible animations
mut events: EventReader<CustomAssetEvent<AnimationClip>>, pub fn add_animations_ui(
mut commands: Commands, player_spawned: Query<&Name, Added<AnimationPlayer>>,
widget: Query<Entity, With<AnimationWidget>>, widget: Query<Entity, With<AnimationWidget>>,
current: Query<(Entity, &ui::TargetAsset<AnimationClip>)>, mut commands: Commands,
gltfs: Res<Assets<Gltf>>,
clips: Res<Assets<AnimationClip>>,
) { ) {
events.iter().for_each(|event| { player_spawned.iter().for_each(|player_name| {
match event { gltfs
CustomAssetEvent::Add { name, handle } => { .iter()
info!("Asset loading! {:?}({:?})", name, handle); .flat_map(|(_, gltf)| gltf.named_animations.iter())
// Spawn new tree .filter_map(|(clip_name, handle)| {
if let Some(clip) = clips.get(&handle) {
Some((clip_name, handle, clip))
} else {
None
}
})
.filter(|(_, _, clip)| clip.compatible_with(player_name))
.for_each(|(clip_name, handle, _)| {
create_asset_button( create_asset_button(
&widget, &widget,
&mut commands, &mut commands,
ui::TargetAsset { ui::TargetAsset {
handle: handle.clone(), handle: handle.clone(),
}, },
name.clone(), clip_name.clone(),
None, None,
); );
} });
CustomAssetEvent::Remove { handle } => { });
}
// When a scene is de-selected, remove any outdated animation options
pub fn remove_animations_ui(
mut removed_players: RemovedComponents<Handle<Scene>>,
names: Query<&Name>,
current: Query<(Entity, &ui::TargetAsset<AnimationClip>)>,
clips: Res<Assets<AnimationClip>>,
targets: Query<(&AnimationPlayer, &Name)>,
mut commands: Commands,
) {
// For each removed scene
removed_players.iter().for_each(|_| {
// Iterate over the current animation buttons
current
.iter()
.filter(|(_, ui::TargetAsset { handle })| {
// Check if this clip is compatible with any remaining entities
// NOTE: We are checking this is *not* compatible with any entities
clips
.get(handle)
.map(|clip| !(targets.iter().any(|(_, name)| clip.compatible_with(name))))
.unwrap_or(true)
})
.for_each(|(_, ui::TargetAsset { handle })| {
// Destroy the buton if it is so
destroy_asset_button( destroy_asset_button(
&current, &current,
&mut commands, &mut commands,
@ -907,11 +850,7 @@ mod animations {
handle: handle.clone(), handle: handle.clone(),
}, },
); );
} });
CustomAssetEvent::Clear => {
commands.entity(widget.single()).despawn_descendants();
}
}
}); });
} }
@ -1098,7 +1037,6 @@ mod monologues {
)); ));
} }
// TODO: Load .txt files for monologues
pub fn texts_ui( pub fn texts_ui(
mut events: EventReader<AssetEvent<Monologue>>, mut events: EventReader<AssetEvent<Monologue>>,
mut commands: Commands, mut commands: Commands,
@ -1151,10 +1089,8 @@ mod monologues {
}); });
} }
// TODO(BUG): Better handle hide/close monologue
pub fn show_preview_text( pub fn show_preview_text(
added: Query<Entity, (With<Button>, Added<ui::Active>)>, added: Query<Entity, (With<Button>, Added<ui::Active>)>,
mut removed: RemovedComponents<ui::Active>,
monologue_handles: Query<&ui::TargetAsset<Monologue>>, monologue_handles: Query<&ui::TargetAsset<Monologue>>,
monologues: Res<Assets<Monologue>>, monologues: Res<Assets<Monologue>>,
container: Query<Entity, With<MonologueContainer>>, container: Query<Entity, With<MonologueContainer>>,
@ -1233,7 +1169,6 @@ mod cameras {
#[derive(Debug, Component, Default)] #[derive(Debug, Component, Default)]
pub struct CameraWidget; pub struct CameraWidget;
// TODO: Despawn camera button when camera removed
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>, Without<EditorCamera>)>,
mut removed: RemovedComponents<Camera>, mut removed: RemovedComponents<Camera>,
@ -1246,20 +1181,14 @@ mod cameras {
destroy_entity_button(&current, &mut commands, &ui::TargetEntity { entity }); destroy_entity_button(&current, &mut commands, &ui::TargetEntity { entity });
}); });
added.iter_mut().for_each(|(entity, mut camera, name)| { added.iter_mut().for_each(|(entity, mut camera, name)| {
let empty = current.iter().len() == 0;
info!("Camera added {:?} {:?}", entity, name); info!("Camera added {:?} {:?}", entity, name);
let e = create_entity_button( create_entity_button(
&widget, &widget,
&mut commands, &mut commands,
ui::TargetEntity { entity }, ui::TargetEntity { entity },
name.as_str().into(), name.as_str().into(),
); );
if empty { camera.is_active = false;
commands.entity(e).insert(ui::Active);
} else {
camera.is_active = false;
}
}); });
} }
@ -1292,13 +1221,11 @@ mod cameras {
mut cameras: Query<&mut Camera>, mut cameras: Query<&mut Camera>,
) { ) {
removed.iter().for_each(|entity| { removed.iter().for_each(|entity| {
info!("Setting {:?} to inactive camera", entity);
if let Ok(mut camera) = cameras.get_mut(entity) { if let Ok(mut camera) = cameras.get_mut(entity) {
camera.is_active = false; camera.is_active = false;
} }
}); });
added.iter().for_each(|entity| { added.iter().for_each(|entity| {
info!("Setting {:?} to active camera", entity);
if let Ok(mut camera) = cameras.get_mut(entity) { if let Ok(mut camera) = cameras.get_mut(entity) {
camera.is_active = true; camera.is_active = true;
} }
@ -1322,3 +1249,28 @@ mod cameras {
}) })
} }
} }
use lighting::*;
mod lighting {
use super::*;
pub fn spot_light_force_shadows(mut spot_lights: Query<&mut SpotLight, Added<SpotLight>>) {
spot_lights.iter_mut().for_each(|mut light| {
light.shadows_enabled = true;
})
}
pub fn directional_light_force_shadows(
mut directional_lights: Query<&mut DirectionalLight, Added<DirectionalLight>>,
) {
directional_lights.iter_mut().for_each(|mut light| {
light.shadows_enabled = true;
})
}
pub fn point_light_force_shadows(mut point_lights: Query<&mut PointLight, Added<PointLight>>) {
point_lights.iter_mut().for_each(|mut light| {
light.shadows_enabled = true;
})
}
}

@ -616,7 +616,7 @@ pub mod alert {
parent.spawn(( parent.spawn((
TitleBarBase::new(color).bundle(), TitleBarBase::new(color).bundle(),
Title { Title {
text: "Alert".into(), text: "Heads up!".into(),
..default() ..default()
}, },
Close { Close {

Loading…
Cancel
Save