diff --git a/src/components.rs b/src/components.rs index 2275861..56e981d 100644 --- a/src/components.rs +++ b/src/components.rs @@ -90,6 +90,10 @@ pub struct TiledMapSettings { /// By default, the layer's offset will be used. /// For Bevy's coordinate system use MapPositioning::Centered pub map_positioning: MapPositioning, + /// Specify which physics backend to use. This is necessary for adding + /// the appropriate colliders to Bevy based on the colliders within the + /// Tiled map. + pub physics_backend: PhysicsBackend, } impl Default for TiledMapSettings { @@ -99,6 +103,7 @@ impl Default for TiledMapSettings { collision_layer_names: ObjectNames::default(), collision_object_names: ObjectNames::default(), map_positioning: MapPositioning::default(), + physics_backend: PhysicsBackend::default(), } } } diff --git a/src/loader.rs b/src/loader.rs index 5f21671..e76b096 100644 --- a/src/loader.rs +++ b/src/loader.rs @@ -41,9 +41,6 @@ use bevy::{ utils::HashMap, }; -#[cfg(feature = "physics")] -use crate::physics::{insert_object_colliders, insert_tile_colliders}; - use crate::prelude::*; use bevy_ecs_tilemap::prelude::*; use tiled::{ @@ -61,8 +58,8 @@ impl Plugin for TiledMapPlugin { app.init_non_send_resource::(); } app.init_asset::() - .register_asset_loader(TiledLoader) - .add_systems(Update, (handle_map_events, process_loaded_maps)); + .register_asset_loader(TiledLoader); + app.add_systems(Update, (handle_map_events, process_loaded_maps)); } } @@ -883,8 +880,9 @@ fn load_objects_layer( #[cfg(feature = "physics")] { + let physics_backend = &tiled_settings.physics_backend; if collision_layer_names.contains(&layer.name.trim().to_lowercase()) { - insert_object_colliders( + physics_backend.insert_object_colliders( commands, _object_entity, map_type, @@ -987,8 +985,9 @@ fn handle_special_tile( // Handle tiles with collision #[cfg(feature = "physics")] { + let physics_backend = &_tiled_settings.physics_backend; if let Some(collision) = tile.collision.as_ref() { - insert_tile_colliders( + physics_backend.insert_tile_colliders( commands, &ObjectNameFilter::from(&_tiled_settings.collision_object_names), tile_entity, diff --git a/src/physics/avian.rs b/src/physics/avian.rs index 13b19f7..092ef00 100644 --- a/src/physics/avian.rs +++ b/src/physics/avian.rs @@ -1,19 +1,18 @@ use avian2d::{math::Vector, prelude::*}; -use bevy::prelude::*; +use bevy::{ecs::system::EntityCommands, prelude::*}; use bevy_ecs_tilemap::prelude::*; use tiled::ObjectData; use crate::prelude::*; /// Insert shapes as physics colliders. -pub(crate) fn insert_avian_colliders_from_shapes( - commands: &mut Commands, +pub fn insert_avian_colliders_from_shapes<'a>( + commands: &'a mut Commands, parent_entity: Entity, _map_type: &TilemapType, grid_size: Option<&TilemapGridSize>, object_data: &ObjectData, - collider_callback: ColliderCallback, -) { +) -> Option> { let rot = object_data.rotation; let (pos, collider) = match &object_data.shape { tiled::ObjectShape::Rect { width, height } => { @@ -43,14 +42,14 @@ pub(crate) fn insert_avian_colliders_from_shapes( ) { Some(x) => x, None => { - return; + return None; } }; (Vector::ZERO, shape) } _ => { - return; + return None; } }; @@ -78,5 +77,5 @@ pub(crate) fn insert_avian_colliders_from_shapes( .insert(TransformBundle::from_transform(transform)) .insert(Name::new(format!("Collider({})", object_data.name))) .set_parent(parent_entity); - collider_callback(&mut entity_commands); + Some(entity_commands) } diff --git a/src/physics/mod.rs b/src/physics/mod.rs index 6acbfb2..48e98e9 100644 --- a/src/physics/mod.rs +++ b/src/physics/mod.rs @@ -4,12 +4,210 @@ pub mod rapier; #[cfg(feature = "avian")] pub mod avian; -use bevy::prelude::*; +use std::sync::Arc; + +use bevy::{ecs::system::EntityCommands, prelude::*}; use bevy_ecs_tilemap::prelude::*; use tiled::{ObjectData, ObjectLayerData}; use crate::prelude::*; +/// Trait defining a generic way of handling colliders +/// across different physics backends. +pub trait HandleColliders { + /// Load shapes from an object layer as physics colliders. + /// + /// By default `bevy_ecs_tiled` will only process object layers + /// named in `collision_layer_names` in `TiledMapSettings`, + /// and tileset collision shapes named in `collision_object_names`. + /// + /// Collision layer names are case-insensitive and leading/trailing + /// whitespace is stripped out. + fn insert_colliders_from_shapes( + &self, + parent_entity: Entity, + map_type: &TilemapType, + grid_size: Option<&TilemapGridSize>, + object_data: &ObjectData, + ) -> EntityCommands; +} + +#[derive(Clone, Resource)] +pub enum PhysicsBackend { + Rapier, + Avian, + None, + Custom(Arc>), +} + +impl Default for PhysicsBackend { + fn default() -> Self { + #[cfg(not(any(feature = "rapier", feature = "avian")))] + return PhysicsBackend::None; + + #[cfg(feature = "rapier")] + return PhysicsBackend::Rapier; + + #[cfg(feature = "avian")] + return PhysicsBackend::Avian; + } +} + +impl PhysicsBackend { + pub fn insert_object_colliders( + &self, + commands: &mut Commands, + object_entity: Entity, + map_type: &TilemapType, + object_data: &ObjectData, + collider_callback: ColliderCallback, + ) { + let e = match self { + PhysicsBackend::Rapier => { + #[cfg(feature = "rapier")] + { + let e = rapier::insert_rapier_colliders_from_shapes( + commands, + object_entity, + map_type, + None, + object_data, + ); + + if e.is_none() { + debug!("failed to create rapier colliders from shapes"); + return None; + } + + Some(e.unwrap()) + } + #[cfg(not(feature = "rapier"))] + { + panic!("Requested Rapier physics backend but feature is disabled"); + } + } + PhysicsBackend::Avian => { + #[cfg(feature = "avian")] + { + let e = avian::insert_avian_colliders_from_shapes( + commands, + object_entity, + map_type, + None, + object_data, + ); + + if e.is_none() { + debug!("failed to create avian colliders from shapes"); + return; + } + + Some(e.unwrap()) + } + #[cfg(not(feature = "avian"))] + { + panic!("Requested Avian physics backend but feature is disabled"); + } + } + PhysicsBackend::Custom(backend) => { + let e = backend.insert_colliders_from_shapes( + object_entity, + map_type, + None, + object_data, + ); + + Some(e) + } + PhysicsBackend::None => { + trace!("No physics backend enabled, skipping inserting object colliders"); + None + } + }; + + if let Some(mut entity_commands) = e { + collider_callback(&mut entity_commands); + } + } + + pub fn insert_tile_colliders( + &self, + commands: &mut Commands, + collision_object_names: &ObjectNameFilter, + tile_entity: Entity, + map_type: &TilemapType, + grid_size: &TilemapGridSize, + collision: &ObjectLayerData, + collider_callback: ColliderCallback, + ) { + for object_data in collision.object_data().iter() { + if collision_object_names.contains(&object_data.name.trim().to_lowercase()) { + match self { + PhysicsBackend::Rapier => { + #[cfg(feature = "rapier")] + { + let e = rapier::insert_rapier_colliders_from_shapes( + commands, + tile_entity, + map_type, + Some(grid_size), + object_data, + ); + + if e.is_none() { + debug!("failed to create rapier colliders from shapes"); + return; + } + + collider_callback(&mut e.unwrap()); + } + #[cfg(not(feature = "rapier"))] + { + panic!("Requested Rapier physics backend but feature is disabled"); + } + } + PhysicsBackend::Avian => { + #[cfg(feature = "avian")] + { + let e = avian::insert_avian_colliders_from_shapes( + commands, + tile_entity, + map_type, + Some(grid_size), + object_data, + ); + + if e.is_none() { + debug!("failed to create avian colliders from shapes"); + return; + } + + collider_callback(&mut e.unwrap()); + } + #[cfg(not(feature = "avian"))] + { + panic!("Requested Avian physics backend but feature is disabled"); + } + } + PhysicsBackend::Custom(backend) => { + let mut e = backend.insert_colliders_from_shapes( + tile_entity, + map_type, + Some(grid_size), + object_data, + ); + + collider_callback(&mut e); + } + PhysicsBackend::None => { + trace!("No physics backend enabled, skipping inserting tilecolliders"); + } + }; + } + } + } +} + /// Load shapes from an object layer as physics colliders. /// /// By default `bevy_ecs_tiled` will only process object layers @@ -23,7 +221,6 @@ pub fn insert_object_colliders( object_entity: Entity, map_type: &TilemapType, object_data: &ObjectData, - collider_callback: ColliderCallback, ) { #[cfg(feature = "rapier")] rapier::insert_rapier_colliders_from_shapes( @@ -32,50 +229,9 @@ pub fn insert_object_colliders( map_type, None, object_data, - collider_callback, ); #[cfg(feature = "avian")] - avian::insert_avian_colliders_from_shapes( - commands, - object_entity, - map_type, - None, - object_data, - collider_callback, - ); -} + avian::insert_avian_colliders_from_shapes(commands, object_entity, map_type, None, object_data); -pub fn insert_tile_colliders( - commands: &mut Commands, - collision_object_names: &ObjectNameFilter, - tile_entity: Entity, - map_type: &TilemapType, - grid_size: &TilemapGridSize, - collision: &ObjectLayerData, - collider_callback: ColliderCallback, -) { - for object_data in collision.object_data().iter() { - if collision_object_names.contains(&object_data.name.trim().to_lowercase()) { - #[cfg(feature = "rapier")] - rapier::insert_rapier_colliders_from_shapes( - commands, - tile_entity, - map_type, - Some(grid_size), - object_data, - collider_callback, - ); - - #[cfg(feature = "avian")] - avian::insert_avian_colliders_from_shapes( - commands, - tile_entity, - map_type, - Some(grid_size), - object_data, - collider_callback, - ); - } - } } diff --git a/src/physics/rapier.rs b/src/physics/rapier.rs index 8fe3cd0..a1e2af8 100644 --- a/src/physics/rapier.rs +++ b/src/physics/rapier.rs @@ -1,4 +1,4 @@ -use bevy::prelude::*; +use bevy::{ecs::system::EntityCommands, prelude::*}; use bevy_ecs_tilemap::prelude::*; use bevy_rapier2d::prelude::*; use tiled::ObjectData; @@ -6,14 +6,13 @@ use tiled::ObjectData; use crate::prelude::*; /// Insert shapes as physics colliders. -pub(crate) fn insert_rapier_colliders_from_shapes( - commands: &mut Commands, +pub(crate) fn insert_rapier_colliders_from_shapes<'a>( + commands: &'a mut Commands, parent_entity: Entity, _map_type: &TilemapType, grid_size: Option<&TilemapGridSize>, object_data: &ObjectData, - collider_callback: ColliderCallback, -) { +) -> Option> { let rot = object_data.rotation; let (pos, collider) = match &object_data.shape { tiled::ObjectShape::Rect { width, height } => { @@ -56,14 +55,14 @@ pub(crate) fn insert_rapier_colliders_from_shapes( ) { Some(x) => x, None => { - return; + return None; } }; (Vect::ZERO, shape) } _ => { - return; + return None; } }; @@ -91,5 +90,5 @@ pub(crate) fn insert_rapier_colliders_from_shapes( .insert(TransformBundle::from_transform(transform)) .insert(Name::new(format!("Collider({})", object_data.name))) .set_parent(parent_entity); - collider_callback(&mut entity_commands); + Some(entity_commands) }