diff --git a/Cargo.lock b/Cargo.lock index 5e928e0..f5627bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -532,6 +532,13 @@ dependencies = [ "wgpu-types", ] +[[package]] +name = "bevy_collision_plugin" +version = "0.1.0" +dependencies = [ + "bevy", +] + [[package]] name = "bevy_color" version = "0.18.0" @@ -1377,13 +1384,6 @@ dependencies = [ "pin-project", ] -[[package]] -name = "bevy_template" -version = "0.1.0" -dependencies = [ - "bevy", -] - [[package]] name = "bevy_text" version = "0.18.0" diff --git a/Cargo.toml b/Cargo.toml index 0798d84..61872e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ cargo-features = ["codegen-backend"] [package] -name = "bevy_template" +name = "bevy_collision_plugin" version = "0.1.0" edition = "2024" diff --git a/examples/example.rs b/examples/example.rs new file mode 100644 index 0000000..906bd01 --- /dev/null +++ b/examples/example.rs @@ -0,0 +1,9 @@ +use bevy::prelude::*; + +use bevy_collision_plugin::CollisionPlugin; + +fn main() { + App::new() + .add_plugins((DefaultPlugins, CollisionPlugin)) + .run(); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..5ae66e6 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,128 @@ +use bevy::math::bounding::IntersectsVolume; +#[warn(missing_docs)] + +use bevy::{math::bounding::{Aabb2d, BoundingCircle}, prelude::*}; + +// TODO: add more precise crate documentation +// TODO: implement event emitting on collision +/// Simple 2D collision system, inspired by Godot Engine. + +#[derive(Component)] +/// Enum of available shapes +pub enum CollisionShape { + /// Rectangle shape + Aabb { + /// The shape itself, tied to **global** position + shape: Aabb2d, + /// Start offset, used to calculate global position. + offset: Vec2, + /// Size of the shape, used to calculate global position. + size: Vec2, + }, + /// Circle shape + Circle { + /// The shape itself, tied to **global** position + shape: BoundingCircle, + /// Start offset, used to calculate global position + 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), + } + }, + } + } +} + +#[derive(Component)] +#[require(Transform)] +/// Component, handling multiple collision shapes for single entity +pub struct Collider { + /// Shapes of the collider + pub shapes: Vec, + /// 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 global position + pub fn update_shapes_position(&mut self, global_position: &Vec2) { + for shape in self.shapes.iter_mut() { + match shape { + CollisionShape::Aabb { shape, offset, size } => { + shape.min.x = global_position.x + offset.x; + shape.min.y = global_position.y + offset.y; + shape.max.x = shape.min.x + size.x; + shape.max.y = shape.min.y + size.y; + }, + CollisionShape::Circle { shape, offset } => { + shape.center.x = global_position.x + offset.x; + shape.center.y = global_position.y + offset.y; + }, + } + } + } +} + +#[derive(Component)] +pub struct UpdateShapes; + +/// Update collider shapes to match new global transform +pub fn update_collider_shapes( + mut commands: Commands, + colliders: Query<(&mut Collider, &GlobalTransform, Entity), With>, +) { + for (mut collider, transform, entity) in colliders { + collider.update_shapes_position(&transform.translation().xy()); + commands.entity(entity).remove::(); + } +} + +/// Plugin that adds collision systems +pub struct CollisionPlugin; + +impl Plugin for CollisionPlugin { + fn build(&self, app: &mut App) { + app.add_systems(Update, update_collider_shapes); + } +} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index e95b0dc..0000000 --- a/src/main.rs +++ /dev/null @@ -1,54 +0,0 @@ -use bevy::prelude::*; - -#[derive(Component)] -struct Person; - -#[derive(Component)] -struct Name(String); - -#[derive(Resource)] -struct GreetTimer(Timer); - -#[derive(Resource)] -struct UpdateTimer(Timer); - -pub struct HelloPlugin; - -impl Plugin for HelloPlugin { - fn build(&self, app: &mut App) { - app.insert_resource(GreetTimer(Timer::from_seconds(2.0, TimerMode::Repeating))); - app.insert_resource(UpdateTimer(Timer::from_seconds(10.0, TimerMode::Once))); - app.add_systems(Startup, add_people); - app.add_systems(Update, (update_people, greet_people).chain()); - } -} - - -fn add_people(mut commands: Commands) { - commands.spawn((Person, Name("Alkesey Mirnekov".to_string()))); - commands.spawn((Person, Name("Alkesey Mirnekov 2".to_string()))); - commands.spawn((Person, Name("Alkesey Mirnekov 3".to_string()))); -} - -fn greet_people(time: Res