Compare commits

..

No commits in common. '748875f765acf3ae0b1ca8968d87f553ff26ae6e' and '3fc6a0663e9d11d731f8d8b444a50a9d25b32adb' have entirely different histories.

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,16 +4,12 @@
//
// BUGS:
// * Camera order ambiguity
// * Load new GLTF -> Despawn all level entities
// * Multi-GLTF UX is bad.
// * Consider GLTF hierarchy (GLTF1 > Scene1a/Scene1b, GlTF2 > Scene2a/Scene2b, etc)
// * Easy despawn when de-selecting gltf
//
// TODO:
// * 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) Play all animations
// * (easy) Clear button to wipe spawned scene
// * (brutal) export level
// * (hard) import level
@ -29,11 +25,12 @@
// * Monologues (done)
use bevy::{
asset::{Asset, AssetLoader, Assets, ChangeWatcher, LoadContext, LoadedAsset},
asset::{Asset, Assets},
asset::{AssetLoader, LoadContext, LoadedAsset},
audio::PlaybackMode,
gltf::Gltf,
prelude::*,
utils::{BoxedFuture, Duration},
utils::BoxedFuture,
};
use monologue_trees::{debug::*, ui};
@ -53,19 +50,14 @@ const WELCOME_MESSAGES: &'static [&'static str] = &[
fn main() {
App::new()
.add_plugins((
DefaultPlugins
.set(WindowPlugin {
primary_window: Some(Window {
title: "Monologue Trees Editor".into(),
resolution: (640., 480.).into(),
..default()
}),
..default()
})
.set(AssetPlugin {
watch_for_changes: ChangeWatcher::with_delay(Duration::from_millis(200)),
DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
title: "Monologue Trees Editor".into(),
resolution: (640., 480.).into(),
..default()
}),
..default()
}),
DebugInfoPlugin,
ui::GameUiPlugin,
))
@ -78,16 +70,16 @@ fn main() {
.add_systems(
Update,
(
manage_gltf_animation_ui,
init_animations_ui,
remove_animations_ui,
add_animations_ui,
animations_ui,
play_all_animations,
play_animation,
),
)
.add_systems(
Update,
(remove_scenes_ui, add_scenes_ui, control_active_scenes),
(manage_gltf_scene_ui, scenes_ui, control_active_scenes),
)
.add_systems(
Update,
@ -111,14 +103,6 @@ fn main() {
sync_monologue_font,
),
)
.add_systems(
Update,
(
point_light_force_shadows,
spot_light_force_shadows,
directional_light_force_shadows,
),
)
.run();
}
@ -240,18 +224,18 @@ fn initialize_ui(mut commands: Commands) {
content_containers.push(spawn_tab_container::<SceneWidget>(
"Scene",
parent,
ui::Select::Single,
));
content_containers.push(spawn_tab_container::<CameraWidget>(
"Camera",
parent,
ui::Select::Single,
ui::Select::Multi,
));
content_containers.push(spawn_tab_container::<AnimationWidget>(
"Animation",
parent,
ui::Select::Multi,
));
content_containers.push(spawn_tab_container::<CameraWidget>(
"Camera",
parent,
ui::Select::Single,
));
});
// Container for tabs that open/close containers
@ -569,6 +553,22 @@ mod assets {
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::*;
@ -650,6 +650,103 @@ 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
@ -660,16 +757,20 @@ mod scenes {
#[derive(Debug, Component, Default)]
pub struct SceneWidget;
pub fn add_scenes_ui(
gltf_selected: Query<&ui::TargetAsset<Gltf>, Added<ui::Active>>,
pub fn scenes_ui(
mut events: EventReader<CustomAssetEvent<Scene>>,
mut commands: Commands,
gltfs: Res<Assets<Gltf>>,
widget: Query<Entity, With<SceneWidget>>,
current: Query<(Entity, &ui::TargetAsset<Scene>)>,
) {
gltf_selected.iter().for_each(|ui::TargetAsset { handle }| {
if let Some(gltf) = gltfs.get(&handle.clone()) {
gltf.named_scenes.iter().for_each(|(name, handle)| {
create_asset_button(
events.iter().for_each(|event| {
let empty = current.iter().len() == 0;
match event {
CustomAssetEvent::Add { name, handle } => {
info!("Asset loading! {:?}({:?})", name, handle);
// Spawn new tree
let e = create_asset_button(
&widget,
&mut commands,
ui::TargetAsset {
@ -678,24 +779,12 @@ mod scenes {
name.clone(),
None,
);
})
}
});
}
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| {
// If this is the first scene being added, set it as active
if empty {
commands.entity(e).insert(ui::Active);
}
}
CustomAssetEvent::Remove { handle } => {
destroy_asset_button(
&current,
&mut commands,
@ -703,8 +792,12 @@ mod scenes {
handle: handle.clone(),
},
);
});
});
}
CustomAssetEvent::Clear => {
commands.entity(widget.single()).despawn_descendants();
}
}
});
}
pub fn control_active_scenes(
@ -785,64 +878,28 @@ mod animations {
})
}
/// When a new scene is loaded, add any newly compatible animations
pub fn add_animations_ui(
player_spawned: Query<&Name, Added<AnimationPlayer>>,
widget: Query<Entity, With<AnimationWidget>>,
pub fn animations_ui(
mut events: EventReader<CustomAssetEvent<AnimationClip>>,
mut commands: Commands,
gltfs: Res<Assets<Gltf>>,
clips: Res<Assets<AnimationClip>>,
widget: Query<Entity, With<AnimationWidget>>,
current: Query<(Entity, &ui::TargetAsset<AnimationClip>)>,
) {
player_spawned.iter().for_each(|player_name| {
gltfs
.iter()
.flat_map(|(_, gltf)| gltf.named_animations.iter())
.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, _)| {
events.iter().for_each(|event| {
match event {
CustomAssetEvent::Add { name, handle } => {
info!("Asset loading! {:?}({:?})", name, handle);
// Spawn new tree
create_asset_button(
&widget,
&mut commands,
ui::TargetAsset {
handle: handle.clone(),
},
clip_name.clone(),
name.clone(),
None,
);
});
});
}
// 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
}
CustomAssetEvent::Remove { handle } => {
destroy_asset_button(
&current,
&mut commands,
@ -850,7 +907,11 @@ mod animations {
handle: handle.clone(),
},
);
});
}
CustomAssetEvent::Clear => {
commands.entity(widget.single()).despawn_descendants();
}
}
});
}
@ -1037,6 +1098,7 @@ mod monologues {
));
}
// TODO: Load .txt files for monologues
pub fn texts_ui(
mut events: EventReader<AssetEvent<Monologue>>,
mut commands: Commands,
@ -1089,8 +1151,10 @@ mod monologues {
});
}
// TODO(BUG): Better handle hide/close monologue
pub fn show_preview_text(
added: Query<Entity, (With<Button>, Added<ui::Active>)>,
mut removed: RemovedComponents<ui::Active>,
monologue_handles: Query<&ui::TargetAsset<Monologue>>,
monologues: Res<Assets<Monologue>>,
container: Query<Entity, With<MonologueContainer>>,
@ -1169,6 +1233,7 @@ mod cameras {
#[derive(Debug, Component, Default)]
pub struct CameraWidget;
// TODO: Despawn camera button when camera removed
pub fn cameras_ui(
mut added: Query<(Entity, &mut Camera, &Name), (Added<Camera>, Without<EditorCamera>)>,
mut removed: RemovedComponents<Camera>,
@ -1181,14 +1246,20 @@ mod cameras {
destroy_entity_button(&current, &mut commands, &ui::TargetEntity { entity });
});
added.iter_mut().for_each(|(entity, mut camera, name)| {
let empty = current.iter().len() == 0;
info!("Camera added {:?} {:?}", entity, name);
create_entity_button(
let e = create_entity_button(
&widget,
&mut commands,
ui::TargetEntity { entity },
name.as_str().into(),
);
camera.is_active = false;
if empty {
commands.entity(e).insert(ui::Active);
} else {
camera.is_active = false;
}
});
}
@ -1221,11 +1292,13 @@ mod cameras {
mut cameras: Query<&mut Camera>,
) {
removed.iter().for_each(|entity| {
info!("Setting {:?} to inactive camera", entity);
if let Ok(mut camera) = cameras.get_mut(entity) {
camera.is_active = false;
}
});
added.iter().for_each(|entity| {
info!("Setting {:?} to active camera", entity);
if let Ok(mut camera) = cameras.get_mut(entity) {
camera.is_active = true;
}
@ -1249,28 +1322,3 @@ 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((
TitleBarBase::new(color).bundle(),
Title {
text: "Heads up!".into(),
text: "Alert".into(),
..default()
},
Close {

Loading…
Cancel
Save