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