Extension Methods for LayerMask

I have seen a TON of threads and tutorial videos and Discord discussions describing the differences between a LayerMask (a 32bit packed list of boolean values) and a layer index (the 0-to-31 index in the list of layer names).

If you know bitwise operators, it’s pretty easy to do the conversion once you understand these two data types. For example, the layer index 4 corresponds to the fifth binary bit in the LayerMask value (0b00000000000000000000000000010000) and you can compute it with (1 << 4). There’s all sorts of bitwise operators to combine or intersect these masks to make it easy and very highly performant to check if a renderer or collider is applicable or not.

For some reason I have never seen C# extension methods to deal with these in a more readable or “code literate” way. Surely, people have made such extensions, after having written this sort of code in long form more than once, but again, I haven’t seen any.

So here I offer mine to whomever might find them convenient.

public static void Add(ref this LayerMask mask, int layer)
{
    mask |= (1 << layer);
}

public static void Remove(ref this LayerMask mask, int layer)
{
    mask &= ~(1 << layer);
}

public static bool Contains(this LayerMask mask, int layer)
{
    return (0 != (mask & (1 << layer)));
}

public static bool Contains(this LayerMask mask, GameObject gob)
{
    if (gob == null)
        return false;
    return (0 != (mask & (1 << gob.layer)));
}

public static bool Contains(this LayerMask mask, Component component)
{
    if (component == null)
        return false;
    return (0 != (mask & (1 << component.gameObject.layer)));
}

Now you can make more readable code like cullingMask.Add(ghostLayer); (*) or if (!interestingMask.Contains(collider)) return; instead of trying to mentally parse the bit math involved every time.

(*) I almost wrote Camera.main.cullingMask.Add(ghostLayer) but that’s a property so you still have to get, modify, set such properties separately.

6 Likes

Nice… I think it just inhabits that gray area between “worth doing” and “not worth doing.”

To me layers are already annoying in that they are project-global, so I’m loathe to pile more on top.

But at least (last I checked) you don’t need to predefine them to actually use them in code. The engine is doing 32-bit masking anyway always.

“There are 10 types of people in this world: those who understand binary, and those who don’t.”

@halley1 is definitely the first type.

2 Likes

The physics system and rendering systems will definitely do this kind of math on their own, and for trivial projects, that’s all you need. But once you start layering systems it gets more complicated. You want the building system to have visible constructs only in that mode. You want to distinguish a trigger that represents a noise for your noise-sensitive component, and a person’s face for your head-attentive component, even if your character’s own awareness trigger accepts both. And it’s also good practice to have named variables that can be adjusted centrally instead of magic constants like “layer 4.”

Really nice. I always define extension methods like this for any enums, the moment that I first need to add or remove a flag from them.

I also use this helper class whenever I define a bit field enumeration type with more than a handful of members:

using System;
using static Sisus.FlagsValues;

[Flags]
public enum MyEnum
{
  None = _0,
  First = _1,
  Second = _2,
  Third = _3,
  Fourth = _4,
  Fifth = _5,
  Sixth = _6,
  Seventh = _7
}

It may not pass the WTFs/Minute quality metric the first time you see it, but I still much prefer it to having to review code with values like 131072 or 1 << 31 being assigned to members