ContactFilter2D for raycasting is unintuitive

Today I had a very frustrating experience with one of Unity’s new features. It shouldn’t have been this hard to make a raycast (2D) work. And I’ll be the first to admit, it was my fault. But in my defense I’ve been spoiled by Unity’s (usually) excellent UX.

This is a series of events that cost me over an hour of thorough debugging because of some some design decisions that… well, I don’t understand.

The new Physics2D raycast seems to have no option for both a layermask and a raycasthit output.

Well, I guess I need to use the new ContactFilter2D. I add the layers to the filter.layerMask field and put it into the raycast.

My ray collides with all layers, which is not what I wanted.

I spend a while making sure that all my layers are fine. Layers are a bit difficult, so I assume I did them wrong. The true bug didn’t occur to me because it just doesn’t make sense.

The filter.useLayerMask needs to be separately set to true even if the filter.layerMask contains something. (huh?) I fixed it.

Now my ray does not collide with triggers. Well, you can probably guess the problem, but again I didn’t realize and messed with physics settings while frantically googling for a bit.

It turns out if you use a ContactFilter2D it overrides your physics settings on raycasting and just ignores triggers unless you manually set it to hit triggers.

Well, this is not one of my shining moments of coding prowess. But it is not one of Unity’s shining moments either. Hidden defaults and unintended side effects are what I expect from my fellow game developers, not the tools I use.

It seems like useLayerMask does not need to be a public field. It should be set automatically when adding to the layerMask. It seems like useTriggers should default to the physics settings, not override them silently whenever you use ContactFilter2D.

2 Likes

So you don’t read documentation then? https://docs.unity3d.com/ScriptReference/ContactFilter2D.html

It sounds like you spent a lot of time guessing when you could’ve easily seen: https://docs.unity3d.com/ScriptReference/ContactFilter2D.SetLayerMask.html

Sets the layerMask filter property using the layerMask parameter provided and also enables layer mask filtering by setting useLayerMask to true.

So we spent time allowing you to turn options on/off without being forced to modify the arguments to those whilst also adding convenience functions that not only set the args but automatically turn on those functions to save you having to separately turn them on. Also added documentation that clearly states that.

I believe it seems you should perhaps read the documentation.

2 Likes

I’m not here for an apology or for Unity developers to admit they’re wrong. I know I’m wrong and it is a little embarrassing to post this here. But I think this could be improved.

Anyone can make code that is usable by reading the documentation.

1 Like

I’m trying to understand how it can be improved as it does what you suggested it should do i.e. automatically set the “useLayerMask” to true when you set it.

It seems like it doesn’t need both useLayerMask and a public layerMask variable. Maybe just have the layerMask variable accessors also turn on the boolean? Or maybe you could have this set up in the constructor, which is the first thing anyone looks at. Maybe both.

So how do you turn the filtering off then for each option? It does need to use “useLayerMask” otherwise there’s no way to turn the filtering off for it and the same goes for the other filter options.

Original feedback was not to do this as it’s just too many implicit actions. Instead we added methods which set them and turn the option on and were documented as doing so. This is convenient for not only turning the filter on but also setting a range in a single method.

Structs cannot have an explicit parameterless constructor in C# which is why we added “ContactFilter2D.NoFilter()” for convenience which we also used internally (when you call physics queries that don’t take a ContactFilter2D we use this to produce no filtering as all internal code go throughs the same code path).

1 Like

You’re being passive aggressive. OP was very reasonable. He even said it wasnt his shiniest moment. Your community members will vary in skill (which, as a programmer, includes reading the documentation). When googling stuff that led me here i was trying to figure out what the RaycastHit2D[ ] parameter is used for when raycasting using ContactFilter2D. I wasnt able to find it in the documentation. So while i greatly(!) appreciate the work you do, maybe there is further room for improvement in either documentation intuitivity or training resource. and if im being stupid, dont - as a company representative - get angry at me for that.

4 Likes

There’s no anger here and I took the time to explain each point carefully so I’m not sure why you’re inferring I’ll get angry at you. Note that last post was 7 months ago so this is an old thread.

You are correct and that’s always true.

I know how to use Physics.Raycast (3D) just fine, but I am also having a hard time trying to figure out this implementation to Physics2D.Raycast using ContactFilter.

ContactFilter2D filter;
filter.NoFilter();
RaycastHit2D[] hits;
if(Physics2D.Raycast(m_GroundCheck.position, m_GroundCheck.right, filter, hits)){}

MelvMay, you seem pretty confident that others are not reading the documentation. I feel the documentation is misleading. With this code above, I get an error because Physics2D.Raycast is returning an int and not a bool. I really want to figure this out rather than avoiding it. I just want to get a list of what objects were hit. :confused:

RaycastHit2D hits = Physics2D.Raycast(m_GroundCheck.position, -m_GroundCheck.right, 2f);
if(hits.collider != null && hits.collider.tag == "Ground"){
    Debug.Log("Hit ground left");
}

This code works obviously, but I im still stuck on the ContactFilter method.

2 Likes

I very much agree with you, and I’m having a similar problem. Sometimes the documentation gives a brief example on how it’s used, and case uses. In this case, there is very little information on how to use it. Thank you [MelvMay] for pointing us to a variable to pass in the documentation, but that’s all there is. Maybe the documentation should be updated.

The age of this thread is irrelevant to its significance.

3 Likes

That isn’t very useful at all.

If you have a specific question I’d be happy to try to answer it. If that leads to something specific that needs adding to the docs then so much the better and I can discuss that with the docs team.

Sorry, I must’ve seen the notification a while back but then forgot to come back to it.

So in the docs, it shows two overloads of the Raycast method. The first returns a RaycastHit2D (not a bool) and the second (which allows you to specify an array to put the results in) returns the number of results placed in the array. This pattern is identical to all the physics queries (including 3D physics) where an array is provided by the user and the number of results placed into the array is returned. This means no allocating arrays which are then left to the GarbageCollector.

RaycastHit2D is implicitly converted to bool so can be used in statements as you indicated i.e. “if(hit) …”. That’ll be true if it’s a valid hit i.e. its “collider” property is not NULL but that obviously isn’t what you’re asking.

Beyond that, I’m not sure what extra information I can provide that’ll help here so please let me know. Again, sorry for not replying earlier.

I’m still new to coding, this is my first time casting a raycast (other than in the Unity tutorials), and I still don’t understand. YES I read the documentation, but still don’t understand it fully, and I feel like I’m following what it says to do and am still not getting results. My code is pretty identical to the one GoalLine posted.

ContactFilter2D filter = new ContactFilter2D();
filter.useTriggers = false;
RaycastHit2D[] hits;
if(Physics2D.Raycast(myposition, mydirection, filter, hits))
{

//bunch of code
}

Trying to understand MelvMay’s explanation, I also tried,

int numOfHits;

numOfHits = Physics2D.Raycast(myposition, mydirection, filter, hits);

The first one says it’s completely wrong because it should be an int, and the second one says that “hits” is an unassigned local variable. If I assign “hits” to a number, then I’m limiting how many hits it can have. What if I want to assign it the same number of hits it’s going to actually get?? So I can return ALL the hits, instead of a specific number of them?

Lol, a google lead me to here but the link after this in another thread here helped me figure out what I was doing wrong. It’s a pretty simple mechanic really. I don’t know what all the fuss was about. But it was an interesting read.

The part I struggled with was how to use CF2D.SetLayerMask to multiple layers without using a global variable. But that was a brain fart really. And just in case any one is wondering the same its;

var lm = 1 << LayerMask.NameToLayer(“L1”) | 1 << LayerMask.NameToLayer(“L2”);
var cf = new ContactFilter2D();
cf.SetLayerMask(lm);

One thing to note is that ContactFilter2D is serializable so you can add it as a public member to your script and set it directly in the inspector which can make things significantly quicker like so: Screenshot - 40bfd19023fccaef7f3e91b7a3a9162f - Gyazo

3 Likes

Hello, I have Queries Hit Triggers disabled in my physics settings, but I need to set particular raycasts to interact with triggers… I found that Contact Filter is an option, but cant figure out how to use it my case… Here is the code:

private RaycastHit2D wallHit;
    bool IsWall
    {
        get
        {
            wallHit = Physics2D.Raycast(box.bounds.center, Vector2.right * moveDirection, 1);
            if (wallHit.collider)
                return wallHit.collider.CompareTag("wall");
            return false;
        }
    }