use std::{fs::File, io::Write}; use bevy::{audio::PlaybackMode, prelude::*, tasks::IoTaskPool}; use monologue_trees::{debug::*, ui}; fn main() { App::new() .init_resource::() .add_plugins(( DefaultPlugins.set(WindowPlugin { primary_window: Some(Window { title: "Serialization WTF".into(), resolution: (640., 480.).into(), ..default() }), ..default() }), DebugInfoPlugin, ui::GameUiPlugin { ..default() }, )) .add_systems(Startup, init) .add_systems( Update, ( export.run_if(interaction_condition::), clear.run_if(interaction_condition::), import.run_if(interaction_condition::), rehydrate::, rehydrate::, PlaybackSettings>, inspect.run_if(interaction_condition::), fallback_camera.run_if(fallback_camera_condition), ), ) .run(); } #[derive(Debug, Component, Reflect, Default)] #[reflect(Component)] struct LevelRoot; #[derive(Debug, Component)] struct ExportAction; #[derive(Debug, Component)] struct ClearAction; #[derive(Debug, Component)] struct ImportAction; #[derive(Debug, Component)] struct InspectAction; #[derive(Debug, Resource, Default)] struct AssetRegistry { handles: Vec, } fn init(server: Res, mut commands: Commands, mut registry: ResMut) { commands.spawn(( Camera2dBundle { ..default() }, UiCameraConfig { show_ui: true }, )); let audio_handle: Handle = server.load("audio/Ambient/Lake Sound 1.ogg"); let scene_handle: Handle = server.load("models/materials.glb#Scene0"); commands .spawn((SpatialBundle { ..default() }, LevelRoot)) .with_children(|parent| { parent.spawn(AudioSourceBundle { source: audio_handle.clone(), settings: PlaybackSettings { mode: PlaybackMode::Loop, paused: false, ..default() }, }); parent.spawn((SceneBundle { scene: scene_handle.clone(), ..default() },)); }); registry.handles.push(audio_handle.clone_untyped()); registry.handles.push(scene_handle.clone_untyped()); commands .spawn(( NodeBundle { style: Style { top: Val::Px(0.0), right: Val::Px(0.0), margin: UiRect::all(Val::Px(5.0)), padding: UiRect::all(Val::Px(5.0)), border: UiRect::all(Val::Px(1.0)), position_type: PositionType::Absolute, ..default() }, background_color: Color::WHITE.into(), border_color: Color::BLACK.into(), ..default() }, ui::Select::Action, )) .with_children(|parent| { parent.spawn(( ButtonBundle { style: Style { margin: UiRect::all(Val::Px(5.0)), padding: UiRect::all(Val::Px(5.0)), border: UiRect::all(Val::Px(1.0)), ..default() }, border_color: Color::BLACK.into(), ..default() }, ui::Title { text: "Export".into(), ..default() }, ExportAction, )); parent.spawn(( ButtonBundle { style: Style { margin: UiRect::all(Val::Px(5.0)), padding: UiRect::all(Val::Px(5.0)), border: UiRect::all(Val::Px(1.0)), ..default() }, border_color: Color::BLACK.into(), ..default() }, ui::Title { text: "Clear".into(), ..default() }, ClearAction, )); parent.spawn(( ButtonBundle { style: Style { margin: UiRect::all(Val::Px(5.0)), padding: UiRect::all(Val::Px(5.0)), border: UiRect::all(Val::Px(1.0)), ..default() }, border_color: Color::BLACK.into(), ..default() }, ui::Title { text: "Import".into(), ..default() }, ImportAction, )); parent.spawn(( ButtonBundle { style: Style { margin: UiRect::all(Val::Px(5.0)), padding: UiRect::all(Val::Px(5.0)), border: UiRect::all(Val::Px(1.0)), ..default() }, border_color: Color::BLACK.into(), ..default() }, ui::Title { text: "Inspect".into(), ..default() }, InspectAction, )); }); } fn interaction_condition( events: Query<&Interaction, (Changed, With)>, ) -> bool { events .iter() .find(|&interaction| *interaction == Interaction::Pressed) .is_some() } fn rehydrate( events: Query, Without)>, mut commands: Commands, ) { events.iter().for_each(|entity| { info!("Rehydrating {:?}", WO::default()); commands.entity(entity).insert(WO::default()); }); } fn ser( root: &Query>, children: &Query<&Children>, world: &World, ) -> String { let app_type_registry = world.resource::().clone(); let mut builder = DynamicSceneBuilder::from_world(world.clone()); builder.deny_all_resources(); // builder.allow_all(); builder.deny::(); // Level administrivia builder.allow::(); // Scene components builder.allow::>(); builder.allow::(); builder.allow::(); builder.allow::(); // Audio components builder.allow::>(); builder.allow::(); root.iter().for_each(|r| { // Extract the level root builder.extract_entity(r); // Extract all level root children builder.extract_entities( children .get(r) .expect("Root has children") .iter() .map(|&entity| entity), ); }); let scene = builder.build(); scene .serialize_ron(&app_type_registry) .expect("Serialize scene") } fn export(root: Query>, children: Query<&Children>, world: &World) { info!("Export level"); let serialized = ser(&root, &children, world); 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(); } fn inspect(root: Query>, children: Query<&Children>, world: &World) { info!("Inexpect level"); let serialized = ser(&root, &children, world); print!("{}", serialized); } fn clear(root: Query>, mut commands: Commands) { info!("Clearing level"); root.iter().for_each(|entity| { commands.entity(entity).despawn_recursive(); }); } fn import(mut commands: Commands, server: Res) { info!("Importing level"); let scene_handle: Handle = server.load("output.scn.ron"); commands.spawn(( LevelRoot, DynamicSceneBundle { scene: scene_handle.clone(), ..default() }, )); } fn fallback_camera_condition( added: Query>, mut removed: RemovedComponents, ) -> bool { added.iter().chain(removed.iter()).count() > 0 } fn fallback_camera( mut ui_camera: Query<&mut Camera, With>, cameras: Query<&mut Camera, Without>, ) { ui_camera.single_mut().is_active = cameras.iter().len() <= 0; }