bevy_yoleck/lib.rs
1//! # Your Own Level Editor Creation Kit
2//!
3//! Yoleck is a crate for having a game built with the Bevy game engine act as its own level
4//! editor.
5//!
6//! Yoleck uses Plain Old Rust Structs to store the data, and uses Serde to store them in files.
7//! The user code defines _populate systems_ for creating Bevy entities (populating their
8//! components) from these structs and _edit systems_ to edit these structs with egui.
9//!
10//! The synchronization between the structs and the files is bidirectional, and so is the
11//! synchronization between the structs and the egui widgets, but the synchronization from the
12//! structs to the entities is unidirectional - changes in the entities are not reflected in the
13//! structs:
14//!
15//! ```none
16//! ┌────────┐ Populate ┏━━━━━━━━━┓ Edit ┌───────┐
17//! │Bevy │ Systems ┃Yoleck ┃ Systems │egui │
18//! │Entities│◄────────────┃Component┃◄═══════════►│Widgets│
19//! └────────┘ ┃Structs ┃ └───────┘
20//! ┗━━━━━━━━━┛
21//! ▲
22//! ║
23//! ║ Serde
24//! ║
25//! ▼
26//! ┌─────┐
27//! │.yol │
28//! │Files│
29//! └─────┘
30//! ```
31//!
32//! To support integrate Yoleck, a game needs to:
33//!
34//! * Define the component structs, and make sure they implement:
35//! ```text
36//! #[derive(Default, Clone, PartialEq, Component, Serialize, Deserialize, YoleckComponent)]
37//! ```
38//! * For each entity type that can be created in the level editor, use
39//! [`add_yoleck_entity_type`](YoleckExtForApp::add_yoleck_entity_type) to add a
40//! [`YoleckEntityType`]. Use [`YoleckEntityType::with`] to register the
41//! [`YoleckComponent`](crate::specs_registration::YoleckComponent)s for that entity type.
42//! * Register edit systems with
43//! [`add_yoleck_edit_system`](YoleckExtForApp::add_yoleck_edit_system).
44//! * Register populate systems on [`YoleckSchedule::Populate`]
45//! * If the application starts in editor mode:
46//! * Add the `EguiPlugin` plugin.
47//! * Add the [`YoleckPluginForEditor`] plugin.
48//! * Use [`YoleckSyncWithEditorState`](crate::editor::YoleckSyncWithEditorState) to synchronize
49//! the game's state with the [`YoleckEditorState`] (optional but highly recommended)
50//! * If the application starts in game mode:
51//! * Add the [`YoleckPluginForGame`] plugin.
52//! * Use the [`YoleckLevelIndex`] asset to determine the list of available levels (optional)
53//! * Spawn an entity with the [`YoleckLoadLevel`](entity_management::YoleckLoadLevel) component
54//! to load the level. Note that the level can be unloaded by despawning that entity or by
55//! removing the [`YoleckKeepLevel`] component that will automatically be added to it.
56//!
57//! To support picking and moving entities in the viewport with the mouse, check out the
58//! [`vpeol_2d`] and [`vpeol_3d`] modules. After adding the appropriate feature flag
59//! (`vpeol_2d`/`vpeol_3d`), import their types from
60//! [`bevy_yoleck::vpeol::prelude::*`](crate::vpeol::prelude).
61//!
62//! # Example
63//!
64//! ```no_run
65//! use bevy::prelude::*;
66//! use bevy_yoleck::bevy_egui::EguiPlugin;
67//! use bevy_yoleck::prelude::*;
68//! use serde::{Deserialize, Serialize};
69//! # use bevy_yoleck::egui;
70//!
71//! fn main() {
72//! let is_editor = std::env::args().any(|arg| arg == "--editor");
73//!
74//! let mut app = App::new();
75//! app.add_plugins(DefaultPlugins);
76//! if is_editor {
77//! // Doesn't matter in this example, but a proper game would have systems that can work
78//! // on the entity in `GameState::Game`, so while the level is edited we want to be in
79//! // `GameState::Editor` - which can be treated as a pause state. When the editor wants
80//! // to playtest the level we want to move to `GameState::Game` so that they can play it.
81//! app.add_plugins(EguiPlugin {
82//! enable_multipass_for_primary_context: true,
83//! });
84//! app.add_plugins(YoleckSyncWithEditorState {
85//! when_editor: GameState::Editor,
86//! when_game: GameState::Game,
87//! });
88//! app.add_plugins(YoleckPluginForEditor);
89//! } else {
90//! app.add_plugins(YoleckPluginForGame);
91//! app.init_state::<GameState>();
92//! // In editor mode Yoleck takes care of level loading. In game mode the game needs to
93//! // tell yoleck which levels to load and when.
94//! app.add_systems(Update, load_first_level.run_if(in_state(GameState::Loading)));
95//! }
96//! app.add_systems(Startup, setup_camera);
97//!
98//! app.add_yoleck_entity_type({
99//! YoleckEntityType::new("Rectangle")
100//! .with::<Rectangle>()
101//! });
102//! app.add_yoleck_edit_system(edit_rectangle);
103//! app.add_systems(YoleckSchedule::Populate, populate_rectangle);
104//!
105//! app.run();
106//! }
107//!
108//! #[derive(States, Default, Debug, Clone, PartialEq, Eq, Hash)]
109//! enum GameState {
110//! #[default]
111//! Loading,
112//! Game,
113//! Editor,
114//! }
115//!
116//! fn setup_camera(mut commands: Commands) {
117//! commands.spawn(Camera2d::default());
118//! }
119//!
120//! #[derive(Clone, PartialEq, Serialize, Deserialize, Component, YoleckComponent)]
121//! struct Rectangle {
122//! width: f32,
123//! height: f32,
124//! }
125//!
126//! impl Default for Rectangle {
127//! fn default() -> Self {
128//! Self {
129//! width: 50.0,
130//! height: 50.0,
131//! }
132//! }
133//! }
134//!
135//! fn populate_rectangle(mut populate: YoleckPopulate<&Rectangle>) {
136//! populate.populate(|_ctx, mut cmd, rectangle| {
137//! cmd.insert(Sprite {
138//! color: bevy::color::palettes::css::RED.into(),
139//! custom_size: Some(Vec2::new(rectangle.width, rectangle.height)),
140//! ..Default::default()
141//! });
142//! });
143//! }
144//!
145//! fn edit_rectangle(mut ui: ResMut<YoleckUi>, mut edit: YoleckEdit<&mut Rectangle>) {
146//! let Ok(mut rectangle) = edit.single_mut() else { return };
147//! ui.add(egui::Slider::new(&mut rectangle.width, 50.0..=500.0).prefix("Width: "));
148//! ui.add(egui::Slider::new(&mut rectangle.height, 50.0..=500.0).prefix("Height: "));
149//! }
150//!
151//! fn load_first_level(
152//! mut level_index_handle: Local<Option<Handle<YoleckLevelIndex>>>,
153//! asset_server: Res<AssetServer>,
154//! level_index_assets: Res<Assets<YoleckLevelIndex>>,
155//! mut commands: Commands,
156//! mut game_state: ResMut<NextState<GameState>>,
157//! ) {
158//! // Keep the handle in local resource, so that Bevy will not unload the level index asset
159//! // between frames.
160//! let level_index_handle = level_index_handle
161//! .get_or_insert_with(|| asset_server.load("levels/index.yoli"))
162//! .clone();
163//! let Some(level_index) = level_index_assets.get(&level_index_handle) else {
164//! // During the first invocation of this system, the level index asset is not going to be
165//! // loaded just yet. Since this system is going to run on every frame during the Loading
166//! // state, it just has to keep trying until it starts in a frame where it is loaded.
167//! return;
168//! };
169//! // A proper game would have a proper level progression system, but here we are just
170//! // taking the first level and loading it.
171//! let level_handle: Handle<YoleckRawLevel> =
172//! asset_server.load(&format!("levels/{}", level_index[0].filename));
173//! commands.spawn(YoleckLoadLevel(level_handle));
174//! game_state.set(GameState::Game);
175//! }
176//! ```
177
178mod editing;
179mod editor;
180mod editor_window;
181mod entity_management;
182mod entity_upgrading;
183mod entity_uuid;
184mod errors;
185pub mod exclusive_systems;
186pub mod knobs;
187mod level_files_manager;
188pub mod level_files_upgrading;
189mod level_index;
190mod picking_helpers;
191mod populating;
192mod specs_registration;
193mod util;
194#[cfg(feature = "vpeol")]
195pub mod vpeol;
196#[cfg(feature = "vpeol_2d")]
197pub mod vpeol_2d;
198#[cfg(feature = "vpeol_3d")]
199pub mod vpeol_3d;
200
201use std::any::{Any, TypeId};
202use std::path::Path;
203use std::sync::Arc;
204
205use bevy::ecs::schedule::ScheduleLabel;
206use bevy::ecs::system::{EntityCommands, SystemId};
207use bevy::platform::collections::HashMap;
208use bevy::prelude::*;
209use bevy_egui::EguiContextPass;
210
211pub mod prelude {
212 pub use crate::editing::{YoleckEdit, YoleckUi};
213 pub use crate::editor::{YoleckEditorState, YoleckPassedData, YoleckSyncWithEditorState};
214 pub use crate::entity_management::{YoleckKeepLevel, YoleckLoadLevel, YoleckRawLevel};
215 pub use crate::entity_upgrading::YoleckEntityUpgradingPlugin;
216 pub use crate::entity_uuid::{YoleckEntityUuid, YoleckUuidRegistry};
217 pub use crate::knobs::YoleckKnobs;
218 pub use crate::level_index::{YoleckLevelIndex, YoleckLevelIndexEntry};
219 pub use crate::populating::{YoleckMarking, YoleckPopulate};
220 pub use crate::specs_registration::{YoleckComponent, YoleckEntityType};
221 pub use crate::{
222 YoleckBelongsToLevel, YoleckExtForApp, YoleckLevelInEditor, YoleckLevelInPlaytest,
223 YoleckLevelJustLoaded, YoleckPluginForEditor, YoleckPluginForGame, YoleckSchedule,
224 };
225 pub use bevy_yoleck_macros::YoleckComponent;
226}
227
228pub use self::editing::YoleckEditMarker;
229pub use self::editor::YoleckDirective;
230pub use self::editor::YoleckEditorEvent;
231use self::editor::YoleckEditorState;
232pub use self::editor_window::YoleckEditorSection;
233pub use self::picking_helpers::*;
234
235use self::entity_management::{EntitiesToPopulate, YoleckRawLevel};
236use self::entity_upgrading::YoleckEntityUpgrading;
237use self::exclusive_systems::YoleckExclusiveSystemsPlugin;
238use self::knobs::YoleckKnobsCache;
239pub use self::level_files_manager::YoleckEditorLevelsDirectoryPath;
240pub use self::level_index::YoleckEditableLevels;
241use self::level_index::YoleckLevelIndex;
242pub use self::populating::{YoleckPopulateContext, YoleckSystemMarker};
243use self::prelude::{YoleckKeepLevel, YoleckUuidRegistry};
244use self::specs_registration::{YoleckComponentHandler, YoleckEntityType};
245use self::util::EditSpecificResources;
246pub use bevy_egui;
247pub use bevy_egui::egui;
248
249struct YoleckPluginBase;
250pub struct YoleckPluginForGame;
251pub struct YoleckPluginForEditor;
252
253#[derive(Debug, Clone, PartialEq, Eq, Hash, SystemSet)]
254enum YoleckSystemSet {
255 ProcessRawEntities,
256 RunPopulateSchedule,
257}
258
259#[derive(Debug, Clone, PartialEq, Eq, Hash, SystemSet)]
260pub(crate) struct YoleckRunEditSystemsSystemSet;
261
262impl Plugin for YoleckPluginBase {
263 fn build(&self, app: &mut App) {
264 app.init_resource::<YoleckEntityConstructionSpecs>();
265 app.insert_resource(YoleckUuidRegistry(Default::default()));
266 app.register_asset_loader(entity_management::YoleckLevelAssetLoader);
267 app.init_asset::<YoleckRawLevel>();
268 app.register_asset_loader(level_index::YoleckLevelIndexLoader);
269 app.init_asset::<YoleckLevelIndex>();
270
271 app.configure_sets(
272 Update,
273 (
274 YoleckSystemSet::ProcessRawEntities,
275 YoleckSystemSet::RunPopulateSchedule,
276 )
277 .chain(),
278 );
279
280 app.add_systems(
281 Update,
282 (
283 entity_management::yoleck_process_raw_entries,
284 ApplyDeferred,
285 (
286 entity_management::yoleck_run_level_loaded_schedule,
287 entity_management::yoleck_remove_just_loaded_marker_from_levels,
288 ApplyDeferred,
289 )
290 .chain()
291 .run_if(
292 |freshly_loaded_level_entities: Query<
293 (),
294 (With<YoleckLevelJustLoaded>, Without<YoleckLevelInEditor>),
295 >| { !freshly_loaded_level_entities.is_empty() },
296 ),
297 )
298 .chain()
299 .in_set(YoleckSystemSet::ProcessRawEntities),
300 );
301 app.insert_resource(EntitiesToPopulate(Default::default()));
302 app.add_systems(
303 Update,
304 (
305 entity_management::yoleck_prepare_populate_schedule,
306 entity_management::yoleck_run_populate_schedule.run_if(
307 |entities_to_populate: Res<EntitiesToPopulate>| {
308 !entities_to_populate.0.is_empty()
309 },
310 ),
311 )
312 .chain()
313 .in_set(YoleckSystemSet::RunPopulateSchedule),
314 );
315 app.add_systems(
316 Update,
317 ((
318 entity_management::process_unloading_command,
319 entity_management::process_loading_command,
320 ApplyDeferred,
321 )
322 .chain()
323 .before(YoleckSystemSet::ProcessRawEntities),),
324 );
325 app.add_schedule(Schedule::new(YoleckSchedule::Populate));
326 app.add_schedule(Schedule::new(YoleckSchedule::LevelLoaded));
327 app.add_schedule(Schedule::new(YoleckSchedule::OverrideCommonComponents));
328 }
329}
330
331impl Plugin for YoleckPluginForGame {
332 fn build(&self, app: &mut App) {
333 app.init_state::<YoleckEditorState>();
334 app.add_systems(
335 Startup,
336 |mut state: ResMut<NextState<YoleckEditorState>>| {
337 state.set(YoleckEditorState::GameActive);
338 },
339 );
340 app.add_plugins(YoleckPluginBase);
341 }
342}
343
344impl Plugin for YoleckPluginForEditor {
345 fn build(&self, app: &mut App) {
346 app.init_state::<YoleckEditorState>();
347 app.add_event::<YoleckEditorEvent>();
348 app.add_plugins(YoleckPluginBase);
349 app.add_plugins(YoleckExclusiveSystemsPlugin);
350 app.init_resource::<YoleckEditSystems>();
351 app.insert_resource(YoleckKnobsCache::default());
352 let level_being_edited = app
353 .world_mut()
354 .spawn((YoleckLevelInEditor, YoleckKeepLevel))
355 .id();
356 app.insert_resource(YoleckState {
357 level_being_edited,
358 level_needs_saving: false,
359 });
360 app.insert_resource(YoleckEditorLevelsDirectoryPath(
361 Path::new(".").join("assets").join("levels"),
362 ));
363 app.insert_resource(YoleckEditorSections::default());
364 app.insert_resource(EditSpecificResources::new().with(YoleckEditableLevels {
365 levels: Default::default(),
366 }));
367 app.add_event::<YoleckDirective>();
368 app.configure_sets(
369 Update,
370 YoleckRunEditSystemsSystemSet.after(YoleckSystemSet::ProcessRawEntities),
371 );
372 app.add_systems(
373 EguiContextPass,
374 editor_window::yoleck_editor_window.in_set(YoleckRunEditSystemsSystemSet),
375 );
376
377 app.add_schedule(Schedule::new(
378 YoleckInternalSchedule::UpdateManagedDataFromComponents,
379 ));
380 }
381}
382
383pub trait YoleckExtForApp {
384 /// Add a type of entity that can be edited in Yoleck's level editor.
385 ///
386 /// ```no_run
387 /// # use bevy::prelude::*;
388 /// # use bevy_yoleck::prelude::*;
389 /// # use serde::{Deserialize, Serialize};
390 /// # #[derive(Default, Clone, PartialEq, Serialize, Deserialize, Component, YoleckComponent)]
391 /// # struct Component1;
392 /// # type Component2 = Component1;
393 /// # type Component3 = Component1;
394 /// # let mut app = App::new();
395 /// app.add_yoleck_entity_type({
396 /// YoleckEntityType::new("MyEntityType")
397 /// .with::<Component1>()
398 /// .with::<Component2>()
399 /// .with::<Component3>()
400 /// });
401 /// ```
402 fn add_yoleck_entity_type(&mut self, entity_type: YoleckEntityType);
403
404 /// Add a system for editing Yoleck components in the level editor.
405 ///
406 /// ```no_run
407 /// # use bevy::prelude::*;
408 /// # use bevy_yoleck::prelude::*;
409 /// # use serde::{Deserialize, Serialize};
410 /// # #[derive(Default, Clone, PartialEq, Serialize, Deserialize, Component, YoleckComponent)]
411 /// # struct Component1;
412 /// # let mut app = App::new();
413 ///
414 /// app.add_yoleck_edit_system(edit_component1);
415 ///
416 /// fn edit_component1(mut ui: ResMut<YoleckUi>, mut edit: YoleckEdit<&mut Component1>) {
417 /// let Ok(component1) = edit.single_mut() else { return };
418 /// // Edit `component1` with the `ui`
419 /// }
420 /// ```
421 ///
422 /// See [`YoleckEdit`](crate::editing::YoleckEdit).
423 fn add_yoleck_edit_system<P>(&mut self, system: impl 'static + IntoSystem<(), (), P>);
424
425 /// Register a function that upgrades entities from a previous version of the app format.
426 ///
427 /// This should only be called _after_ adding
428 /// [`YoleckEntityUpgradingPlugin`](crate::entity_upgrading::YoleckEntityUpgradingPlugin). See
429 /// that plugin's docs for more info.
430 fn add_yoleck_entity_upgrade(
431 &mut self,
432 to_version: usize,
433 upgrade_dlg: impl 'static + Send + Sync + Fn(&str, &mut serde_json::Value),
434 );
435
436 /// Register a function that upgrades entities of a specific type from a previous version of
437 /// the app format.
438 fn add_yoleck_entity_upgrade_for(
439 &mut self,
440 to_version: usize,
441 for_type_name: impl ToString,
442 upgrade_dlg: impl 'static + Send + Sync + Fn(&mut serde_json::Value),
443 ) {
444 let for_type_name = for_type_name.to_string();
445 self.add_yoleck_entity_upgrade(to_version, move |type_name, data| {
446 if type_name == for_type_name {
447 upgrade_dlg(data);
448 }
449 });
450 }
451}
452
453impl YoleckExtForApp for App {
454 fn add_yoleck_entity_type(&mut self, entity_type: YoleckEntityType) {
455 let construction_specs = self
456 .world_mut()
457 .get_resource_or_insert_with(YoleckEntityConstructionSpecs::default);
458
459 let mut component_type_ids = Vec::with_capacity(entity_type.components.len());
460 let mut component_handlers_to_register = Vec::new();
461 for handler in entity_type.components.into_iter() {
462 component_type_ids.push(handler.component_type());
463 if !construction_specs
464 .component_handlers
465 .contains_key(&handler.component_type())
466 {
467 component_handlers_to_register.push(handler);
468 }
469 }
470
471 for handler in component_handlers_to_register.iter() {
472 handler.build_in_bevy_app(self);
473 }
474
475 let new_entry = YoleckEntityTypeInfo {
476 name: entity_type.name.clone(),
477 components: component_type_ids,
478 on_init: entity_type.on_init,
479 has_uuid: entity_type.has_uuid,
480 };
481
482 let mut construction_specs = self
483 .world_mut()
484 .get_resource_mut::<YoleckEntityConstructionSpecs>()
485 .expect("YoleckEntityConstructionSpecs was inserted earlier in this function");
486
487 let new_index = construction_specs.entity_types.len();
488 construction_specs
489 .entity_types_index
490 .insert(entity_type.name, new_index);
491 construction_specs.entity_types.push(new_entry);
492 for handler in component_handlers_to_register {
493 // Can handlers can register systems? If so, this needs to be broken into two phases...
494 construction_specs
495 .component_handlers
496 .insert(handler.component_type(), handler);
497 }
498 }
499
500 fn add_yoleck_edit_system<P>(&mut self, system: impl 'static + IntoSystem<(), (), P>) {
501 let system_id = self.world_mut().register_system(system);
502 let mut edit_systems = self
503 .world_mut()
504 .get_resource_or_insert_with(YoleckEditSystems::default);
505 edit_systems.edit_systems.push(system_id);
506 }
507
508 fn add_yoleck_entity_upgrade(
509 &mut self,
510 to_version: usize,
511 upgrade_dlg: impl 'static + Send + Sync + Fn(&str, &mut serde_json::Value),
512 ) {
513 let mut entity_upgrading = self.world_mut().get_resource_mut::<YoleckEntityUpgrading>()
514 .expect("add_yoleck_entity_upgrade can only be called after the YoleckEntityUpgrading plugin was added");
515 if entity_upgrading.app_format_version < to_version {
516 panic!("Cannot create an upgrade system to version {} when YoleckEntityUpgrading set the version to {}", to_version, entity_upgrading.app_format_version);
517 }
518 entity_upgrading
519 .upgrade_functions
520 .entry(to_version)
521 .or_default()
522 .push(Box::new(upgrade_dlg));
523 }
524}
525
526type BoxedArc = Arc<dyn Send + Sync + Any>;
527type BoxedAny = Box<dyn Send + Sync + Any>;
528
529/// A component that describes how Yoleck manages an entity under its control.
530#[derive(Component)]
531pub struct YoleckManaged {
532 /// A name to display near the entity in the entities list.
533 ///
534 /// This is for level editors' convenience only - it will not be used in the games.
535 pub name: String,
536
537 /// The type of the Yoleck entity, as registered with
538 /// [`add_yoleck_entity_type`](YoleckExtForApp::add_yoleck_entity_type).
539 ///
540 /// This defines the Yoleck components that can be edited for the entity.
541 pub type_name: String,
542
543 lifecycle_status: YoleckEntityLifecycleStatus,
544
545 components_data: HashMap<TypeId, BoxedAny>,
546}
547
548/// A marker for entities that belongs to the Yoleck level and should be despawned with it.
549///
550/// Yoleck already adds this automatically to entities created from the editor. The game itself
551/// should add this to entities created during gameplay, like bullets or spawned enemeis, so that
552/// they'll be despawned when a playtest is finished or restarted.
553///
554/// When removing a [`YoleckKeepLevel`] from entity (or removing the entire entity), Yoleck will
555/// automatically despawn all the entities that have this component and point to that level.
556///
557/// There is no need to add this to child entities of entities that already has this marker,
558/// because Bevy will already despawn them when despawning their parent.
559#[derive(Component, Debug, Clone)]
560pub struct YoleckBelongsToLevel {
561 /// The entity which was used with [`YoleckLoadLevel`](entity_management::YoleckLoadLevel) to
562 /// load the level that this entity belongs to.
563 pub level: Entity,
564}
565
566pub enum YoleckEntityLifecycleStatus {
567 Synchronized,
568 JustCreated,
569 JustChanged,
570}
571
572#[derive(Default, Resource)]
573struct YoleckEditSystems {
574 edit_systems: Vec<SystemId>,
575}
576
577impl YoleckEditSystems {
578 pub(crate) fn run_systems(&mut self, world: &mut World) {
579 for system_id in self.edit_systems.iter() {
580 world
581 .run_system(*system_id)
582 .expect("edit systems handled by Yoleck - system should been properly handled");
583 }
584 }
585}
586
587pub(crate) struct YoleckEntityTypeInfo {
588 pub name: String,
589 pub components: Vec<TypeId>,
590 #[allow(clippy::type_complexity)]
591 pub(crate) on_init:
592 Vec<Box<dyn 'static + Sync + Send + Fn(YoleckEditorState, &mut EntityCommands)>>,
593 pub has_uuid: bool,
594}
595
596#[derive(Default, Resource)]
597pub(crate) struct YoleckEntityConstructionSpecs {
598 pub entity_types: Vec<YoleckEntityTypeInfo>,
599 pub entity_types_index: HashMap<String, usize>,
600 pub component_handlers: HashMap<TypeId, Box<dyn YoleckComponentHandler>>,
601}
602
603impl YoleckEntityConstructionSpecs {
604 pub fn get_entity_type_info(&self, entity_type: &str) -> Option<&YoleckEntityTypeInfo> {
605 Some(&self.entity_types[*self.entity_types_index.get(entity_type)?])
606 }
607}
608
609/// Fields of the Yoleck editor.
610#[derive(Resource)]
611pub(crate) struct YoleckState {
612 level_being_edited: Entity,
613 level_needs_saving: bool,
614}
615
616/// Sections for the Yoleck editor window.
617///
618/// Already contains sections by default, but can be used to customize the editor by adding more
619/// sections. Each section is a function/closure that accepts a world and returns a closure that
620/// accepts as world and a UI. The outer closure is responsible for prepareing a `SystemState` for
621/// the inner closure to use.
622///
623/// ```no_run
624/// # use bevy::prelude::*;
625/// use bevy::ecs::system::SystemState;
626/// # use bevy_yoleck::{YoleckEditorSections, egui};
627/// # let mut app = App::new();
628/// app.world_mut().resource_mut::<YoleckEditorSections>().0.push((|world: &mut World| {
629/// let mut system_state = SystemState::<(
630/// Res<Time>,
631/// )>::new(world);
632/// move |world: &mut World, ui: &mut egui::Ui| {
633/// let (
634/// time,
635/// ) = system_state.get_mut(world);
636/// ui.label(format!("Time since startup is {:?}", time.elapsed()));
637/// }
638/// }).into());
639/// ```
640#[derive(Resource)]
641pub struct YoleckEditorSections(pub Vec<YoleckEditorSection>);
642
643impl Default for YoleckEditorSections {
644 fn default() -> Self {
645 YoleckEditorSections(vec![
646 level_files_manager::level_files_manager_section.into(),
647 editor::new_entity_section.into(),
648 editor::entity_selection_section.into(),
649 editor::entity_editing_section.into(),
650 ])
651 }
652}
653#[derive(ScheduleLabel, Debug, Clone, PartialEq, Eq, Hash)]
654pub(crate) enum YoleckInternalSchedule {
655 UpdateManagedDataFromComponents,
656}
657
658/// Schedules for user code to do the actual entity/level population after Yoleck spawns the level
659/// "skeleton".
660#[derive(ScheduleLabel, Debug, Clone, PartialEq, Eq, Hash)]
661pub enum YoleckSchedule {
662 /// This is where user defined populate systems should reside.
663 ///
664 /// Note that populate systems, rather than directly trying to query the entities to be
665 /// populated, should use [`YoleckPopulate`](crate::prelude::YoleckPopulate):
666 ///
667 /// ```no_run
668 /// # use bevy::prelude::*;
669 /// # use bevy_yoleck::prelude::*;
670 /// # use serde::{Deserialize, Serialize};
671 /// # #[derive(Default, Clone, PartialEq, Serialize, Deserialize, Component, YoleckComponent)]
672 /// # struct Component1;
673 /// # let mut app = App::new();
674 ///
675 /// app.add_systems(YoleckSchedule::Populate, populate_component1);
676 ///
677 /// fn populate_component1(mut populate: YoleckPopulate<&Component1>) {
678 /// populate.populate(|_ctx, mut cmd, component1| {
679 /// // Add Bevy components derived from `component1` to `cmd`.
680 /// });
681 /// }
682 /// ```
683 Populate,
684 /// Right after all the level entities are loaded, but before any populate systems manage to
685 /// run.
686 LevelLoaded,
687 /// Since many bundles add their own transform and visibility components, systems that override
688 /// them explicitly need to go here.
689 OverrideCommonComponents,
690}
691
692/// Automatically added to level entities that are being edited in the level editor.
693#[derive(Component)]
694pub struct YoleckLevelInEditor;
695
696/// Automatically added to level entities that are being play-tested in the level editor.
697///
698/// Note that this only gets added to the levels that are launched from the editor UI. If game
699/// systems load new levels during the play-test, this component will not be added to them.
700#[derive(Component)]
701pub struct YoleckLevelInPlaytest;
702
703/// During the [`YoleckSchedule::LevelLoaded`] schedule, this component marks the level entities
704/// that were just loaded and triggered that schedule.
705///
706/// Note that this component will be removed after that schedule finishes running - it should not
707/// be relied on in systems outside that schedule.
708#[derive(Component)]
709pub struct YoleckLevelJustLoaded;