generated from 2ndbeam/bevy-template
177 lines
5.9 KiB
Rust
177 lines
5.9 KiB
Rust
#![warn(missing_docs)]
|
|
|
|
// TODO: add more precise crate documentation
|
|
//! Simple 2D collision system, inspired by Godot Engine.
|
|
|
|
use bevy::math::bounding::IntersectsVolume;
|
|
|
|
use bevy::{math::bounding::{Aabb2d, BoundingCircle}, prelude::*};
|
|
|
|
#[derive(Component, PartialEq, Debug)]
|
|
/// Enum of available shapes
|
|
pub enum CollisionShape {
|
|
/// Rectangle shape
|
|
Aabb {
|
|
/// The shape itself, tied to [`Transform`]
|
|
shape: Aabb2d,
|
|
/// Start offset, used to calculate current position inside shape.
|
|
offset: Vec2,
|
|
/// Size of the shape, used to calculate global position.
|
|
size: Vec2,
|
|
},
|
|
/// Circle shape
|
|
Circle {
|
|
/// The shape itself, tied to [`Transform`]
|
|
shape: BoundingCircle,
|
|
/// Start offset, used to calculate current position inside shape.
|
|
offset: Vec2,
|
|
},
|
|
}
|
|
|
|
impl CollisionShape {
|
|
/// Returns true if shapes intersect
|
|
pub fn intersects(&self, other: &CollisionShape) -> bool {
|
|
match self {
|
|
Self::Aabb { shape: aabb, .. } => {
|
|
match other {
|
|
CollisionShape::Aabb { shape: other_aabb, .. } => aabb.intersects(other_aabb),
|
|
CollisionShape::Circle { shape: circle, .. } => aabb.intersects(circle),
|
|
}
|
|
},
|
|
Self::Circle{ shape: circle, .. } => {
|
|
match other {
|
|
CollisionShape::Aabb { shape: aabb, .. } => circle.intersects(aabb),
|
|
CollisionShape::Circle { shape: other_circle, .. } => circle.intersects(other_circle),
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Create [`CollisionShape::Aabb`]
|
|
pub fn new_aabb(offset: Vec2, size: Vec2) -> Self {
|
|
let half_size = Vec2 { x: size.x / 2f32, y: size.y / 2f32 };
|
|
Self::Aabb { shape: Aabb2d::new(offset, half_size), offset, size }
|
|
}
|
|
|
|
/// Create [`CollisionShape::Circle`]
|
|
pub fn new_circle(offset: Vec2, radius: f32) -> Self {
|
|
Self::Circle { shape: BoundingCircle::new(offset, radius), offset }
|
|
}
|
|
|
|
/// Returns current centered offset for the shape
|
|
pub fn current_offset(&self) -> Vec2 {
|
|
match self {
|
|
Self::Aabb { shape, .. } => {
|
|
Vec2::new(
|
|
(shape.min.x + shape.max.x) / 2.,
|
|
(shape.min.y + shape.max.y) / 2.,
|
|
)
|
|
},
|
|
Self::Circle { shape, .. } => {
|
|
shape.center
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Component, PartialEq, Debug)]
|
|
#[require(Transform)]
|
|
/// Component, handling multiple collision shapes for single entity
|
|
pub struct Collider {
|
|
/// Shapes of the collider
|
|
pub shapes: Vec<CollisionShape>,
|
|
/// Bitmask of collider groups.
|
|
/// Setting this to 0 makes collider invisible for others
|
|
pub mask: usize,
|
|
/// Bitmask of groups which will be checked for collision.
|
|
/// Setting this to 0 makes collider ignorant to others
|
|
pub check_mask: usize,
|
|
}
|
|
|
|
impl Collider {
|
|
#[inline]
|
|
/// Returns true if any of self shapes intersect other's shapes, ignoring groups
|
|
pub fn intersects_unchecked(&self, other: &Collider) -> bool {
|
|
for shape in self.shapes.iter() {
|
|
for other_shape in other.shapes.iter() {
|
|
if shape.intersects(other_shape) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
/// Returns true if any of self shapes intersect other's shapes, respecting groups.
|
|
/// Wrapper around [intersects_unchecked](Self::intersects_unchecked)
|
|
pub fn intersects(&self, other: &Collider) -> bool {
|
|
if self.check_mask & other.mask == 0 {
|
|
return false;
|
|
}
|
|
self.intersects_unchecked(other)
|
|
}
|
|
|
|
/// Update shapes' current position
|
|
pub fn update_shapes_position(&mut self, new_position: &Vec2) {
|
|
for shape in self.shapes.iter_mut() {
|
|
match shape {
|
|
CollisionShape::Aabb { shape, offset, size } => {
|
|
shape.min.x = new_position.x + offset.x - size.x / 2.;
|
|
shape.min.y = new_position.y + offset.y - size.y / 2.;
|
|
shape.max.x = shape.min.x + size.x;
|
|
shape.max.y = shape.min.y + size.y;
|
|
},
|
|
CollisionShape::Circle { shape, offset } => {
|
|
shape.center.x = new_position.x + offset.x;
|
|
shape.center.y = new_position.y + offset.y;
|
|
},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Update collider shapes to match new [`Transform`]
|
|
pub fn update_collider_shapes(
|
|
colliders: Query<(&mut Collider, &Transform), Changed<Transform>>,
|
|
) {
|
|
for (mut collider, transform) in colliders {
|
|
collider.update_shapes_position(&transform.translation.xy());
|
|
}
|
|
}
|
|
|
|
#[derive(EntityEvent, Debug)]
|
|
/// Emitted when colliders intersect in [`check_for_collisions`] system
|
|
pub struct CollisionEvent {
|
|
/// Entity, whose [`Collider`] detected another [`Collider`]
|
|
pub entity: Entity,
|
|
/// Entity, whose [`Collider`] was detected as intersecting
|
|
pub detected: Entity,
|
|
}
|
|
|
|
fn collider_filter(checker: &&Collider, checkable: &&Collider) -> bool {
|
|
checker != checkable && checker.check_mask != 0
|
|
}
|
|
|
|
/// Check each [`Collider`] for collisions with another colliders.
|
|
/// Triggers [`CollisionEvent`] on collider intersection
|
|
pub fn check_for_collisions(
|
|
mut commands: Commands,
|
|
colliders: Query<(&Collider, Entity)>,
|
|
) {
|
|
for (collider, entity) in colliders.iter() {
|
|
for (other_collider, other_entity) in colliders.iter().filter(|(c, _)| collider_filter(&collider, c)) {
|
|
if collider.intersects(other_collider) {
|
|
commands.trigger(CollisionEvent { entity, detected: other_entity });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Plugin that adds collision systems
|
|
pub struct CollisionPlugin;
|
|
|
|
impl Plugin for CollisionPlugin {
|
|
fn build(&self, app: &mut App) {
|
|
app.add_systems(PostUpdate, (update_collider_shapes, check_for_collisions).chain());
|
|
}
|
|
}
|