Godot: Replace Collision Layer/Masks with Collision Groups

Created on 20 Dec 2017  Âˇ  24Comments  Âˇ  Source: godotengine/godot

The current way of managing what should collides with, works, but when the project start growing it starts to get messy and very unpleasant to work with, and for new users it gets frustrating to understand how it really works.

Groups are great to identify something as a tag like Collisions layers are meant to, and since Collision Groups are alot simplier of Collision Layers/Masks I feel like I need to explain it.

Instead of setting the Layer of the node and the Masks to tell wich layer should collide with, on Collision groups the only thing you need to set is with wich groups should collide this node, if this node is in certain group then it shoud collide, otherwise you could check overlapping (check #14884).

Let’s say on a platformer game you have 5 objects and you add them to certain groups as you would do normally:

  • Player is on "player" group
  • Block is on "solids" group
  • IceBlock is on "solids" "Ice_blocks" groups
  • InviBlock on "barrier" group
  • Enemy is on "baddies" and "solids" groups

At this point nothing should colliding with anything, even at the beginning there is no collisions set as default. Since we want the player and enemies to collide with something, we have to set it to collide with any group or groups, e.x:

  • Player will collide with everything inside "solids" group (Block, IceBlock, Enemy)
  • Enemy will collide with everything inside "solids" and "barrier" group (Block, IceBlock, Enemy)

In order to set collisions on the UI I suggest replacing the current Layer and Masks checkmarks with something that says “Collide with” with a button on the right that says Edit that pops up something like this:
image
On script you could set it with something like this_ set_collision_with("solids").

Would be cool if this function had multiple variable support so you can do something like _Enemy.set_collision_with(["solids","barrier"])_ and set collisions with a single line of code. This way Enemy will collide also with Invisible blocks so enemies won’t enter certain areas where the Player can.
If you want characters to act differently with an IceBlock then you can check with something that already exists: _get_collider().is_in_group("ice_blocks")_ they already are colliding with IceBlocks because IceBlocks are in the “solids” group.

Now, for example, Block is not set to collide with any group, but as long as it exists in the level and its In the "solids" group, then Player and Enemy still have something to collide with, Block can have values in a script so other nodes can get values from the Block when they collide or overlaps with it.

TL;DR:

  • Bodies will not collide with anything unless you tell them to and with what certain groups will collide, if player is set to collide with a “solids” group, it will collide with all the bodies inside the “solids” group.
  • Set a body to collide with something by just adding the group name to the list of collision groups on the node inspector or just with set_collision_with("group_name") .

Benefits:

  • Easy, simple and very customizable.
  • Easier to recognize in comparison of Layers.
  • You don’t have to set the Collision Layers and Masks for every PhysicsBody, just set the groups you want the body to collide with.
  • There is no set/get_layer_bit, instead, you just need to add or remove collision groups

There are times where we reach a point when the physics engine starts to get in our way.

What you guys think? What are flaws of this or the benefits of the current system? Its just an idea based on my experience using various engines and frameworks where they use a similar system.

archived feature proposal physics

Most helpful comment

Being dependent on string values to much is in my opinion is a bad idea. In case if you rename collision mask the program will not care much and it will continue to work. With such constants you will have crash and you will be obligated to fix the error (and more important you will find easily all the places where the fix is needed - at the time when you made a rename).
If you want to get values directly from project settings why not do something like this:

var LAYER = {}
func _ready():
    for idx in range(20):
        var name = ProjectSettings.get_setting("layer_names/3d_physics/layer_" + str(idx+1));
    LAYER[name] = idx;

and you can use it for example with:
set_collision_mask_bit(Constants.LAYER.YOUR_NAME, true);

This way in case of error you will get nice crash with error in concrete line in code.

All 24 comments

No, because it's costly to test when having thousands of physics objects, whereas the current approach is just a bit and operation. In Godot 3.0 you can name your layers, so it's more similar to what you are proposing.

it should be the same as the current way, while you can name the layers, its should be just the group name, diferences are, you already set this "layer" when you gave it a group. Instead of setting two values on the inspector for wich layer is this body and with wich layer should collide this body, instead you just set the mask, in this case you set the groups to collide with.

the purpose of this is so you don't have to set the layer for every node since you already gave it a group.

I don't think this is a good idea, you are mixing logic groups with collision groups. If someone wanted, for example, to change the logic group of a node without changing the collision group they would need to create an extra "group" for that exception only that wouldn't be used anywhere else.

@mrcdk I think that's just confusion from using the word "Group", since nothing so far has hinted at these Collision Groups being the same as the node groups we already have.
(edit: Nevermind, it says that it's using the logic groups right there; I misread!)

I don't think this is a bad idea, in the regard that it looks like a clean way to expose the bit masks to the user, as an editor feature rather than changing anything in the backend.
Where currently you use the kind of unintuitive bitmask editor with the 20 little boxes that you can give names (once you find that setting at the bottom of the project settings), instead you pop up a little popupmenu that looks like the picture in the original post, which lets you add your layer masks there. Only instead of titling it "Groups" you title it "Collides with" and "Collides as", or something, with an additional button to edit the collision layer names from the popup itself.

Add a few functions that let you get the exact collision mask of whichever layer names you want and the user can basically use collision layers without ever having to figure out that it's just a bunch of bit flags on the backend.
Something like get_collision_mask("solids", "collectibles"), which would then return the collision mask for those two groups, which can be plugged into direct state access addresses, other nodes' collision masks and what have you.

Whether this is in any way necessary I don't know, but I think it could be a cleaner approach than the current mess of boxes and bits, especially since (and I think I already mentioned this in some other issue), this is so far the only appearance of bitwise operations in the Godot UI anywhere.

@mrcdk If you mean by deleting a group or changing a group name, you can have multiple groups in a node, with the example I explained, every PhysicsBody that is in the "solid" group, the enemy and the player will collide with it, if for some reason you´re using the "solid" group for something else too then you gotta be careful with that.

I see this as an UI issue. If there were some interface sugar to abstract this away, it would be interesting. Not to remove the collision layers / mask (not even from the inspector), but just add another interface bit to help you manage them. Similar to the Sprite region editor, it's a sugar interface to avoid setting the values manually, while it's still possible. That said, I don't think the underlying API should change, so doing things from script should remain the same.

I also don't like the idea of nothing colliding by default, that's counter-intuitive and will trip beginners off.

@leoddd I don't think having to add the collision layers names for each node is easier or more intuitive than this:
https://i.imgur.com/3G1Br26.gifv
(it's just a quick example, I may have goofed something 😅 )

At the end of the day, you still need to name the collision layers somehow. I agree that naming the layers is a bit hidden... maybe adding a button in the layer popup to name or rename the layers so the user knows that that's actually a thing.

@vnen This is not just a UI issue, on script is actually a _bit_ messed up, I assume you didn't read but I suggested a new function to determine collisions set_collision_with("solids") or even set_collision_with("solids", "barrier", ...) to set multiple collision groups, to delete them just use something like disable_collision_with("solids") or disable_collision_with("solids", "barrier", ...)

instead of

  • set_collision_mask( int mask )
  • set_collision_mask_bit( int bit, bool value )
  • set_layer_mask( int mask )
  • set_layer_mask_bit( int bit, bool value )

I think what will trip begginers more is to learn using Collision Layers/Masks. That's just a small detail, because there is no way godot can predict what group you'll put there. But in this way people will learn straight away that they can set what will collide with that, if you want everything to collide with everything just create a group named "solid" and add all the bodies in there.

@mrcdk There you have to add the collision layer AND collision mask, you have to check them true or false, and in order to give them names you need to go into the project settins. Instead with groups you already are giving them a name while creating a group on a node so this node is in this group. There, two steps in one.

Then for setting the Masks you just click on Add collision group and it shows a list like this, it should be empty if you haven't added any group here:
image
Instead of showing you ALL the collision Layers and masks that exist or could exist, it shows a list of group names that should be empty, if you haven't add any, where you add the "layers" or groups names you want this particular body to collide with, like a collision exception list but backwards.

The problem is not about naming the layers or hiding the background logic, the problem is all about the bits and setting them true or false on script, and while on the UI you have to put checkmarks on Layers AND Masks.

@aaronmzn I think they realized that that was your original intent, but reduz' comment pretty handily summed up that that is not a thing that will happen.
The current bitwise logic is MUCH faster computation wise than checking an unknown amount of groups for each body, which is a real concern in something that runs as often as the collision checks, which thus needs to be as optimized as possible.
Everyone else's responses are discussing ways to "fake" the UI you were proposing for it, to make it easier to work with bit masks without actually changing how it works (because that is not a thing that's desired).

@mrcdk I'd disagree with that. The current method isn't, like, impossible to work with or anything, but it feels hamfisted, visually looks really ugly compared to everything else in Godot's UI and, again, it's the only time Godot exposes bit masks to the user, meaning a lot of people who have no clue what a bit even is will be REALLY confused about how to calculate the bit mask value in code or what those boxes mean, while a clean UI would alleviate that entirely.

If not a whole new UI system for collision layers specifically (such as the one being discussed here, using a "tag" system that just set bits on the backend), at the very least it should be discussed how to make the current bit editor prettier.
The in-Inspector representation likes to cut through the boundaries of the Inspector and the popup is just a completely unorganized mess of 20 checkboxes with variable space between them thanks to the name labels.

@leoddd I understand the current bitwise logic is much faster and I won't discuss that, But in some way I'm saying if there is an automatic way to asign a bit value to a group (like how variables works) in terms of when they where created and that would let you use functions like set_collision_with("solids", "barrier", ..) or disable_collision_with("solids", "barrier", ...) without affecting the performance then that would be great. it will be more readable in script and you don´t have to take a look in the UI or a list that you make aside to check what bit is what for.
`
But still if only the UI gets changed to something close to what I suggested then I think is good enough.

@aaronmzn but why dont you code your own function, it would be easy to have the behavior you want with the things godot already offer, if you want this in lets say characters, you can create a base class that is kinematic body and then add such function, so now you can have, player, enemies, npcs that inherit that base class with your function available.

@aaronmzn

@vnen This is not just a UI issue, on script is actually a bit messed up, I assume you didn't read but I suggested a new function to determine collisions set_collision_with("solids") or even set_collision_with("solids", "barrier", ...)

Script is not messed up, like always in such cases you should represent your numbers somehow to maintain readability:

set_collision_mask_bit(Constants.LAYER_HERO, true);
set_collision_layer(Constants.LAYER_HERO | Constants.LAYER_ENEMIES);

Couldn't the proposed function translate from the existing names to the existing bitmask?

Like, say you can currently name the first layer "hero".
Now, instead of set_collision_mask_bit(0, true) you do set_collision_mask("hero", true) and it translates "hero" to "0" once.

Just a suggestion on the current discussion however, no sure how useful (or not) this would be or how big of a problem the current behaviour is.

Being dependent on string values to much is in my opinion is a bad idea. In case if you rename collision mask the program will not care much and it will continue to work. With such constants you will have crash and you will be obligated to fix the error (and more important you will find easily all the places where the fix is needed - at the time when you made a rename).
If you want to get values directly from project settings why not do something like this:

var LAYER = {}
func _ready():
    for idx in range(20):
        var name = ProjectSettings.get_setting("layer_names/3d_physics/layer_" + str(idx+1));
    LAYER[name] = idx;

and you can use it for example with:
set_collision_mask_bit(Constants.LAYER.YOUR_NAME, true);

This way in case of error you will get nice crash with error in concrete line in code.

@djrm @kubecz3k yea I could do that but even if I did it or not, I had to do it on every project, I´m proposing this here so its already implemented in the engine right off the bat so its simplier to work with collisions on UI and on the script even, when you got figured out how collision layer/masks works then there is no problem but tell me how long it took you to learn use it, unless you used unity or blender before, you will have some troubles getting it. People will start to understand how it works when they actually need it in the middle of the project and it will slows down their work figuring things out.

I see it this way, having the term "mask" in there it's confusing, right now masks seems like something different form layers when they are actually the exact same layers with another term, if you want layer 1 to collide with mask 1 you´re actually saying to collide layer 1 with layer 1 so it collides with itself, seems redundant, but when you set a "layer" automatically with a group you don't need to set the layers in the UI, instead you just have to set the layers you want it to collide with (masks) or in this case, add the groups to a list of groups to collide with.

So maybe we should simply have a hint when user hovers over the masks with something like 'what this body will collide with'. What do you think?

@kubecz3k That and the option to name layers should really be way more obvious.

@aaronmzn the concept of mask is just a bitmask and it is quite useful to filter collisions, let say you want just an area that does not report collisions because by itself it doesnt do anything but instead the player uses collision with it to check for behaviors, if the collection system does not give you the ability to set a mask this area will report collisions resulting in unwanted behavior or wasted resources.

sorry, didn't saw the last two messages before sending that last message. I'll answer them here.

@mhilbrunner
Now, instead of set_collision_mask_bit(0, true) you do set_collision_mask("hero", true) and it translates "hero" to "0" once.

Yes that would be a bit better too, but like @kubecz3k said, being dependent on strings values seems like a bad idea, so instead of treating them like string they should be treated more like some global variables that can be accessed from anywhere. Right now Groups are treated as strings too, e.x:

is_in_group("IceBlocks") instead could be is_in_group(iceBlocks) where IceBlock would return an unique ID value or the bit value for the purpose of replacing colision layers, and if you check what groups this node is in, then this should return the names as strings. Same with set_collision_mask(hero, true) where hero returns the bit value.

@djrm there is no diferences with Group Collisions, you could do the same, I based this Group collision in the current system anyway, I understand what masks are, and groups collisions would replace this. If you would change or add this body to a layer, just add this node to a group. If you want this area to check this body then add this group to the collision group in the area node, just make sure player doesn't has this group on the collision group list. It's the same, the only diference is that with Layers/Masks you have to set a layer and a mask and with groups is add the node to a group and add it to a "groups to collide" list. The benefit of this is that you'll me managing group names instead of bit and setting themn to true or false in script.

I didn't propose this hoping everyone would agree. Instead to discuss whether what is the best way to implement something similar or even if it's really worth it or needed. If not, then we can move on but I do think this should and can be improved. Also this is so the main devs have this in mind.

@aaronmzn thats actually not the use case i proposed, i said i dont want the area to detect things, so in your proposed system im not able to do that because if i leave it without a group nothing will collide with it, and i want to check in some other object if it is colliding with the area (that has not group), so there is no easy way to do that without masks. this is not a remote case by any means im using this logic in several places, i think it could also work with your proposal but as i said it will waste resources because collision checking will have to be performed in both objects.

And of course nobody is simply denying your proposal but you have to prove that your method is better and covers all the use cases supported by the current system.

For just grouping a set of layers and masks into some name to be selected without having to remember the combinations, a plugin can be enough.

@djrm you're confusing me now... areas can't collide with anything. if you want a body/area 1 to be detected by a body/area 2, you have to set body/area 1 in a group and then add this group to the collision group list of the area/body 2 in order to detect it, exactly the same with the current system.

If you don't add the collision group or lets say you don't check the mask to true where this body/area 1 is, then for the body/area 2, area/body 1 won't exist, exactly the same with the current system.

if body 1 has no group and body 2 is in a group and you set body 1 to collide with the group body 2 is in, then body 1 will collide with body 2. but if body 2 was an area instead then area 2 won't detect body 1 because body 1 has no group to add on the collision group list. Same with Layer/Masks.

  • Layers = Groups
  • Masks = Collision Groups (or groups this body will collide/detect)
  • if body will collide with "solid" groups is the same as, body will collide with the Mask 3
  • body has layer 0 check, same as, body is in group "player"
  • body has mask, 1, 2, 3 checked == body is set to collide with "player", "solid", "pancakes" groups.

it will waste resources because collision checking will have to be performed in both objects.

can you explain me that better

@eon-s don't know how to do that

what I prefer doing is having a constants.gd

class COLLISIONS:
    enum COLLISION_GROUPS {
        DEFAULT = int(pow(2,0)),
        GROUND_MAP = int(pow(2,1)),
        DEFAULT_UNITS = int(pow(2,2)),
    }

and then in my unit.gd

I can do stuff like

_ready():
    $StaticBody.collision_layer = (Constants.COLLISIONS.GROUND_MAP & Constants.COLLISIONS.DEFAULT)

just do bitwise logic on named enums. Though I wish there was a way for my secript to sync to the godot 3.0 named layer hints so that I wouldn't have to sync manually.

I'll close this due to lack of support. If you still have a need for this, please open a proposal on the Godot Proposals repository.

Was this page helpful?
0 / 5 - 0 ratings