diff --git a/src/colliders.rs b/src/colliders.rs new file mode 100644 index 0000000..e426fb8 --- /dev/null +++ b/src/colliders.rs @@ -0,0 +1,76 @@ +use bevy::prelude::*; +use bevy_ecs_ldtk::prelude::*; + +use bevy_rapier2d::prelude::*; + +#[derive(Clone, Default, Bundle, LdtkIntCell)] +pub struct ColliderBundle { + pub collider: Collider, + pub rigid_body: RigidBody, + pub velocity: Velocity, + pub rotation_constraints: LockedAxes, + pub gravity_scale: GravityScale, + pub friction: Friction, + pub density: ColliderMassProperties, +} + +impl From<&EntityInstance> for ColliderBundle { + fn from(entity_instance: &EntityInstance) -> ColliderBundle { + let rotation_constraints = LockedAxes::ROTATION_LOCKED; + + match entity_instance.identifier.as_ref() { + "Player" => ColliderBundle { + collider: Collider::cuboid(6., 14.), + rigid_body: RigidBody::Dynamic, + friction: Friction { + coefficient: 0.0, + combine_rule: CoefficientCombineRule::Min, + }, + rotation_constraints, + ..Default::default() + }, + "Mob" => ColliderBundle { + collider: Collider::cuboid(5., 5.), + rigid_body: RigidBody::KinematicVelocityBased, + rotation_constraints, + ..Default::default() + }, + "Chest" => ColliderBundle { + collider: Collider::cuboid(8., 8.), + rigid_body: RigidBody::Dynamic, + rotation_constraints, + gravity_scale: GravityScale(1.0), + friction: Friction::new(0.5), + density: ColliderMassProperties::Density(15.0), + ..Default::default() + }, + _ => ColliderBundle::default(), + } + } +} + +#[derive(Clone, Default, Bundle, LdtkIntCell)] +pub struct SensorBundle { + pub collider: Collider, + pub sensor: Sensor, + pub active_events: ActiveEvents, + pub rotation_constraints: LockedAxes, +} + +impl From for SensorBundle { + fn from(int_grid_cell: IntGridCell) -> SensorBundle { + let rotation_constraints = LockedAxes::ROTATION_LOCKED; + + // ladder + if int_grid_cell.value == 2 { + SensorBundle { + collider: Collider::cuboid(8., 8.), + sensor: Sensor, + rotation_constraints, + active_events: ActiveEvents::COLLISION_EVENTS, + } + } else { + SensorBundle::default() + } + } +} diff --git a/src/ground_detection.rs b/src/ground_detection.rs new file mode 100644 index 0000000..391f60f --- /dev/null +++ b/src/ground_detection.rs @@ -0,0 +1,103 @@ +use std::collections::HashSet; + +use bevy::prelude::*; + +use bevy_rapier2d::prelude::*; + +#[derive(Component)] +pub struct GroundSensor { + pub ground_detection_entity: Entity, + pub intersecting_ground_entities: HashSet, +} + +#[derive(Clone, Default, Component)] +pub struct GroundDetection { + pub on_ground: bool, +} + +pub fn spawn_ground_sensor( + mut commands: Commands, + detect_ground_for: Query<(Entity, &Collider), Added>, +) { + for (entity, shape) in &detect_ground_for { + if let Some(cuboid) = shape.as_cuboid() { + let Vec2 { + x: half_extents_x, + y: half_extents_y, + } = cuboid.half_extents(); + + let detector_shape = Collider::cuboid(half_extents_x / 2.0, 2.); + + let sensor_translation = Vec3::new(0., -half_extents_y, 0.); + + commands.entity(entity).with_children(|builder| { + builder + .spawn_empty() + .insert(ActiveEvents::COLLISION_EVENTS) + .insert(detector_shape) + .insert(Sensor) + .insert(Transform::from_translation(sensor_translation)) + .insert(GlobalTransform::default()) + .insert(GroundSensor { + ground_detection_entity: entity, + intersecting_ground_entities: HashSet::new(), + }); + }); + } + } +} + +pub fn ground_detection( + mut ground_sensors: Query<&mut GroundSensor>, + mut collisions: EventReader, + collidables: Query, Without)>, +) { + for collision_event in collisions.read() { + match collision_event { + CollisionEvent::Started(e1, e2, _) => { + if collidables.contains(*e1) { + if let Ok(mut sensor) = ground_sensors.get_mut(*e2) { + sensor.intersecting_ground_entities.insert(*e1); + } + } else if collidables.contains(*e2) { + if let Ok(mut sensor) = ground_sensors.get_mut(*e1) { + sensor.intersecting_ground_entities.insert(*e2); + } + } + } + CollisionEvent::Stopped(e1, e2, _) => { + if collidables.contains(*e1) { + if let Ok(mut sensor) = ground_sensors.get_mut(*e2) { + sensor.intersecting_ground_entities.remove(e1); + } + } else if collidables.contains(*e2) { + if let Ok(mut sensor) = ground_sensors.get_mut(*e1) { + sensor.intersecting_ground_entities.remove(e2); + } + } + } + } + } +} + +pub fn update_on_ground( + mut ground_detectors: Query<&mut GroundDetection>, + ground_sensors: Query<&GroundSensor, Changed>, +) { + for sensor in &ground_sensors { + if let Ok(mut ground_detection) = ground_detectors.get_mut(sensor.ground_detection_entity) { + ground_detection.on_ground = !sensor.intersecting_ground_entities.is_empty(); + } + } +} + +/// Handles platformer-specific physics operations, specifically ground detection. +pub struct GroundDetectionPlugin; + +impl Plugin for GroundDetectionPlugin { + fn build(&self, app: &mut App) { + app.add_systems(Update, spawn_ground_sensor) + .add_systems(Update, ground_detection) + .add_systems(Update, update_on_ground); + } +}