Compare commits

..

4 Commits

1
Cargo.lock generated

@ -2147,6 +2147,7 @@ name = "monologue-trees"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bevy", "bevy",
"ron",
"serde", "serde",
] ]

@ -47,7 +47,8 @@ path = "bin/serialize-wtf.rs"
[dependencies] [dependencies]
bevy = "0.11" bevy = "0.11"
# bevy_rapier3d = "*" # bevy_rapier3d = "*"
serde = "1" serde = { version = "1", features = ["derive"] }
ron = "0.8"
# From rapier docs # From rapier docs
[profile.dev.package.bevy_rapier3d] [profile.dev.package.bevy_rapier3d]

@ -0,0 +1,18 @@
Level(
epochs: [
Epoch(
id: 1,
scene: (
resources: [],
entities: [],
),
),
Epoch(
id: 2,
scene: (
resources: [],
entities: [],
),
),
],
)

@ -1,5 +1,41 @@
///
/// General levevel structure is this:
/// Level: Vec<Epoch>
/// Epoch: Id, Handle<DynamicScene>
///
/// You can index a Level by it's Epoch like
/// `/path/to/level.file#epoch-id`
///
/// !!!TODO!!!
/// * read .trees.level files, loading Levels
/// * Similar to GLTFs, this contains nested assets
/// * Handle<Level> -> Vec<Handle<Epochs>> => Handle<DynamicScene>
/// * Review how GLTF loading works for this, very similar goal.
///
/// Ex:
/// foo.trees.level:
/// [
/// Epoch {
/// id: 1,
/// scene: [
/// entities: [...]
/// resources: [...]
/// ],
/// },
/// Epoch {
/// id: 2,
/// scene: [...]
/// }, etc
/// ]
///
use crate::editor::prelude::*; use crate::editor::prelude::*;
use bevy::tasks::IoTaskPool; use bevy::{
asset::{AssetLoader, LoadContext, LoadedAsset},
reflect::{TypePath, TypeUuid},
scene::SceneLoader,
tasks::IoTaskPool,
utils::BoxedFuture,
};
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct EditorLevelPlugin; pub struct EditorLevelPlugin;
@ -7,8 +43,10 @@ pub struct EditorLevelPlugin;
impl Plugin for EditorLevelPlugin { impl Plugin for EditorLevelPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.register_type::<LevelRoot>() app.register_type::<LevelRoot>()
.add_asset::<Level>()
.init_asset_loader::<LevelAssetLoader>()
.add_systems(Update, level_ui) .add_systems(Update, level_ui)
.add_systems(Update, load_level) .add_systems(Update, set_level)
.add_systems(Update, export_level.run_if(ui::activated::<ExportLevel>)) .add_systems(Update, export_level.run_if(ui::activated::<ExportLevel>))
.add_systems(Update, rehydrate_level::<Visibility, ComputedVisibility>) .add_systems(Update, rehydrate_level::<Visibility, ComputedVisibility>)
.add_systems( .add_systems(
@ -24,7 +62,16 @@ impl Plugin for EditorLevelPlugin {
#[reflect(Component)] #[reflect(Component)]
pub struct LevelRoot; pub struct LevelRoot;
pub type Level = DynamicScene; #[derive(Debug, TypeUuid, TypePath, Default, Deserialize)]
#[uuid = "eb5c5f73-076e-4476-83f2-d97cf077ec90"]
struct Level {
epochs: Vec<Epoch>,
}
#[derive(Reflect)]
struct RawLevel {
epochs: Vec<Epoch>,
}
#[derive(Debug, Component, Default)] #[derive(Debug, Component, Default)]
pub struct LevelWidget; pub struct LevelWidget;
@ -84,7 +131,7 @@ fn level_ui(
}); });
} }
fn load_level( fn set_level(
events: Query< events: Query<
&ui::TargetAsset<DynamicScene>, &ui::TargetAsset<DynamicScene>,
(Added<ui::Active>, With<ui::TargetAsset<DynamicScene>>), (Added<ui::Active>, With<ui::TargetAsset<DynamicScene>>),
@ -103,13 +150,12 @@ fn load_level(
}); });
} }
fn export_level( pub fn export_level_state(
level_root: Query<Entity, With<LevelRoot>>, level_root: &Query<Entity, With<LevelRoot>>,
audio_root: Query<Entity, With<AudioRoot>>, audio_root: &Query<Entity, With<AudioRoot>>,
children: Query<&Children>, children: &Query<&Children>,
world: &World, world: &World,
) { ) -> DynamicScene {
let app_type_registry = world.resource::<AppTypeRegistry>().clone();
let mut builder = DynamicSceneBuilder::from_world(world.clone()); let mut builder = DynamicSceneBuilder::from_world(world.clone());
builder.deny_all_resources(); builder.deny_all_resources();
@ -161,10 +207,18 @@ fn export_level(
} }
}); });
let scene = builder.build(); builder.build()
}
let serialized = scene fn export_level(
.serialize_ron(&app_type_registry) level_root: Query<Entity, With<LevelRoot>>,
audio_root: Query<Entity, With<AudioRoot>>,
children: Query<&Children>,
registry: Res<AppTypeRegistry>,
world: &World,
) {
let serialized = export_level_state(&level_root, &audio_root, &children, world)
.serialize_ron(&registry.clone())
.expect("Serialize scene"); .expect("Serialize scene");
IoTaskPool::get() IoTaskPool::get()
@ -217,3 +271,39 @@ fn clear_level(
}); });
}) })
} }
#[derive(Default)]
pub struct LevelAssetLoader;
impl AssetLoader for LevelAssetLoader {
fn load<'a>(
&'a self,
bytes: &'a [u8],
load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, Result<(), bevy::asset::Error>> {
Box::pin(async move { Ok(load_level(bytes, load_context).await) })
}
fn extensions(&self) -> &[&str] {
&["trees.level"]
}
}
// ref: https://github.com/bevyengine/bevy/blob/12c6fa7e591545afe8c7101b0f021121eea7272f/crates/bevy_gltf/src/loader.rs#L92
pub async fn load_level<'a, 'b>(bytes: &'a [u8], load_context: &'a mut LoadContext<'b>) {
let level: Level = ron::de::from_bytes(bytes).expect("Deserialize level");
info!("Level: {:?}", level);
let mut world = World::new();
world.insert_resource(AppTypeRegistry { ..default() });
let scene = SceneLoader::from_world(&mut world)
.load(bytes, load_context)
.await;
info!("Scene result: {:?}", scene);
let epochs: Vec<Epoch> = {
todo!("Load epochs");
let id = todo!("get epoch ID");
let scene = todo!("get dynamic scene");
vec![]
};
load_context.set_default_asset(LoadedAsset::new(Level { epochs }));
}

@ -6,3 +6,4 @@ pub use crate::{
ui, ui,
}; };
pub use bevy::{gltf::Gltf, prelude::*}; pub use bevy::{gltf::Gltf, prelude::*};
pub use serde::{Deserialize, Serialize};

@ -1,3 +1,5 @@
use bevy::reflect::{TypePath, TypeUuid};
use crate::editor::prelude::*; use crate::editor::prelude::*;
#[derive(Debug, Default)] #[derive(Debug, Default)]
@ -6,23 +8,17 @@ pub struct EditorTimelinePlugin;
impl Plugin for EditorTimelinePlugin { impl Plugin for EditorTimelinePlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(Update, add_timeline_epoch.run_if(ui::activated::<AddEpoch>)) app.add_systems(Update, add_timeline_epoch.run_if(ui::activated::<AddEpoch>))
.add_systems(Update, set_epoch_gltf) .add_systems(Update, control_active_epoch);
.add_systems(Update, load_epoch_gltf) }
.add_systems(Update, set_epoch_scene)
.add_systems(Update, load_epoch_scene)
.add_systems(Update, set_epoch_camera)
.add_systems(Update, load_epoch_camera)
.add_systems(Update, set_epoch_music)
.add_systems(Update, load_epoch_music)
.add_systems(Update, set_epoch_monologue)
.add_systems(Update, load_epoch_monologue)
.add_systems(Update, set_epoch_font)
.add_systems(Update, load_epoch_font)
.add_systems(Update, set_epoch_sfx)
.add_systems(Update, load_epoch_sfx)
.add_systems(Update, set_epoch_animations)
.add_systems(Update, load_epoch_animations);
} }
#[derive(
Debug, Component, Resource, Clone, Default, TypeUuid, Deserialize, TypePath, FromReflect,
)]
#[uuid = "959f5f02-7c80-4b3d-ad02-9dc2e5d1b963"]
pub struct Epoch {
id: usize,
scene: DynamicScene,
} }
/// Timeline widget marker /// Timeline widget marker
@ -33,58 +29,13 @@ pub struct TimelineWidget;
#[derive(Debug, Component)] #[derive(Debug, Component)]
pub struct AddEpoch; pub struct AddEpoch;
/// Epoch ID Component fn control_active_epoch(
#[derive(Debug, Reflect, Component, Clone)] events: Query<(Entity, &Epoch), Added<ui::Active>>,
pub struct EpochId { mut commands: Commands,
id: usize, ) {
} events.iter().for_each(|(entity, epoch)| {
commands.insert_resource(epoch.clone());
/// Epoch GLTF Component });
#[derive(Debug, Reflect, Component, Clone)]
pub struct EpochGltf {
gltf: Handle<Gltf>,
}
/// Epoch Scene Component
#[derive(Debug, Reflect, Component, Clone)]
pub struct EpochScene {
scene: Handle<Scene>,
}
/// Epoch Camera Component, marking the current camera
#[derive(Debug, Reflect, Component, Clone)]
pub struct EpochCamera {
camera: Entity,
}
/// Epoch music component, marking the opening track for this epoch
#[derive(Debug, Reflect, Default, Component, Clone)]
pub struct EpochMusic {
music: Handle<AudioSource>,
}
/// Epoch monologue, marking the dialog spoken this epoch
#[derive(Debug, Reflect, Component, Clone)]
pub struct EpochMonologue {
monologue: Handle<Monologue>,
}
/// Epoch font, marking the font used for this epoch's monologue
#[derive(Debug, Reflect, Component, Clone)]
pub struct EpochFont {
font: Handle<Font>,
}
/// A vector of audios looping this epoch as background tracks
#[derive(Debug, Reflect, Component, Clone)]
pub struct EpochSfx {
sfx: Vec<Handle<AudioSource>>,
}
/// Epoch animations, looping this epoch
#[derive(Debug, Reflect, Component, Clone)]
pub struct EpochAnimations {
animations: Vec<Handle<AnimationClip>>,
} }
/// System for adding an epoch to the level's timeline /// System for adding an epoch to the level's timeline
@ -114,237 +65,8 @@ fn add_timeline_epoch(
text: name, text: name,
..default() ..default()
}, },
EpochId { id }, Epoch { id, ..default() },
)); ));
}); });
}); });
} }
/// Set the GLTF for the current epoch
fn set_epoch_gltf(
events: Query<&ui::TargetAsset<Gltf>, Added<ui::Active>>,
active_epoch: Query<Entity, (With<ui::Active>, With<EpochId>)>,
mut commands: Commands,
) {
// Each time a GLTF is selected in the editor
events.iter().for_each(|ui::TargetAsset { handle }| {
// Iterate over all (0 or 1) active epochs
active_epoch.iter().for_each(|entity| {
// Set the GLTF (overwrite existing GLTF selections)
commands.entity(entity).insert(EpochGltf {
gltf: handle.clone(),
});
// TODO: Unset Scene, Camera, Animations
});
});
}
fn load_epoch_gltf(events: Query<Option<&EpochGltf>, (Added<ui::Active>, With<EpochId>)>) {
events.iter().for_each(|epoch_gltf| {
warn!("TODO: Load epoch GLTF!");
})
}
fn set_epoch_scene(
events: Query<&ui::TargetAsset<Scene>, Added<ui::Active>>,
active_epoch: Query<Entity, (With<ui::Active>, With<EpochId>)>,
mut commands: Commands,
) {
// Each time a Scene is selected in the editor
events.iter().for_each(|ui::TargetAsset { handle }| {
// Iterate over all (0 or 1) active epochs
active_epoch.iter().for_each(|entity| {
// Set the Scene (overwrite existing Scene selections)
commands.entity(entity).insert(EpochScene {
scene: handle.clone(),
});
});
});
}
fn load_epoch_scene(events: Query<Option<&EpochScene>, (Added<ui::Active>, With<EpochId>)>) {
events.iter().for_each(|epoch_scene| {
warn!("TODO: Load epoch Scene!");
})
}
fn set_epoch_camera(
events: Query<&ui::TargetEntity, Added<ui::Active>>,
active_epoch: Query<Entity, (With<ui::Active>, With<EpochId>)>,
mut commands: Commands,
) {
// Each time a Scene is selected in the editor
events.iter().for_each(|ui::TargetEntity { entity }| {
// Iterate over all (0 or 1) active epochs
active_epoch.iter().for_each(|this_entity| {
// Set the Scene (overwrite existing Scene selections)
commands
.entity(this_entity)
.insert(EpochCamera { camera: *entity });
});
});
}
fn load_epoch_camera(events: Query<Option<&EpochCamera>, (Added<ui::Active>, With<EpochId>)>) {
events.iter().for_each(|epoch_camera| {
warn!("TODO: Load epoch Camera");
})
}
fn set_epoch_music(
events: Query<&ui::TargetAsset<AudioSource>, Added<ui::Active>>,
active_epoch: Query<Entity, (With<ui::Active>, With<EpochId>)>,
mut commands: Commands,
) {
// Each time a Scene is selected in the editor
events.iter().for_each(|ui::TargetAsset { handle }| {
info!("TODO: Select scene music");
// // Iterate over all (0 or 1) active epochs
// active_epoch.iter().for_each(|entity| {
// // Set the Scene (overwrite existing Scene selections)
// commands.entity(entity).insert(EpochMusic { music: handle.clone() });
// });
});
}
fn load_epoch_music(events: Query<Option<&EpochMusic>, (Added<ui::Active>, With<EpochId>)>) {
events.iter().for_each(|epoch_music| {
warn!("TODO: Load epoch music!");
})
}
fn set_epoch_monologue(
events: Query<&ui::TargetAsset<Monologue>, Added<ui::Active>>,
active_epoch: Query<Entity, (With<ui::Active>, With<EpochId>)>,
mut commands: Commands,
) {
// Each time a Scene is selected in the editor
events.iter().for_each(|ui::TargetAsset { handle }| {
// Iterate over all (0 or 1) active epochs
active_epoch.iter().for_each(|entity| {
// Set the Scene (overwrite existing Scene selections)
commands.entity(entity).insert(EpochMonologue {
monologue: handle.clone(),
});
});
});
}
fn load_epoch_monologue(
events: Query<Option<&EpochMonologue>, (Added<ui::Active>, With<EpochId>)>,
) {
events.iter().for_each(|epoch_monologue| {
warn!("TODO: unset epoch Monologue!");
epoch_monologue
.iter()
.for_each(|EpochMonologue { monologue }| {
warn!("TODO: Set level epoch");
});
});
}
fn set_epoch_font(
events: Query<&ui::TargetAsset<Font>, Added<ui::Active>>,
active_epoch: Query<Entity, (With<ui::Active>, With<EpochId>)>,
mut commands: Commands,
) {
// Each time a Scene is selected in the editor
events.iter().for_each(|ui::TargetAsset { handle }| {
// Iterate over all (0 or 1) active epochs
active_epoch.iter().for_each(|entity| {
// Set the Scene (overwrite existing Scene selections)
commands.entity(entity).insert(EpochFont {
font: handle.clone(),
});
});
});
}
fn load_epoch_font(
events: Query<Option<&EpochFont>, (Added<ui::Active>, With<EpochId>)>,
mut font_info: ResMut<FontInfo>,
) {
events.iter().for_each(|epoch_font| {
font_info.default = epoch_font.map(|EpochFont { font }| font.clone());
});
}
fn set_epoch_sfx(
events: Query<&ui::TargetAsset<AudioSource>, Added<ui::Active>>,
mut active_epoch: Query<(Entity, Option<&mut EpochSfx>), (With<ui::Active>, With<EpochId>)>,
mut commands: Commands,
) {
// Each time a Scene is selected in the editor
events.iter().for_each(|ui::TargetAsset { handle }| {
// Iterate over all (0 or 1) active epochs
active_epoch.iter_mut().for_each(|(entity, maybe_sfx)| {
info!("Adding sfx {:?} to epoch {:?}", maybe_sfx, entity);
if let Some(mut epoch_sfx) = maybe_sfx {
epoch_sfx.sfx.push(handle.clone());
} else {
// Set the Scene (overwrite existing Scene selections)
commands.entity(entity).insert(EpochSfx {
sfx: vec![handle.clone()],
});
}
});
});
}
fn load_epoch_sfx(
added: Query<Entity, (Added<ui::Active>, With<EpochId>)>,
mut removed: RemovedComponents<ui::Active>,
epoch_sfx: Query<&EpochSfx>,
mut writer: EventWriter<ControlAudio>,
) {
removed.iter().for_each(|entity| {
epoch_sfx.get(entity).iter().for_each(|EpochSfx { sfx }| {
sfx.iter().for_each(|handle| {
writer.send(ControlAudio::Stop(handle.clone()));
});
});
});
added.iter().for_each(|entity| {
epoch_sfx.get(entity).iter().for_each(|EpochSfx { sfx }| {
sfx.iter().for_each(|handle| {
writer.send(ControlAudio::Loop(handle.clone()));
});
});
});
}
fn set_epoch_animations(
events: Query<&ui::TargetAsset<AnimationClip>, Added<ui::Active>>,
mut active_epoch: Query<
(Entity, Option<&mut EpochAnimations>),
(With<ui::Active>, With<EpochId>),
>,
mut commands: Commands,
) {
// Each time a Scene is selected in the editor
events.iter().for_each(|ui::TargetAsset { handle }| {
// Iterate over all (0 or 1) active epochs
active_epoch
.iter_mut()
.for_each(|(entity, maybe_animations)| {
if let Some(mut epoch_animations) = maybe_animations {
epoch_animations.animations.push(handle.clone());
} else {
// Set the Scene (overwrite existing Scene selections)
commands.entity(entity).insert(EpochAnimations {
animations: vec![handle.clone()],
});
}
});
});
}
fn load_epoch_animations(
events: Query<Option<&EpochAnimations>, (Added<ui::Active>, With<EpochId>)>,
) {
events.iter().for_each(|epoch_animations| {
warn!("TODO: Load epoch Animations!");
})
}

Loading…
Cancel
Save