diff --git a/Cargo.toml b/Cargo.toml index 07035a36..b38cda7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ members = ["macros"] [dependencies] bevy_ecs_ldtk_macros = { version = "0.5.0", optional = true, path = "macros" } bevy_ecs_tilemap = { version = "0.9", default-features = false } -bevy = { version = "0.9", default-features = false, features = ["bevy_sprite"] } +bevy = { version = "0.10", default-features = false, features = ["bevy_sprite"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" regex = "1" @@ -25,8 +25,8 @@ anyhow = "1.0" thiserror = "1.0" [dev-dependencies] -bevy = "0.9" -bevy_rapier2d = "0.19" +bevy = "0.10" +bevy_rapier2d = "0.21" rand = "0.8" [features] @@ -38,3 +38,6 @@ render = ["bevy_ecs_tilemap/render"] [[example]] name = "platformer" path = "examples/platformer/main.rs" + +[patch.crates-io] +bevy_ecs_tilemap = { version = "0.9", git = "https://github.com/geieredgar/bevy_ecs_tilemap", branch = "bevy_track" } diff --git a/examples/platformer/components.rs b/examples/platformer/components.rs index 35af8ad3..59728316 100644 --- a/examples/platformer/components.rs +++ b/examples/platformer/components.rs @@ -70,7 +70,6 @@ impl From for SensorBundle { sensor: Sensor, rotation_constraints, active_events: ActiveEvents::COLLISION_EVENTS, - ..Default::default() } } else { SensorBundle::default() @@ -189,23 +188,21 @@ impl LdtkEntity for Patrol { .find(|f| f.identifier == *"patrol") .unwrap(); if let FieldValue::Points(ldtk_points) = &ldtk_patrol.value { - for ldtk_point in ldtk_points { - if let Some(ldtk_point) = ldtk_point { - // The +1 is necessary here due to the pivot of the entities in the sample - // file. - // The patrols set up in the file look flat and grounded, - // but technically they're not if you consider the pivot, - // which is at the bottom-center for the skulls. - let pixel_coords = (ldtk_point.as_vec2() + Vec2::new(0.5, 1.)) - * Vec2::splat(layer_instance.grid_size as f32); - - points.push(ldtk_pixel_coords_to_translation_pivoted( - pixel_coords.as_ivec2(), - layer_instance.c_hei * layer_instance.grid_size, - IVec2::new(entity_instance.width, entity_instance.height), - entity_instance.pivot, - )); - } + for ldtk_point in ldtk_points.iter().flatten() { + // The +1 is necessary here due to the pivot of the entities in the sample + // file. + // The patrols set up in the file look flat and grounded, + // but technically they're not if you consider the pivot, + // which is at the bottom-center for the skulls. + let pixel_coords = (ldtk_point.as_vec2() + Vec2::new(0.5, 1.)) + * Vec2::splat(layer_instance.grid_size as f32); + + points.push(ldtk_pixel_coords_to_translation_pivoted( + pixel_coords.as_ivec2(), + layer_instance.c_hei * layer_instance.grid_size, + IVec2::new(entity_instance.width, entity_instance.height), + entity_instance.pivot, + )); } } diff --git a/examples/platformer/main.rs b/examples/platformer/main.rs index 3a973a5a..9bf4fc38 100644 --- a/examples/platformer/main.rs +++ b/examples/platformer/main.rs @@ -14,6 +14,8 @@ fn main() { .add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest())) .add_plugin(LdtkPlugin) .add_plugin(RapierPhysicsPlugin::::pixels_per_meter(100.0)) + // Required to prevent race conditions between bevy_ecs_ldtk's and bevy_rapier's systems + .configure_set(LdtkSystemSet::ProcessApi.before(PhysicsSet::SyncBackend)) .insert_resource(RapierConfiguration { gravity: Vec2::new(0.0, -2000.0), ..Default::default() diff --git a/examples/platformer/systems.rs b/examples/platformer/systems.rs index 32edf65d..2ee0782e 100644 --- a/examples/platformer/systems.rs +++ b/examples/platformer/systems.rs @@ -309,6 +309,7 @@ pub fn patrol(mut query: Query<(&mut Transform, &mut Velocity, &mut Patrol)>) { const ASPECT_RATIO: f32 = 16. / 9.; +#[allow(clippy::type_complexity)] pub fn camera_fit_inside_current_level( mut camera_query: Query< ( @@ -339,27 +340,26 @@ pub fn camera_fit_inside_current_level( let level = &ldtk_level.level; if level_selection.is_match(&0, level) { let level_ratio = level.px_wid as f32 / ldtk_level.level.px_hei as f32; - - orthographic_projection.scaling_mode = bevy::render::camera::ScalingMode::None; - orthographic_projection.bottom = 0.; - orthographic_projection.left = 0.; + orthographic_projection.viewport_origin = Vec2::ZERO; if level_ratio > ASPECT_RATIO { // level is wider than the screen - orthographic_projection.top = (level.px_hei as f32 / 9.).round() * 9.; - orthographic_projection.right = orthographic_projection.top * ASPECT_RATIO; - camera_transform.translation.x = (player_translation.x - - level_transform.translation.x - - orthographic_projection.right / 2.) - .clamp(0., level.px_wid as f32 - orthographic_projection.right); + let height = (level.px_hei as f32 / 9.).round() * 9.; + let width = height * ASPECT_RATIO; + orthographic_projection.scaling_mode = + bevy::render::camera::ScalingMode::Fixed { width, height }; + camera_transform.translation.x = + (player_translation.x - level_transform.translation.x - width / 2.) + .clamp(0., level.px_wid as f32 - width); camera_transform.translation.y = 0.; } else { // level is taller than the screen - orthographic_projection.right = (level.px_wid as f32 / 16.).round() * 16.; - orthographic_projection.top = orthographic_projection.right / ASPECT_RATIO; - camera_transform.translation.y = (player_translation.y - - level_transform.translation.y - - orthographic_projection.top / 2.) - .clamp(0., level.px_hei as f32 - orthographic_projection.top); + let width = (level.px_wid as f32 / 16.).round() * 16.; + let height = width / ASPECT_RATIO; + orthographic_projection.scaling_mode = + bevy::render::camera::ScalingMode::Fixed { width, height }; + camera_transform.translation.y = + (player_translation.y - level_transform.translation.y - height / 2.) + .clamp(0., level.px_hei as f32 - height); camera_transform.translation.x = 0.; } diff --git a/src/lib.rs b/src/lib.rs index ec25db90..d6c19e8c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -138,33 +138,34 @@ mod plugin { use super::*; - /// [SystemLabel] used by the plugin for scheduling its systems. - #[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, SystemLabel)] - pub enum LdtkSystemLabel { - ProcessAssets, - LevelSelection, - LevelSet, - LevelSpawning, - Other, - } - - /// [StageLabel] for stages added by the plugin. - #[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, StageLabel)] - pub enum LdtkStage { - /// Occurs immediately after [CoreStage::Update]. + /// Base [SystemSet]s for systems added by the plugin. + #[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, SystemSet)] + #[system_set(base)] + pub enum LdtkSystemSet { + /// Scheduled after [CoreSet::UpdateFlush]. /// /// Used for systems that process components and resources provided by this plugin's API. - /// In particular, this stage processes.. + /// In particular, this set processes.. /// - [resources::LevelSelection] /// - [components::LevelSet] /// - [components::Worldly] /// - [components::Respawn] /// /// As a result, you can expect minimal frame delay when updating these in - /// [CoreStage::Update]. + /// [CoreSet::Update]. + /// + /// You might need to add additional scheduling constraints to prevent race conditions + /// between systems in this set and other external systems. As an example, `bevy_rapier`'s + /// `PhysicsSet::BackendSync` should be scheduled after `LdtkSystemSet::ProcessApi`. ProcessApi, } + #[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, SystemSet)] + enum ProcessApiSet { + PreClean, + Clean, + } + /// Adds the default systems, assets, and resources used by `bevy_ecs_ldtk`. /// /// Add it to your [App] to gain LDtk functionality! @@ -178,10 +179,15 @@ mod plugin { app = app.add_plugin(bevy_ecs_tilemap::TilemapPlugin); } - app.add_stage_after( - CoreStage::Update, - LdtkStage::ProcessApi, - SystemStage::parallel(), + app.configure_set( + LdtkSystemSet::ProcessApi + .after(CoreSet::UpdateFlush) + .before(CoreSet::PostUpdate), + ) + .configure_sets( + (ProcessApiSet::PreClean, ProcessApiSet::Clean) + .chain() + .in_base_set(LdtkSystemSet::ProcessApi), ) .init_non_send_resource::() .init_non_send_resource::() @@ -191,37 +197,25 @@ mod plugin { .add_asset::() .init_asset_loader::() .add_event::() - .add_system_to_stage( - CoreStage::PreUpdate, - systems::process_ldtk_assets.label(LdtkSystemLabel::ProcessAssets), - ) - .add_system_to_stage( - CoreStage::PreUpdate, - systems::process_ldtk_levels.label(LdtkSystemLabel::LevelSpawning), - ) - .add_system_to_stage( - LdtkStage::ProcessApi, - systems::worldly_adoption.label(LdtkSystemLabel::Other), - ) - .add_system_to_stage( - LdtkStage::ProcessApi, - systems::apply_level_selection.label(LdtkSystemLabel::LevelSelection), + .add_systems( + (systems::process_ldtk_assets, systems::process_ldtk_levels) + .in_base_set(CoreSet::PreUpdate), ) - .add_system_to_stage( - LdtkStage::ProcessApi, - systems::apply_level_set - .label(LdtkSystemLabel::LevelSet) - .after(LdtkSystemLabel::LevelSelection), + .add_system(systems::worldly_adoption.in_set(ProcessApiSet::PreClean)) + .add_systems( + (systems::apply_level_selection, systems::apply_level_set) + .chain() + .in_set(ProcessApiSet::PreClean), ) - .add_system_to_stage( - LdtkStage::ProcessApi, - systems::clean_respawn_entities.at_end(), + .add_systems( + (apply_system_buffers, systems::clean_respawn_entities) + .chain() + .in_set(ProcessApiSet::Clean), ) - .add_system_to_stage( - CoreStage::PostUpdate, + .add_system( systems::detect_level_spawned_events .pipe(systems::fire_level_transformed_events) - .label(LdtkSystemLabel::Other), + .in_base_set(CoreSet::PostUpdate), ) .register_type::() .register_type::() @@ -242,7 +236,7 @@ pub mod prelude { Respawn, TileEnumTags, TileMetadata, Worldly, }, ldtk::{self, FieldValue, LayerInstance, TilesetDefinition}, - plugin::LdtkPlugin, + plugin::{LdtkPlugin, LdtkSystemSet}, resources::{ IntGridRendering, LdtkSettings, LevelBackground, LevelEvent, LevelSelection, LevelSpawnBehavior, SetClearColor, diff --git a/src/resources.rs b/src/resources.rs index e7c13f9b..b82a537d 100644 --- a/src/resources.rs +++ b/src/resources.rs @@ -63,10 +63,11 @@ impl Default for SetClearColor { } /// Option in [LdtkSettings] that determines level spawn behavior. -#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] pub enum LevelSpawnBehavior { /// Newly spawned levels will be spawned with a translation of zero relative to the /// [LdtkWorldBundle]. + #[default] UseZeroTranslation, /// Newly spawned levels will be spawned with translations like their location in the LDtk /// world. @@ -79,43 +80,27 @@ pub enum LevelSpawnBehavior { }, } -impl Default for LevelSpawnBehavior { - fn default() -> Self { - LevelSpawnBehavior::UseZeroTranslation - } -} - /// Option in [LdtkSettings] that determines the visual representation of IntGrid layers when they don't have AutoTile rules. -#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] pub enum IntGridRendering { /// Renders the tile with its corresponding color in LDtk, so it appears like it does in LDtk + #[default] Colorful, /// Does not render the tile Invisible, } -impl Default for IntGridRendering { - fn default() -> Self { - IntGridRendering::Colorful - } -} - /// Option in [LdtkSettings] that dictates how the plugin handles level backgrounds. -#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] pub enum LevelBackground { /// The level background's color (and image, if it exists) are rendered. /// The first layer of the level will be the background color. + #[default] Rendered, /// There will be no level backgrounds, not even an empty layer. Nonexistent, } -impl Default for LevelBackground { - fn default() -> Self { - LevelBackground::Rendered - } -} - /// Settings resource for the plugin. /// Check out the documentation for each field type to learn more. #[derive(Copy, Clone, Eq, PartialEq, Debug, Default, Resource)] @@ -139,7 +124,7 @@ pub enum LevelEvent { /// you want to listen for. /// If your systems are [GlobalTransform]-dependent, see [LevelEvent::Transformed]. Spawned(String), - /// Occurs during the [CoreStage::PostUpdate] after the level has spawned, so all + /// Occurs during the [CoreSet::PostUpdate] after the level has spawned, so all /// [GlobalTransform]s of the level should be updated. Transformed(String), /// Indicates that a level has despawned.