Skip to content
Snippets Groups Projects
Verified Commit 0d2b6cc1 authored by Louis's avatar Louis :fire:
Browse files

Update to bevy 0.14, improve docs

parent aec837fd
No related branches found
No related tags found
No related merge requests found
This diff is collapsed.
[package] [package]
name = "micro_ldtk" name = "micro_ldtk"
version = "0.10.0" version = "0.11.0"
edition = "2021" edition = "2021"
authors = [ authors = [
"Louis Capitanchik <louis@microhacks.co.uk>" "Louis Capitanchik <louis@microhacks.co.uk>"
] ]
repository = "https://lab.lcr.gr/microhacks/bevy-micro-ldtk.git" repository = "https://lab.lcr.gr/microhacks/bevy-micro-ldtk.git"
description = "Load data from LDTK, index it and make it accessible through Bevy assets, extract and use autotile rules" description = "Load data from LDTK, index it and make it accessible through Bevy assets, extract and use autotile rules"
...@@ -34,7 +34,7 @@ _optional_tile_list = [] ...@@ -34,7 +34,7 @@ _optional_tile_list = []
no_panic = [] no_panic = []
[dependencies] [dependencies]
bevy = { version = "0.13", default-features = false, features = ["bevy_render", "bevy_sprite", "bevy_asset"] } bevy = { version = "0.14", default-features = false, features = ["bevy_render", "bevy_sprite", "bevy_asset", "serialize"] }
anyhow = "1.0" anyhow = "1.0"
thiserror = "1.0" thiserror = "1.0"
......
...@@ -16,8 +16,7 @@ pub fn handle_ldtk_project_events( ...@@ -16,8 +16,7 @@ pub fn handle_ldtk_project_events(
for event in events.read() { for event in events.read() {
match event { match event {
AssetEvent::LoadedWithDependencies { id } | AssetEvent::Modified { id } => { AssetEvent::LoadedWithDependencies { id } | AssetEvent::Modified { id } => {
let handle = Handle::Weak(*id); if let Some(project) = assets.get(*id) {
if let Some(project) = assets.get(handle) {
for level in project.get_all_levels() { for level in project.get_all_levels() {
if level.external_rel_path.is_none() { if level.external_rel_path.is_none() {
level_index level_index
...@@ -53,8 +52,7 @@ pub(crate) fn handle_ldtk_level_set_events( ...@@ -53,8 +52,7 @@ pub(crate) fn handle_ldtk_level_set_events(
for event in events.read() { for event in events.read() {
match event { match event {
AssetEvent::LoadedWithDependencies { id } | AssetEvent::Modified { id } => { AssetEvent::LoadedWithDependencies { id } | AssetEvent::Modified { id } => {
let handle = Handle::Weak(*id); if let Some(level) = level_sets.get(*id) {
if let Some(level) = level_sets.get(handle) {
for level in level.0.iter().flat_map(|hd| assets.get(hd)) { for level in level.0.iter().flat_map(|hd| assets.get(hd)) {
level_index level_index
.insert(level.identifier.clone(), LdtkLevel::from(level.clone())); .insert(level.identifier.clone(), LdtkLevel::from(level.clone()));
...@@ -76,8 +74,7 @@ pub fn handle_ldtk_level_events( ...@@ -76,8 +74,7 @@ pub fn handle_ldtk_level_events(
for event in events.read() { for event in events.read() {
match event { match event {
AssetEvent::LoadedWithDependencies { id } | AssetEvent::Modified { id } => { AssetEvent::LoadedWithDependencies { id } | AssetEvent::Modified { id } => {
let handle = Handle::Weak(*id); if let Some(level) = assets.get(*id) {
if let Some(level) = assets.get(handle) {
level_index.insert(level.identifier.clone(), LdtkLevel::from(level.clone())); level_index.insert(level.identifier.clone(), LdtkLevel::from(level.clone()));
update_events.send(LevelDataUpdated(level.identifier.clone())); update_events.send(LevelDataUpdated(level.identifier.clone()));
} }
......
...@@ -20,9 +20,7 @@ mod data_1_4_0; ...@@ -20,9 +20,7 @@ mod data_1_4_0;
mod data_1_5_3; mod data_1_5_3;
use bevy::asset::io::Reader; use bevy::asset::io::Reader;
use bevy::asset::{ use bevy::asset::{AssetLoader, AsyncReadExt, LoadContext, UntypedAssetId, VisitAssetDependencies};
AssetLoader, AsyncReadExt, BoxedFuture, LoadContext, UntypedAssetId, VisitAssetDependencies,
};
use bevy::prelude::{Asset, Handle}; use bevy::prelude::{Asset, Handle};
use bevy::reflect::TypePath; use bevy::reflect::TypePath;
...@@ -185,51 +183,49 @@ impl AssetLoader for LdtkLoader { ...@@ -185,51 +183,49 @@ impl AssetLoader for LdtkLoader {
type Settings = (); type Settings = ();
type Error = LdtkLoadError; type Error = LdtkLoadError;
fn load<'a>( async fn load<'a>(
&'a self, &'a self,
reader: &'a mut Reader, reader: &'a mut Reader<'_>,
_settings: &'a Self::Settings, _settings: &'a Self::Settings,
load_context: &'a mut LoadContext, load_context: &'a mut LoadContext<'_>,
) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>> { ) -> Result<Self::Asset, Self::Error> {
Box::pin(async move { let mut bytes = Vec::new();
let mut bytes = Vec::new(); reader.read_to_end(&mut bytes).await?;
reader.read_to_end(&mut bytes).await?; let project = Project::from_bytes(bytes.as_slice())?;
let project = Project::from_bytes(bytes.as_slice())?;
let levels = project
let levels = project .levels
.levels .iter()
.iter() .flat_map(|level| {
.flat_map(|level| { log::debug!(
log::debug!( "Checking if level is external: {} [{}]",
"Checking if level is external: {} [{}]", level.identifier,
level.identifier, level.external_rel_path.is_some()
level.external_rel_path.is_some() );
);
level
level .external_rel_path
.external_rel_path .as_ref()
.as_ref() .map(|path| (level.identifier.clone(), path))
.map(|path| (level.identifier.clone(), path)) })
}) .collect::<Vec<(String, &String)>>();
.collect::<Vec<(String, &String)>>();
let parent_path = load_context.path().parent().map(|pp| pp.to_path_buf());
let parent_path = load_context.path().parent().map(|pp| pp.to_path_buf()); let mut level_set = Vec::with_capacity(levels.len());
let mut level_set = Vec::with_capacity(levels.len());
for (_, path) in levels {
for (_, path) in levels { level_set.push(match &parent_path {
level_set.push(match &parent_path { Some(parent) => load_context.load::<Level>(parent.join(path)),
Some(parent) => load_context.load::<Level>(parent.join(path)), None => load_context.load::<Level>(path),
None => load_context.load::<Level>(path), });
}); }
}
load_context.add_labeled_asset( load_context.add_labeled_asset(
format!("{}ExternalLevels", project.iid), format!("{}ExternalLevels", project.iid),
LevelSet(level_set), LevelSet(level_set),
); );
Ok(project) Ok(project)
})
} }
fn extensions(&self) -> &[&str] { fn extensions(&self) -> &[&str] {
...@@ -244,18 +240,16 @@ impl AssetLoader for LdtkLevelLoader { ...@@ -244,18 +240,16 @@ impl AssetLoader for LdtkLevelLoader {
type Settings = (); type Settings = ();
type Error = LdtkLoadError; type Error = LdtkLoadError;
fn load<'a>( async fn load<'a>(
&'a self, &'a self,
reader: &'a mut Reader, reader: &'a mut Reader<'_>,
_settings: &'a Self::Settings, _settings: &'a Self::Settings,
_load_context: &'a mut LoadContext, _load_context: &'a mut LoadContext<'_>,
) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>> { ) -> Result<Self::Asset, Self::Error> {
Box::pin(async move { let mut bytes = Vec::new();
let mut bytes = Vec::new(); reader.read_to_end(&mut bytes).await?;
reader.read_to_end(&mut bytes).await?; let level = Level::from_bytes(bytes.as_slice())?;
let level = Level::from_bytes(bytes.as_slice())?; Ok(level)
Ok(level)
})
} }
fn extensions(&self) -> &[&str] { fn extensions(&self) -> &[&str] {
&["ldtkl"] &["ldtkl"]
......
...@@ -90,7 +90,7 @@ pub use __plugin::{MicroLDTKCameraPlugin, MicroLDTKPlugin}; ...@@ -90,7 +90,7 @@ pub use __plugin::{MicroLDTKCameraPlugin, MicroLDTKPlugin};
pub use assets::{LevelIndex, TileMetadata, TilesetIndex}; pub use assets::{LevelIndex, TileMetadata, TilesetIndex};
pub use camera::CameraBounder; pub use camera::CameraBounder;
pub use ldtk::{LdtkLoader, LdtkProject}; pub use ldtk::{LdtkLoader, LdtkProject};
pub use map_query::{CameraBounds, MapQuery}; pub use map_query::{CameraBounds, InstanceRef, MapQuery};
#[cfg(feature = "autotile")] #[cfg(feature = "autotile")]
pub use micro_autotile as autotile; pub use micro_autotile as autotile;
pub use pregen::{write_layer_to_texture, write_map_to_texture, Rasterise}; pub use pregen::{write_layer_to_texture, write_map_to_texture, Rasterise};
......
use std::fmt::Debug; use std::fmt::Debug;
use std::ops::Index;
use std::str::FromStr; use std::str::FromStr;
use bevy::ecs::system::SystemParam; use bevy::ecs::system::SystemParam;
...@@ -15,43 +16,116 @@ pub struct MapQuery<'w> { ...@@ -15,43 +16,116 @@ pub struct MapQuery<'w> {
index: Res<'w, LevelIndex>, index: Res<'w, LevelIndex>,
} }
#[derive(Clone)]
pub struct InstanceRef<'a> { pub struct InstanceRef<'a> {
pub entity: &'a EntityInstance, pub entity: &'a EntityInstance,
} }
impl<'a> InstanceRef<'a> { impl<'a> InstanceRef<'a> {
/// Get the leftmost pixel of this entity's anchor point
pub fn x(&self) -> i64 { pub fn x(&self) -> i64 {
self.entity.px[0] self.entity.px[0]
} }
/// Get the topmost pixel of this entity's anchor point
pub fn y(&self) -> i64 { pub fn y(&self) -> i64 {
self.entity.px[1] self.entity.px[1]
} }
/// Get the pixel width of this entity
pub fn width(&self) -> i64 { pub fn width(&self) -> i64 {
self.entity.width self.entity.width
} }
/// Get the pixel width of this entity
pub fn height(&self) -> i64 { pub fn height(&self) -> i64 {
self.entity.height self.entity.height
} }
/// Get the category that this instance belongs to. Exactly matches the string name
/// found in the LDTK entities list
pub fn get_type(&self) -> &'a String { pub fn get_type(&self) -> &'a String {
&self.entity.identifier &self.entity.identifier
} }
/// Try to get a type safe representation of this entity's type, as long as the target type
/// can be produced from a [str] representation
///
/// ## Example
///
/// ```
/// # use std::str::FromStr;
/// # use micro_ldtk::InstanceRef;
/// # use micro_ldtk::ldtk::EntityInstance;
///
/// #[derive(PartialEq, Debug)]
/// enum MyEntityType {
/// Player,
/// Monster,
/// }
///
/// impl FromStr for MyEntityType {
/// # type Err = ();
/// fn from_str(s: &str) -> Result<Self, Self::Err> {
/// match s {
/// "player" => Ok(Self::Player),
/// "monster" => Ok(Self::Monster),
/// # _ => panic!("Oh no")
/// }
/// }
/// }
///
/// let data_from_ldtk: EntityInstance = EntityInstance {
/// identifier: "player".to_string(),
/// // ...other properties
/// # smart_color: "".to_string(),
/// # grid: vec![],
/// # pivot: vec![],
/// # tags: vec![],
/// # tile: None,
/// # world_x: None,
/// # world_y: None,
/// # def_uid: 0,
/// # field_instances: vec![],
/// # height: 0,
/// # iid: "".to_string(),
/// # px: vec![],
/// # width: 0,
/// };
/// #
/// # let process_ldtk_data = || -> InstanceRef<'_> {
/// # InstanceRef {
/// # entity: &data_from_ldtk,
/// # }
/// # };
///
/// let my_entity_type: InstanceRef<'_> = process_ldtk_data();
/// assert_eq!(my_entity_type.try_get_typed_id(), Ok(MyEntityType::Player));
/// ```
pub fn try_get_typed_id<T: FromStr>(&self) -> Result<T, T::Err> { pub fn try_get_typed_id<T: FromStr>(&self) -> Result<T, T::Err> {
T::from_str(self.get_type().as_str()) T::from_str(self.get_type().as_str())
} }
/// Retrieve an associated property from this instance. Will return [serde_json::Value::Null]
/// if there is no property with the given name
pub fn property(&self, name: impl ToString) -> serde_json::Value { pub fn property(&self, name: impl ToString) -> serde_json::Value {
let target = name.to_string(); self[name].clone()
}
/// Get a reference to the inner instance of this instance ref
pub fn instance_ref(&self) -> &EntityInstance {
self.entity
}
}
impl<'a, T: ToString> Index<T> for InstanceRef<'a> {
type Output = serde_json::Value;
fn index(&self, index: T) -> &Self::Output {
let name = index.to_string();
for field in &self.entity.field_instances { for field in &self.entity.field_instances {
if field.identifier == target { if field.identifier == name {
return field return field.value.as_ref().unwrap_or(&serde_json::Value::Null);
.value
.as_ref()
.cloned()
.unwrap_or(serde_json::Value::Null);
} }
} }
serde_json::Value::Null &serde_json::Value::Null
} }
} }
...@@ -83,16 +157,19 @@ impl<'w> MapQuery<'w> { ...@@ -83,16 +157,19 @@ impl<'w> MapQuery<'w> {
// --- than the currently active one. 'active' methods are a convenience to // --- than the currently active one. 'active' methods are a convenience to
// --- call the static accessors on whatever the current level is // --- call the static accessors on whatever the current level is
/// Perform an action on each layer of the given LDTK level
pub fn for_each_layer_of(level: &LdtkLevel, mut cb: impl FnMut(&LdtkLayer)) { pub fn for_each_layer_of(level: &LdtkLevel, mut cb: impl FnMut(&LdtkLayer)) {
for layer in level.layers() { for layer in level.layers() {
cb(layer); cb(layer);
} }
} }
/// Retrieve an iterator over every layer in the given level, regardless of type
pub fn get_layers_of(level: &LdtkLevel) -> impl DoubleEndedIterator<Item = &LdtkLayer> { pub fn get_layers_of(level: &LdtkLevel) -> impl DoubleEndedIterator<Item = &LdtkLayer> {
level.layers() level.layers()
} }
/// Retrieve a reference to every entity stored in the given level, regardless of which layer it is found on
pub fn get_entities_of(level: &LdtkLevel) -> Vec<&EntityInstance> { pub fn get_entities_of(level: &LdtkLevel) -> Vec<&EntityInstance> {
level level
.layers() .layers()
...@@ -100,6 +177,7 @@ impl<'w> MapQuery<'w> { ...@@ -100,6 +177,7 @@ impl<'w> MapQuery<'w> {
.collect() .collect()
} }
/// Retrieve an enhanced wrapper to every entity stored in the given level, regardless of which layer it is found on
pub fn get_instance_refs_of(level: &LdtkLevel) -> Vec<InstanceRef> { pub fn get_instance_refs_of(level: &LdtkLevel) -> Vec<InstanceRef> {
level level
.layers() .layers()
...@@ -113,6 +191,8 @@ impl<'w> MapQuery<'w> { ...@@ -113,6 +191,8 @@ impl<'w> MapQuery<'w> {
.collect() .collect()
} }
/// Retrieve a reference to every entity stored in the given level that matches the specified type name.
/// This must exactly match the name shown in the LDTK entity list
pub fn get_filtered_entities_of( pub fn get_filtered_entities_of(
level: &LdtkLevel, level: &LdtkLevel,
entity_type: impl ToString, entity_type: impl ToString,
...@@ -125,6 +205,8 @@ impl<'w> MapQuery<'w> { ...@@ -125,6 +205,8 @@ impl<'w> MapQuery<'w> {
.collect() .collect()
} }
/// Retrieve an enhanced wrapper to every entity stored in the given level that matches the specified type name.
/// This must exactly match the name shown in the LDTK entity list
pub fn get_filtered_instance_refs_of( pub fn get_filtered_instance_refs_of(
level: &LdtkLevel, level: &LdtkLevel,
entity_type: impl ToString, entity_type: impl ToString,
...@@ -143,6 +225,7 @@ impl<'w> MapQuery<'w> { ...@@ -143,6 +225,7 @@ impl<'w> MapQuery<'w> {
.collect() .collect()
} }
/// Retrieve an owned copy of all entity data in the given level
pub fn get_owned_entities_of(level: &LdtkLevel) -> Vec<EntityInstance> { pub fn get_owned_entities_of(level: &LdtkLevel) -> Vec<EntityInstance> {
level level
.layers() .layers()
...@@ -150,6 +233,8 @@ impl<'w> MapQuery<'w> { ...@@ -150,6 +233,8 @@ impl<'w> MapQuery<'w> {
.collect() .collect()
} }
/// Use the size of the level to create a zero-based rectangle indicating the boundaries that a camera should
///stay within to avoid showing any out-of-level space
pub fn get_camera_bounds_of(level: &LdtkLevel) -> CameraBounds { pub fn get_camera_bounds_of(level: &LdtkLevel) -> CameraBounds {
let level = level.level_ref(); let level = level.level_ref();
CameraBounds { CameraBounds {
...@@ -160,6 +245,8 @@ impl<'w> MapQuery<'w> { ...@@ -160,6 +245,8 @@ impl<'w> MapQuery<'w> {
} }
} }
/// Check to see if the currently active level has the given name. Will always return false if there
/// is no active level
pub fn active_level_is(&self, name: impl ToString) -> bool { pub fn active_level_is(&self, name: impl ToString) -> bool {
self.active self.active
.as_ref() .as_ref()
...@@ -167,33 +254,62 @@ impl<'w> MapQuery<'w> { ...@@ -167,33 +254,62 @@ impl<'w> MapQuery<'w> {
.unwrap_or(false) .unwrap_or(false)
} }
/// Get the name of the currently active level, or `None` if no level is active
pub fn get_active_level_name(&self) -> Option<&String> { pub fn get_active_level_name(&self) -> Option<&String> {
self.active.as_ref().map(|m| &m.map) self.active.as_ref().map(|m| &m.map)
} }
/// Get a reference to the currently active level data
pub fn get_active_level(&self) -> Option<&LdtkLevel> { pub fn get_active_level(&self) -> Option<&LdtkLevel> {
self.get_active_level_name() self.get_active_level_name()
.and_then(|index| self.index.get(index)) .and_then(|index| self.index.get(index))
} }
/// Get a list of references to entities in the currently active level
/// See [MapQuery::get_entities_of]
pub fn get_entities(&self) -> Vec<&EntityInstance> { pub fn get_entities(&self) -> Vec<&EntityInstance> {
self.get_active_level() self.get_active_level()
.map(MapQuery::get_entities_of) .map(MapQuery::get_entities_of)
.unwrap_or_default() .unwrap_or_default()
} }
/// Get a list of enhanced wrapper to entities in the currently active level
/// See [MapQuery::get_instance_refs_of]
pub fn get_instance_refs(&self) -> Vec<InstanceRef> { pub fn get_instance_refs(&self) -> Vec<InstanceRef> {
self.get_active_level() self.get_active_level()
.map(MapQuery::get_instance_refs_of) .map(MapQuery::get_instance_refs_of)
.unwrap_or_default() .unwrap_or_default()
} }
/// Get a zero based rect that indicated pixel bounds for a camera
/// See [MapQuery::get_camera_bounds_of]
pub fn get_camera_bounds(&self) -> Option<CameraBounds> { pub fn get_camera_bounds(&self) -> Option<CameraBounds> {
self.get_active_level().map(MapQuery::get_camera_bounds_of) self.get_active_level().map(MapQuery::get_camera_bounds_of)
} }
/// Perform an action for every layer in the currently active level
/// See [MapQuery::for_each_layer_of]
pub fn for_each_layer(&self, cb: impl FnMut(&LdtkLayer)) { pub fn for_each_layer(&self, cb: impl FnMut(&LdtkLayer)) {
if let Some(level) = self.get_active_level() { if let Some(level) = self.get_active_level() {
Self::for_each_layer_of(level, cb); Self::for_each_layer_of(level, cb);
} }
} }
/// Search for the first listed instance ref of a given entity type in any layer within the
/// currently active level
pub fn find_one(&self, entity_type: impl AsRef<str>) -> Option<InstanceRef> {
let name = entity_type.as_ref();
self.get_instance_refs()
.iter()
.find(|ir| ir.get_type() == name)
.cloned()
}
/// Search for an instance ref with a specific IID within the currently active level
pub fn find_entity_by_iid(&self, iid: impl ToString) -> Option<InstanceRef> {
let iid = iid.to_string();
self.get_instance_refs()
.iter()
.find(|ir| ir.instance_ref().iid == iid)
.cloned()
}
} }
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::{Debug, Formatter}; use std::fmt::{Debug, Formatter};
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut, Index};
use std::path::Path; use std::path::Path;
use bevy::math::{IVec2, Rect, UVec2, Vec2}; use bevy::math::{IVec2, Rect, UVec2, Vec2};
...@@ -517,6 +517,17 @@ impl Properties { ...@@ -517,6 +517,17 @@ impl Properties {
} }
} }
impl<T: ToString> Index<T> for WorldEntity {
type Output = Value;
fn index(&self, index: T) -> &Self::Output {
self.properties
.0
.get(&index.to_string())
.unwrap_or(&Value::Null)
}
}
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub enum LayerType { pub enum LayerType {
Entities(Vec<WorldEntity>), Entities(Vec<WorldEntity>),
......
use bevy::prelude::{Component, Resource}; use bevy::prelude::{Component, IVec2, Resource};
use num_traits::AsPrimitive; use num_traits::AsPrimitive;
use crate::get_ldtk_tile_scale; use crate::get_ldtk_tile_scale;
...@@ -86,4 +86,11 @@ impl Indexer { ...@@ -86,4 +86,11 @@ impl Indexer {
x >= 0 && x < self.width && y >= 0 && y < self.height x >= 0 && x < self.width && y >= 0 && y < self.height
} }
/// Perform a transformation to flip a grid point (top down coordinates) into a render
/// point (bottom up coordinates)
pub fn flip_y(&self, point: IVec2) -> IVec2 {
let new_y = self.height() as i32 - point.y;
IVec2::new(point.x, new_y)
}
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment