Skip to content

Commit

Permalink
Use a single layer as the default membership instead of all. (#476)
Browse files Browse the repository at this point in the history
# Objective
Using `ALL` memberships requires a lot more effort to correctly use the layer system in gameplay, since interactions you wanted exclusive to one object now trigger on all defaulted objects as well.

This system is similar to those used in Box2d (one memberships, all filters) and Godot (one memberships, one filters), ~however the current system is the same as Rapier and Unity (all memberships, all filters).~ Unity is actually *exclusively* one membership, all filters with configurable filters in your project.

## Solution
- Reserve layer `1 << 0` for a default layer.
~- Adjust `PhysicsLayer` derive to start at `1 << 1`.~ keep it the same, just document the change, this shouldn't actually change any behavior aside from other special interactions relying on the default being all membership (which should be few and might indicate a bug)

## Additional Questions

~- Should we use the default layer for filters as well?~
No, this doesn't make sense unless we swap to using || for the calculation and/or allow default configuration as it forbids some valid usecases like a default member not interacting with default.

## Changelog
- Reserved `1 << 0` as a default `LayerMask`, default `CollisionLayers` now only includes a single bit for membership, while including all bits for filters.

## Migration Guide
- Make sure your `1 << 0`/`1` collision layer is fine with being a default membership, you may need to add a `CollisionLayers` component to entities that have did not have one before to allow interactions to happen again. However this may also indicate a bug in logic.

---------

Co-authored-by: Joona Aalto <jondolf.dev@gmail.com>
  • Loading branch information
Aceeri and Jondolf authored Aug 22, 2024
1 parent 571dcc6 commit e443720
Showing 1 changed file with 36 additions and 19 deletions.
55 changes: 36 additions & 19 deletions src/collision/layers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,14 @@ impl<L: PhysicsLayer> PhysicsLayer for &L {
/// #
/// #[derive(PhysicsLayer, Clone, Copy, Debug)]
/// enum GameLayer {
/// Player, // Layer 0
/// Enemy, // Layer 1
/// Ground, // Layer 2
/// Default, // Layer 0 - the default layer that objects are assigned to
/// Player, // Layer 1
/// Enemy, // Layer 2
/// Ground, // Layer 3
/// }
///
/// // Here, `GameLayer::Enemy` is automatically converted to a `LayerMask` for the comparison.
/// assert_eq!(LayerMask(0b0010), GameLayer::Enemy);
/// assert_eq!(LayerMask(0b00100), GameLayer::Enemy);
/// ```
///
/// Bitwise operations can be used to modify and combine masks:
Expand Down Expand Up @@ -105,6 +106,8 @@ impl LayerMask {
pub const ALL: Self = Self(0xffff_ffff);
/// Contains no layers.
pub const NONE: Self = Self(0);
/// Contains the default layer.
pub const DEFAULT: Self = Self(1);

/// Adds the given `layers` to `self`.
///
Expand Down Expand Up @@ -236,8 +239,12 @@ impl Not for LayerMask {
/// - The memberships of `A` contain a layer that is also in the filters of `B`
/// - The memberships of `B` contain a layer that is also in the filters of `A`
///
/// Colliders without this component can be considered as having all memberships and filters, and they can
/// interact with everything that belongs on any layer.
/// The memberships and filters are stored as [`LayerMask`]s, which represent [bitmasks] for layers.
/// The first bit `0b0001` is reserved for the default layer, which all entities belong to by default.
///
/// Colliders without this component have all filters and can interact with any layer.
///
/// [bitmasks]: https://en.wikipedia.org/wiki/Mask_(computing)
///
/// ## Creation
///
Expand All @@ -264,13 +271,17 @@ impl Not for LayerMask {
/// #
/// #[derive(PhysicsLayer)]
/// enum GameLayer {
/// Player, // Layer 0
/// Enemy, // Layer 1
/// Ground, // Layer 2
/// Default, // Layer 0 - the default layer that objects are assigned to
/// Player, // Layer 1
/// Enemy, // Layer 2
/// Ground, // Layer 3
/// }
///
/// // Player collides with enemies and the ground, but not with other players
/// let layers = CollisionLayers::new(GameLayer::Player, [GameLayer::Enemy, GameLayer::Ground]);
/// let layers = CollisionLayers::new(
/// GameLayer::Player,
/// [GameLayer::Default, GameLayer::Enemy, GameLayer::Ground],
/// );
/// ```
///
/// You can also use [`LayerMask`] directly:
Expand All @@ -291,7 +302,7 @@ impl Not for LayerMask {
/// # use bevy::prelude::Commands;
/// #
/// // `1 << n` is bitshifting: the first layer shifted by `n` layers.
/// pub const FIRST_LAYER: u32 = 1 << 0;
/// pub const FIRST_LAYER: u32 = 1 << 0; // Note: this is the default layer.
/// pub const SECOND_LAYER: u32 = 1 << 1;
/// pub const LAST_LAYER: u32 = 1 << 31;
///
Expand Down Expand Up @@ -345,6 +356,12 @@ pub struct CollisionLayers {
}

impl CollisionLayers {
/// Contains the default layer and all filters.
pub const DEFAULT: Self = Self {
memberships: LayerMask::DEFAULT,
filters: LayerMask::ALL,
};

/// Contains all memberships and filters.
pub const ALL: Self = Self {
memberships: LayerMask::ALL,
Expand Down Expand Up @@ -399,10 +416,7 @@ impl CollisionLayers {

impl Default for CollisionLayers {
fn default() -> Self {
Self {
memberships: LayerMask::ALL,
filters: LayerMask::ALL,
}
CollisionLayers::DEFAULT
}
}

Expand All @@ -418,18 +432,21 @@ mod tests {

#[derive(PhysicsLayer)]
enum GameLayer {
Default,
Player,
Enemy,
Ground,
}

#[test]
fn creation() {
let with_bitmask = CollisionLayers::new(0b0010, 0b0101);
let with_enum =
CollisionLayers::new(GameLayer::Enemy, [GameLayer::Player, GameLayer::Ground]);
let with_bitmask = CollisionLayers::new(0b00100, 0b01011);
let with_enum = CollisionLayers::new(
GameLayer::Enemy,
[GameLayer::Default, GameLayer::Player, GameLayer::Ground],
);
let with_layers =
CollisionLayers::new(LayerMask::from(GameLayer::Enemy), LayerMask(0b0101));
CollisionLayers::new(LayerMask::from(GameLayer::Enemy), LayerMask(0b01011));

assert_eq!(with_bitmask, with_enum);
assert_eq!(with_bitmask, with_layers);
Expand Down

0 comments on commit e443720

Please sign in to comment.