Why can't an object monitor collisions without being monitored? tldr: "Mask" doesn't mask.

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By new_user

First of all thanks to everyone for creating an amazing game engine, great documentation and tutorials and an awesome community. I ran into some troubles today that caused me a lot of headaches, and after figuring it out, I’m very puzzled why this was designed the way it is.

Here goes…

Godot is designed so that if:

A has “collision layer” 10000000… and “collision mask” 01000000…

and

B has “collision layer” 01000000… and “collision mask” 00000000…

then when A and B enter each other, not only will A detect B (obviously, because they share a bit: A.mask && B.layer > 0), but B will also strangely detect A (even though A.layer && B.mask == 0).

This is consistent with the official Godot documentation:

The area’s physics layer(s). Collidable objects can exist in any of 32
different layers. A contact is detected if object A is in any of the
layers that object B scans, or object B is in any layers that object A
scans
.

In other words: the “mask” doesn’t actually mask anything. I.e. it doesn’t hide, cover or conceal collision events. It doesn’t let you specify what you don’t want to detect (using 0’s), only what you do want to detect (using 1’s) while forcing those objects to also detect the collisions regardless of their “masks”. Masking would be: A.layer && B.mask (or the opposite), not (A.layer && B.mask) || (A.mask && B.layer)

This is counter-intuitive and goes against what several tutorials claim:

From the GodotEngine.org tutorial:

collision_mask
This describes what layers the body will scan for collisions. If an object isn’t in one of the mask layers, the body will ignore it. By
default, all bodies scan layer 1.

Link: Physics introduction — Godot Engine (stable) documentation in English

This is not true. An object cannot ignore bodies/areas if those bodies/areas monitor that object.

From the KidsCanCode tutorial:

collision_mask describes what layers the body will scan for
collisions. If an object isn’t in one of the mask layers, the body
will ignore it
. By default, all bodies scan layer 1.

Link: Godot 3.0: Using KinematicBody2D · KCC Blog

Again, not true.

Several explanations online use examples like:

Player.layer = 10000000… Player.mask = 01100000…
Coin.layer = 01000000… Coin.mask = 10000000…
Enemy.layer = 0010000… Enemy.mask = 10000000…

Player detects coins and enemies. Coins detect the player. Enemies detect the player.

This gives the impression that coins and enemies detect the player because of the values of Coin.mask and Enemy.mask. However, they would still detect the player no matter what their values were, as long as Player.mask matches Coin.layer and Enemy.layer (or vice versa, the Player.mask does nothing in this example if Coin.mask and Enemy.mask match Player.layer).

My problem: In my game, if I want A to detect B, but don’t want B to detect A, then I have to do my own masking by checking things like area.name etc. while keeping track of which names I want to filter out.:

From bullet.gd:

func _on_area_entered(area):
   if area.name == "EnemyBody" or area.name == "EnemyRocket" or ... :
       hit()

Implementing my own masking system kind of defeats the whole point of having Area2D.layer and Area2D.mask.

I’m currently writing a game where certain objects will respond to bullets entering certain regions (e.g. dodge them), which I want to implement using Area2D nodes that detect the bullets. However, the bullets should only detect objects like the enemy body, rockets or other things that will actually affect the bullet.

To summarize, I believe the way “mask” currently behaves is not ideal, because:

  1. It’s not what “mask” means.
  2. It’s not intuitive.
  3. It requires you to do your own masking, if you want to actually mask collision events.
  4. There are plenty of cases where you would want to actually mask collision events and be sure to only detect certain collisions.

Why is it so, and is there any chance this will be changed in the future? (Godot 4?)

:bust_in_silhouette: Reply From: njamster

Why is it so?

Most humans are fairly certain they cannot walk through walls. But if you change your mind one day and try to walk through a wall, chances are (unless you found the way to Hogwarts) you will still collide with the wall when you try to. Why is that so? Because the wall still thinks you should! Collisions always involve two parties, so it’s not enough that one party thinks they shouldn’t collide - they need to actually agree on that!

In real life you cannot bypass that rule. However, in Godot you can! You just need to ensure you’re on no collision layer at all. So if you want an Area2D to monitor without being monitored, simply disable all layer bits and enable the mask bits of the layers you want to monitor. No need to script something.

Hi njamster. Thank you for answering :slight_smile:

Your answer proves my point. You and I both agree that this is how Area2D should behave:

So if you want an Area2D to monitor without being monitored, simply disable all layer bits and enable the mask bits of the layers you want to monitor.

That’s what one would reasonably assume, and that’s what all the tutorials (that I’ve seen) say. However, that’s not how Area2D actually behaves. Both Area2D’s will be monitoring, and both will be monitored.

I’ve made a tiny project here that shows it very clearly: https://www.sendspace.com/file/pvai8x

The example contains to Area2D objects that move towards each other and when they enter each other, each one will print a message to the console, if their _on_area_entered() function is called. As it turns out, both are called, even if one of them has a completely blank “mask”. Please try it out and see that you get:

TestObject2 was hit (by TestObject1)
TestObject1 was hit (by TestObject2)

in printed to the console.

The documentation actually says this, although not very clearly:

A contact is detected if object A is in any of the
layers that object B scans, or object B is in any layers that object A scans.

The behavior that you and I both expect would be written something like:

if A.layer & B.mask:
    B._on_area_entered()
if B.layer & A.mask:
    A._on_area_entered()

(The bitwise AND operation A.layer & B.mask is != 0 if A.layer and B.mask have one or more bits in common)

What Godot actually does is:

if (A.layer & B.mask) or (B.layer & A.mask):
    B._on_area_entered()
    A._on_area_entered()

CORRECTION: in my first post I wrote “&&” (boolean AND) instead of “&” (bitwise AND). Sorry, I’m a bit rusty :slight_smile:

new_user | 2020-05-08 07:41

As it turns out, both are called, even if one of them has a completely blank “mask”.

Indeed, two Area2D’s are a special case. Unchecking the “Monitorable”-property of “TestObject2” will fix that though. I was referring to the scenario where an Area2D monitors bodies. I agree that it’s not very intuitive at first, but this:

if (A.layer & B.mask) or (B.layer & A.mask):
    B._on_area_entered()
    A._on_area_entered()

(given my wall-example) loosely translates to this:

if (I know I will collide with the wall) or (The wall knows it will collide with me):
    The wall will notice the collision
    I will notice the collision

Whereas your understanding would translate to:

if (I know I will collide with the wall):
    I will notice the collision
if (The Wall knows it will collide with me):
    The wall will notice the collision

I’m not claiming the latter version doesn’t make sense - it does in your case! However, I think for physical collisions the former makes much more sense. That’s what I tried to highlight by my somehwat crude example about running into walls. :wink:

njamster | 2020-05-08 12:20