Skip to content

Commit

Permalink
feat!: add LevelIndices type defining a level's location in a proje…
Browse files Browse the repository at this point in the history
…ct and use it in `LevelSelection::Indices` (#221)

To provide constant lookup of raw/loaded levels in an asset, we need to
use its world index/level index separately. Simply iterating through all
the levels with the existing `LdtkProject::iter_levels` and getting the
`.nth()` element isn't constant, even though it does chain together root
levels and world levels. This PR provides a simple type `LevelIndices`
that stores a world index and a level index, which can be used for this
kind of lookup. Methods for performing this lookup and more have been
added to `LdtkJson`/`LdtkProject`. It is also further integrated with
the API by being used in a new `LevelSelection::Indices` variant, which
replaces `LevelSelection::Index`.

Getting a level by `LevelSelection` can now be made more performant if
users are using the `Indices` variant, but this optimization will be
implemented in a future PR. Some new convenience constructor methods
have been added to `LevelSelection` to ease the migration.

# Changes

- add `LevelIndices` type
- add `iter_levels_with_indices` methods to `LdtkProject` and `LdtkJson`
that iterate through levels while enumerating them using the new type
- add `get_level_at_indices` methods to `LdtkProject` and `LdtkJson` for
constant lookup of levels with `LevelIndices`
- add `LevelSelection::index` constructor method for easing migration to
new variant for most users
- add `LevelSelection::indices` constructor method for more
ergonomically using the new variant in multi-world projects

## Breaking

feat!: replace `LevelSelection::Index` with `LevelSelection::Indices`
variant that uses `LevelIndices` internally
  BREAKING-CHANGE: Since it has been replaced by
`LevelSelection::Indices`, any usage of `LevelSelection::Index` will
break. Construction of the new variant can easily be migrated for
non-multi-world projects with the `LevelSelection::index` constructor.
  • Loading branch information
Trouv authored Aug 16, 2023
1 parent 0039ed7 commit 59618fe
Show file tree
Hide file tree
Showing 10 changed files with 405 additions and 17 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ fn main() {
.add_plugins(DefaultPlugins)
.add_plugins(LdtkPlugin)
.add_system(Startup, setup)
.insert_resource(LevelSelection::Index(0))
.insert_resource(LevelSelection::index(0))
.register_ldtk_entity::<MyBundle>("MyEntityIdentifier")
.run();
}
Expand Down
2 changes: 1 addition & 1 deletion examples/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ fn main() {
)
.add_plugins(LdtkPlugin)
.add_systems(Startup, setup)
.insert_resource(LevelSelection::Index(0))
.insert_resource(LevelSelection::index(0))
.register_ldtk_entity::<MyBundle>("MyEntityIdentifier")
.run();
}
Expand Down
4 changes: 2 additions & 2 deletions examples/platformer/systems.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ pub fn camera_fit_inside_current_level(
for (level_transform, level_handle) in &level_query {
if let Some(ldtk_level) = ldtk_levels.get(level_handle) {
let level = &ldtk_level.data();
if level_selection.is_match(&0, level) {
if level_selection.is_match(&LevelIndices::default(), level) {
let level_ratio = level.px_wid as f32 / ldtk_level.data().px_hei as f32;
orthographic_projection.viewport_origin = Vec2::ZERO;
if level_ratio > ASPECT_RATIO {
Expand Down Expand Up @@ -392,7 +392,7 @@ pub fn update_level_selection(
&& player_transform.translation.x > level_bounds.min.x
&& player_transform.translation.y < level_bounds.max.y
&& player_transform.translation.y > level_bounds.min.y
&& !level_selection.is_match(&0, ldtk_level.data())
&& !level_selection.is_match(&LevelIndices::default(), ldtk_level.data())
{
*level_selection = LevelSelection::iid(ldtk_level.data().iid.clone());
}
Expand Down
2 changes: 1 addition & 1 deletion examples/traitless.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fn main() {
.add_plugins(LdtkPlugin)
.add_systems(Startup, setup)
.add_systems(Update, process_my_entity)
.insert_resource(LevelSelection::Index(0))
.insert_resource(LevelSelection::index(0))
.run();
}

Expand Down
18 changes: 15 additions & 3 deletions src/assets/ldtk_project.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
assets::{ldtk_path_to_asset_path, LdtkLevel},
assets::{ldtk_path_to_asset_path, LdtkLevel, LevelIndices},
ldtk::{LdtkJson, Level},
resources::LevelSelection,
};
Expand Down Expand Up @@ -44,14 +44,26 @@ impl LdtkProject {
self.data.iter_levels()
}

/// Iterate through all levels in the project paired with their [`LevelIndices`].
///
/// This works for multi-world and single-world projects agnostically.
/// It iterates through levels in the root first, then levels in the worlds.
pub fn iter_levels_with_indices(&self) -> impl Iterator<Item = (LevelIndices, &Level)> {
self.data.iter_levels_with_indices()
}

/// Immutable access to a level at the given [`LevelIndices`].
pub fn get_level_at_indices(&self, indices: &LevelIndices) -> Option<&Level> {
self.data.get_level_at_indices(indices)
}

/// Find a particular level using a [`LevelSelection`].
///
/// Note: the returned level is the one existent in the [`LdtkProject`].
/// This level will have "incomplete" data if you use LDtk's external levels feature.
/// To always get full level data, you'll need to access `Assets<LdtkLevel>`.
pub fn get_level(&self, level_selection: &LevelSelection) -> Option<&Level> {
self.iter_levels()
.enumerate()
self.iter_levels_with_indices()
.find(|(i, l)| level_selection.is_match(i, l))
.map(|(_, l)| l)
}
Expand Down
51 changes: 51 additions & 0 deletions src/assets/level_indices.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/// Indices pointing to the location of a level in an [`LdtkProject`] or [`LdtkJson`].
///
/// This type supports multi-world projects by storing an optional `world` index.
/// If this is present, the level index is used within that world.
/// If not, the level index is used in the project root's level collection.
///
/// [`LdtkProject`]: crate::assets::LdtkProject
/// [`LdtkJson`]: crate::ldtk::LdtkJson
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct LevelIndices {
/// The index of the world the level belongs to, if the project is multi-world.
pub world: Option<usize>,
/// The index of the level, either within a world or in the root of the project.
pub level: usize,
}

impl LevelIndices {
/// Construct a new [`LevelIndices`] pointing to a level in a world.
///
/// # Example
/// ```
/// use bevy_ecs_ldtk::prelude::*;
///
/// let level_indices = LevelIndices::in_world(1, 2);
///
/// assert_eq!(level_indices, LevelIndices { world: Some(1), level: 2 });
/// ```
pub fn in_world(world_index: usize, level_index: usize) -> LevelIndices {
LevelIndices {
world: Some(world_index),
level: level_index,
}
}

/// Construct a new [`LevelIndices`] pointing to a level in the project root.
///
/// # Example
/// ```
/// use bevy_ecs_ldtk::prelude::*;
///
/// let level_indices = LevelIndices::in_root(3);
///
/// assert_eq!(level_indices, LevelIndices { world: None, level: 3 });
/// ```
pub fn in_root(index: usize) -> LevelIndices {
LevelIndices {
world: None,
level: index,
}
}
}
3 changes: 3 additions & 0 deletions src/assets/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ pub use ldtk_level::LdtkLevel;
mod ldtk_project;
pub use ldtk_project::LdtkProject;

mod level_indices;
pub use level_indices::LevelIndices;

fn ldtk_path_to_asset_path<'b>(ldtk_path: &Path, rel_path: &str) -> AssetPath<'b> {
ldtk_path.parent().unwrap().join(Path::new(rel_path)).into()
}
Loading

0 comments on commit 59618fe

Please sign in to comment.