Skip to content

Commit

Permalink
Implement generic physics backend support via the PhysicsBackend enum…
Browse files Browse the repository at this point in the history
… and HandleColliders traits
  • Loading branch information
zbuc committed Sep 6, 2024
1 parent 2a9d7e4 commit 6515d5c
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 67 deletions.
5 changes: 5 additions & 0 deletions src/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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(),
}
}
}
13 changes: 6 additions & 7 deletions src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand All @@ -61,8 +58,8 @@ impl Plugin for TiledMapPlugin {
app.init_non_send_resource::<TiledCustomTileRegistry>();
}
app.init_asset::<TiledMap>()
.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));
}
}

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
15 changes: 7 additions & 8 deletions src/physics/avian.rs
Original file line number Diff line number Diff line change
@@ -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<EntityCommands<'a>> {
let rot = object_data.rotation;
let (pos, collider) = match &object_data.shape {
tiled::ObjectShape::Rect { width, height } => {
Expand Down Expand Up @@ -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;
}
};

Expand Down Expand Up @@ -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)
}
244 changes: 200 additions & 44 deletions src/physics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Box<dyn HandleColliders + Send + Sync>>),
}

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
Expand All @@ -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(
Expand All @@ -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,
);
}
}
}
Loading

0 comments on commit 6515d5c

Please sign in to comment.