|
|
|
|
@ -3,30 +3,15 @@
|
|
|
|
|
// Editor for creating Monologue Trees levels
|
|
|
|
|
//
|
|
|
|
|
// BUGS:
|
|
|
|
|
// * Camera order ambiguity
|
|
|
|
|
// * Load new GLTF -> Despawn all level entities
|
|
|
|
|
//
|
|
|
|
|
// 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) Clear button to wipe spawned scene
|
|
|
|
|
// * edit textbox with actions
|
|
|
|
|
// * (brutal) export level
|
|
|
|
|
// * (hard) import level
|
|
|
|
|
//
|
|
|
|
|
// Asset types:
|
|
|
|
|
// * Audios (done)
|
|
|
|
|
// * Loop individual (done)
|
|
|
|
|
// * Gltfs (doing)
|
|
|
|
|
// * Scenes
|
|
|
|
|
// * Animations
|
|
|
|
|
// * Play/Pause all
|
|
|
|
|
// * Fonts (done)
|
|
|
|
|
// * Monologues (done)
|
|
|
|
|
// * (hard) Harden Active Camera
|
|
|
|
|
// * (medium) Pre-compute animation target entities
|
|
|
|
|
// * make min/max/close buttons into actions not selects
|
|
|
|
|
// * (???) Better handle hide/close monologue
|
|
|
|
|
|
|
|
|
|
use bevy::{
|
|
|
|
|
asset::{Asset, AssetLoader, Assets, ChangeWatcher, LoadContext, LoadedAsset},
|
|
|
|
|
@ -39,9 +24,8 @@ use monologue_trees::{debug::*, ui};
|
|
|
|
|
|
|
|
|
|
const WELCOME_MESSAGES: &'static [&'static str] = &[
|
|
|
|
|
"Welcome to the Monologue Trees editor!",
|
|
|
|
|
"Import assets by dragging and dropping files or folders into the editor!",
|
|
|
|
|
concat!(
|
|
|
|
|
"Import assets by dragging and dropping files into the editor\n",
|
|
|
|
|
"\n",
|
|
|
|
|
"Supported file types (for now):\n",
|
|
|
|
|
"* 3D: .gltf, .glb\n",
|
|
|
|
|
"* Audio: .ogg\n",
|
|
|
|
|
@ -67,13 +51,15 @@ fn main() {
|
|
|
|
|
..default()
|
|
|
|
|
}),
|
|
|
|
|
DebugInfoPlugin,
|
|
|
|
|
ui::GameUiPlugin,
|
|
|
|
|
ui::GameUiPlugin {
|
|
|
|
|
enable_alerts: true,
|
|
|
|
|
},
|
|
|
|
|
))
|
|
|
|
|
.register_type::<LevelRoot>()
|
|
|
|
|
.init_resource::<AssetRegistry>()
|
|
|
|
|
.init_resource::<FontInfo>()
|
|
|
|
|
.add_asset::<Monologue>()
|
|
|
|
|
.init_asset_loader::<MonologueLoader>()
|
|
|
|
|
.add_event::<CustomAssetEvent<Scene>>()
|
|
|
|
|
.add_event::<CustomAssetEvent<AnimationClip>>()
|
|
|
|
|
.add_systems(Startup, (initialize_ui, init_texts_ui, welcome_message))
|
|
|
|
|
.add_systems(
|
|
|
|
|
Update,
|
|
|
|
|
@ -89,26 +75,25 @@ fn main() {
|
|
|
|
|
Update,
|
|
|
|
|
(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(
|
|
|
|
|
Update,
|
|
|
|
|
(
|
|
|
|
|
cameras_ui,
|
|
|
|
|
manage_active_camera,
|
|
|
|
|
control_active_camera,
|
|
|
|
|
fallback_camera,
|
|
|
|
|
gltf_ui,
|
|
|
|
|
texts_ui,
|
|
|
|
|
control_active_gltf,
|
|
|
|
|
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(
|
|
|
|
|
Update,
|
|
|
|
|
(
|
|
|
|
|
import_files,
|
|
|
|
|
gltf_ui,
|
|
|
|
|
fonts_ui,
|
|
|
|
|
texts_ui,
|
|
|
|
|
manage_active_gltf,
|
|
|
|
|
show_preview_text,
|
|
|
|
|
sync_monologue_font,
|
|
|
|
|
reload_assets.run_if(ui::activated::<ReloadAssets>),
|
|
|
|
|
clear_assets.run_if(ui::activated::<ClearAssets>),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.add_systems(
|
|
|
|
|
@ -119,23 +104,27 @@ fn main() {
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Resource, Default)]
|
|
|
|
|
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)]
|
|
|
|
|
pub struct TabRoot;
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Component)]
|
|
|
|
|
#[derive(Debug, Component, Reflect, Default)]
|
|
|
|
|
#[reflect(Component)]
|
|
|
|
|
pub struct LevelRoot;
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Component)]
|
|
|
|
|
@ -143,7 +132,7 @@ pub struct EditorCamera;
|
|
|
|
|
|
|
|
|
|
fn initialize_ui(mut commands: Commands) {
|
|
|
|
|
// Empty entity for populating the level being edited
|
|
|
|
|
commands.spawn((SpatialBundle { ..default() }, LevelRoot));
|
|
|
|
|
commands.spawn((SpatialBundle { ..default() }, LevelRoot::default()));
|
|
|
|
|
|
|
|
|
|
commands.spawn((
|
|
|
|
|
Camera2dBundle { ..default() },
|
|
|
|
|
@ -161,6 +150,15 @@ fn initialize_ui(mut commands: Commands) {
|
|
|
|
|
..default()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let simple_button = ButtonBundle {
|
|
|
|
|
style: Style {
|
|
|
|
|
..base_style.clone()
|
|
|
|
|
},
|
|
|
|
|
background_color: Color::WHITE.into(),
|
|
|
|
|
border_color: Color::BLACK.into(),
|
|
|
|
|
..default()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
commands
|
|
|
|
|
.spawn(NodeBundle {
|
|
|
|
|
style: Style {
|
|
|
|
|
@ -232,6 +230,11 @@ fn initialize_ui(mut commands: Commands) {
|
|
|
|
|
parent,
|
|
|
|
|
ui::Select::Multi,
|
|
|
|
|
));
|
|
|
|
|
content_containers.push(spawn_tab_container::<LevelWidget>(
|
|
|
|
|
"Level",
|
|
|
|
|
parent,
|
|
|
|
|
ui::Select::Single,
|
|
|
|
|
));
|
|
|
|
|
content_containers.push(spawn_tab_container::<GltfWidget>(
|
|
|
|
|
"Gltf",
|
|
|
|
|
parent,
|
|
|
|
|
@ -274,31 +277,111 @@ fn initialize_ui(mut commands: Commands) {
|
|
|
|
|
ui::Select::Single,
|
|
|
|
|
))
|
|
|
|
|
.with_children(|parent| {
|
|
|
|
|
let b = ButtonBundle {
|
|
|
|
|
content_containers.iter().enumerate().for_each(
|
|
|
|
|
|(i, (name, target))| {
|
|
|
|
|
parent.spawn((
|
|
|
|
|
simple_button.clone(),
|
|
|
|
|
ui::Title {
|
|
|
|
|
text: name.clone(),
|
|
|
|
|
..default()
|
|
|
|
|
},
|
|
|
|
|
ui::Collapse { target: *target },
|
|
|
|
|
ui::Sorting(i as u8),
|
|
|
|
|
));
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
.id();
|
|
|
|
|
parent.spawn((
|
|
|
|
|
ui::TitleBarBase::new(Color::WHITE).bundle(),
|
|
|
|
|
ui::Title {
|
|
|
|
|
text: "Assets".into(),
|
|
|
|
|
..default()
|
|
|
|
|
},
|
|
|
|
|
ui::Minimize { target: container },
|
|
|
|
|
ui::Sorting(0),
|
|
|
|
|
));
|
|
|
|
|
});
|
|
|
|
|
commands
|
|
|
|
|
.spawn(NodeBundle {
|
|
|
|
|
style: Style {
|
|
|
|
|
..base_style.clone()
|
|
|
|
|
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()
|
|
|
|
|
};
|
|
|
|
|
content_containers.iter().for_each(|(name, target)| {
|
|
|
|
|
})
|
|
|
|
|
.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((
|
|
|
|
|
b.clone(),
|
|
|
|
|
simple_button.clone(),
|
|
|
|
|
ReloadAssets,
|
|
|
|
|
ui::Sorting(1),
|
|
|
|
|
ui::Title {
|
|
|
|
|
text: name.clone(),
|
|
|
|
|
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()
|
|
|
|
|
},
|
|
|
|
|
ui::Collapse { target: *target },
|
|
|
|
|
));
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
.id();
|
|
|
|
|
parent.spawn((
|
|
|
|
|
ui::TitleBarBase::new(Color::WHITE).bundle(),
|
|
|
|
|
ui::Title {
|
|
|
|
|
text: "Assets".into(),
|
|
|
|
|
text: "Actions".into(),
|
|
|
|
|
..default()
|
|
|
|
|
},
|
|
|
|
|
ui::Minimize { target: container },
|
|
|
|
|
@ -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::*;
|
|
|
|
|
mod audio {
|
|
|
|
|
|
|
|
|
|
@ -442,29 +504,41 @@ mod audio {
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn play_audio(
|
|
|
|
|
events: Query<(Entity, &Interaction, &AudioSink), (With<Button>, Changed<Interaction>)>,
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
) {
|
|
|
|
|
pub fn play_audio(events: Query<&AudioSink, (With<Button>, Added<ui::Active>)>) {
|
|
|
|
|
events.iter().for_each(|sink| {
|
|
|
|
|
sink.play();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn pause_audio(mut events: RemovedComponents<ui::Active>, sinks: Query<&AudioSink>) {
|
|
|
|
|
events
|
|
|
|
|
.iter()
|
|
|
|
|
.filter(|(_, &interaction, _)| interaction == Interaction::Pressed)
|
|
|
|
|
.for_each(|(entity, _, sink)| {
|
|
|
|
|
sink.toggle();
|
|
|
|
|
if sink.is_paused() {
|
|
|
|
|
commands.entity(entity).remove::<ui::Active>();
|
|
|
|
|
} else {
|
|
|
|
|
commands.entity(entity).insert(ui::Active);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
.filter_map(|entity| sinks.get(entity).ok())
|
|
|
|
|
.for_each(|sink| sink.stop());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
use assets::*;
|
|
|
|
|
mod assets {
|
|
|
|
|
|
|
|
|
|
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 {
|
|
|
|
|
if let Some(asset_path) = server.get_handle_path(handle.clone()) {
|
|
|
|
|
if let Some(stem) = asset_path.path().file_stem() {
|
|
|
|
|
@ -578,7 +652,6 @@ mod gltf {
|
|
|
|
|
#[derive(Debug, Component, Default)]
|
|
|
|
|
pub struct GltfWidget;
|
|
|
|
|
|
|
|
|
|
// TODO: Mark selected gltf as active ~single exclusive~
|
|
|
|
|
pub fn gltf_ui(
|
|
|
|
|
mut events: EventReader<AssetEvent<Gltf>>,
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
@ -631,28 +704,17 @@ mod gltf {
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn manage_active_gltf(
|
|
|
|
|
events: Query<
|
|
|
|
|
(Entity, &Interaction, Option<&ui::Active>),
|
|
|
|
|
(With<Button>, Changed<Interaction>),
|
|
|
|
|
>,
|
|
|
|
|
pub fn control_active_gltf(
|
|
|
|
|
events: Query<Entity, (With<ui::TargetAsset<Gltf>>, Added<ui::Active>)>,
|
|
|
|
|
root: Query<Entity, With<LevelRoot>>,
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
) {
|
|
|
|
|
events
|
|
|
|
|
.iter()
|
|
|
|
|
.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);
|
|
|
|
|
}
|
|
|
|
|
events.iter().for_each(|_| {
|
|
|
|
|
commands.entity(root.single()).despawn_descendants();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Mark loaded animation as active
|
|
|
|
|
use scenes::*;
|
|
|
|
|
mod scenes {
|
|
|
|
|
use super::*;
|
|
|
|
|
@ -746,8 +808,6 @@ mod scenes {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Play all animations
|
|
|
|
|
// TODO: Mark playing animation as active
|
|
|
|
|
use animations::*;
|
|
|
|
|
mod animations {
|
|
|
|
|
use super::*;
|
|
|
|
|
@ -786,6 +846,7 @@ mod animations {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// When a new scene is loaded, add any newly compatible animations
|
|
|
|
|
/// TODO: Add target entity(s) too
|
|
|
|
|
pub fn add_animations_ui(
|
|
|
|
|
player_spawned: Query<&Name, Added<AnimationPlayer>>,
|
|
|
|
|
widget: Query<Entity, With<AnimationWidget>>,
|
|
|
|
|
@ -822,7 +883,6 @@ mod animations {
|
|
|
|
|
// 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)>,
|
|
|
|
|
@ -921,6 +981,11 @@ mod fonts {
|
|
|
|
|
#[derive(Debug, Component, Default)]
|
|
|
|
|
pub struct FontWidget;
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Resource, Default)]
|
|
|
|
|
pub struct FontInfo {
|
|
|
|
|
pub default: Option<Handle<Font>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn fonts_ui(
|
|
|
|
|
mut events: EventReader<AssetEvent<Font>>,
|
|
|
|
|
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::*;
|
|
|
|
|
@ -1095,6 +1169,7 @@ mod monologues {
|
|
|
|
|
monologues: Res<Assets<Monologue>>,
|
|
|
|
|
container: Query<Entity, With<MonologueContainer>>,
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
font: Res<FontInfo>,
|
|
|
|
|
) {
|
|
|
|
|
added
|
|
|
|
|
.iter()
|
|
|
|
|
@ -1131,15 +1206,21 @@ mod monologues {
|
|
|
|
|
},
|
|
|
|
|
ui::Sorting(0),
|
|
|
|
|
));
|
|
|
|
|
parent.spawn((
|
|
|
|
|
TextBundle::from_section(
|
|
|
|
|
monologue.text.clone(),
|
|
|
|
|
TextStyle {
|
|
|
|
|
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((
|
|
|
|
|
TextBundle::from_section(monologue.text.clone(), style),
|
|
|
|
|
handle.clone(),
|
|
|
|
|
));
|
|
|
|
|
});
|
|
|
|
|
@ -1149,18 +1230,21 @@ mod monologues {
|
|
|
|
|
|
|
|
|
|
// TODO: Sync Handle<Monologue> and TextStyle components to automagically generate and sync text
|
|
|
|
|
pub fn sync_monologue_font(
|
|
|
|
|
events: Query<&ui::TargetAsset<Font>, Added<ui::Active>>,
|
|
|
|
|
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| {
|
|
|
|
|
text.sections.iter_mut().for_each(|section| {
|
|
|
|
|
section.style.font = handle.clone();
|
|
|
|
|
});
|
|
|
|
|
text.sections
|
|
|
|
|
.iter_mut()
|
|
|
|
|
.for_each(|section| match &font.default {
|
|
|
|
|
Some(handle) => section.style.font = handle.clone(),
|
|
|
|
|
None => section.style.font = Handle::default(),
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
use cameras::*;
|
|
|
|
|
mod cameras {
|
|
|
|
|
@ -1170,8 +1254,9 @@ mod cameras {
|
|
|
|
|
pub struct CameraWidget;
|
|
|
|
|
|
|
|
|
|
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>,
|
|
|
|
|
editor_camera: Query<Entity, With<EditorCamera>>,
|
|
|
|
|
widget: Query<Entity, With<CameraWidget>>,
|
|
|
|
|
current: Query<(Entity, &ui::TargetEntity)>,
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
@ -1188,47 +1273,25 @@ mod cameras {
|
|
|
|
|
ui::TargetEntity { entity },
|
|
|
|
|
name.as_str().into(),
|
|
|
|
|
);
|
|
|
|
|
camera.is_active = false;
|
|
|
|
|
camera.is_active = entity == editor_camera.single();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Set the camera active component based on button clicks
|
|
|
|
|
pub fn manage_active_camera(
|
|
|
|
|
events: Query<(&Interaction, &ui::TargetEntity), Changed<Interaction>>,
|
|
|
|
|
cameras: Query<Entity, With<Camera>>,
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
events: Query<&ui::TargetEntity, Added<ui::Active>>,
|
|
|
|
|
mut cameras: Query<(Entity, &mut Camera)>,
|
|
|
|
|
) {
|
|
|
|
|
events
|
|
|
|
|
.iter()
|
|
|
|
|
.filter(|(&interaction, _)| interaction == Interaction::Pressed)
|
|
|
|
|
.for_each(|(_, ui::TargetEntity { entity })| {
|
|
|
|
|
cameras.iter().for_each(|this_entity| {
|
|
|
|
|
events.iter().for_each(|ui::TargetEntity { entity }| {
|
|
|
|
|
cameras.iter_mut().for_each(|(this_entity, mut camera)| {
|
|
|
|
|
if this_entity == *entity {
|
|
|
|
|
info!("Marking {:?} as active camera", entity);
|
|
|
|
|
commands.entity(this_entity).insert(ui::Active);
|
|
|
|
|
camera.is_active = true;
|
|
|
|
|
} 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(
|
|
|
|
|
¤t,
|
|
|
|
|
&mut commands,
|
|
|
|
|
&ui::TargetAsset {
|
|
|
|
|
handle: handle.clone(),
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
AssetEvent::Modified { handle } => {
|
|
|
|
|
info!("Asset modified! {:?}", event);
|
|
|
|
|
destroy_asset_button(
|
|
|
|
|
¤t,
|
|
|
|
|
&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());
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|