use bevy::prelude::*;
#[cfg(feature = "physics")]
use avian2d::prelude::*;
use crate::entity_registry::{EntityProperties, MapEntityMarker};
use crate::MapRoot;
#[cfg(feature = "physics")]
use bevy_map_core::{ColliderConfig, MapProject, PhysicsBodyType, PhysicsConfig};
pub struct MapEntityPhysicsPlugin;
#[cfg(feature = "physics")]
impl Plugin for MapEntityPhysicsPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
Update,
spawn_entity_physics.run_if(any_with_component::<MapEntityMarker>),
);
}
}
#[cfg(not(feature = "physics"))]
impl Plugin for MapEntityPhysicsPlugin {
fn build(&self, _app: &mut App) {
bevy::log::warn!(
"MapEntityPhysicsPlugin requires the 'physics' feature. Enable it with: \
bevy_map_runtime = {{ features = [\"physics\"] }}"
);
}
}
#[derive(Component)]
pub struct EntityPhysicsSpawned;
#[cfg(feature = "physics")]
fn spawn_entity_physics(
mut commands: Commands,
entity_query: Query<
(Entity, &MapEntityMarker, Option<&EntityProperties>),
(Added<MapEntityMarker>, Without<EntityPhysicsSpawned>),
>,
map_root_query: Query<&MapRoot>,
map_assets: Res<Assets<MapProject>>,
) {
let project = map_root_query
.iter()
.find_map(|root| map_assets.get(&root.handle));
let Some(project) = project else {
return;
};
for (entity, marker, entity_props) in entity_query.iter() {
let Some(type_config) = project.get_entity_type_config(&marker.type_name) else {
commands.entity(entity).insert(EntityPhysicsSpawned);
continue;
};
let Some(base_physics) = &type_config.physics else {
commands.entity(entity).insert(EntityPhysicsSpawned);
continue;
};
let physics_config: PhysicsConfig = if let Some(props) = entity_props {
if let Some(ref physics_overrides) = props.component_overrides.physics {
base_physics.with_overrides(physics_overrides)
} else {
base_physics.clone()
}
} else {
base_physics.clone()
};
spawn_physics_components(&mut commands, entity, &physics_config);
commands.entity(entity).insert(EntityPhysicsSpawned);
info!(
"Spawned physics for entity '{}' (type: {})",
marker.instance_id, marker.type_name
);
}
}
#[cfg(feature = "physics")]
fn spawn_physics_components(commands: &mut Commands, entity: Entity, config: &PhysicsConfig) {
let rigid_body = match config.body_type {
PhysicsBodyType::Dynamic => RigidBody::Dynamic,
PhysicsBodyType::Kinematic => RigidBody::Kinematic,
PhysicsBodyType::Static => RigidBody::Static,
};
let collider = match &config.collider {
ColliderConfig::Box { width, height } => Collider::rectangle(*width, *height),
ColliderConfig::Capsule { width, height } => {
let radius = width / 2.0;
let shaft_height = (height - width).max(0.0);
Collider::capsule(shaft_height, radius)
}
ColliderConfig::Circle { radius } => Collider::circle(*radius),
};
commands.entity(entity).insert((
rigid_body,
collider,
GravityScale(config.gravity_scale),
Friction::new(config.friction),
Restitution::new(config.restitution),
LinearDamping(config.linear_damping),
));
if config.lock_rotation {
commands.entity(entity).insert(LockedAxes::ROTATION_LOCKED);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_plugin_compiles() {
let _plugin = MapEntityPhysicsPlugin;
}
}