Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

ContactFilter2D for raycasting is unintuitive

Discussion in 'Physics' started by heskey30, Jun 5, 2017.

  1. heskey30

    heskey30

    Joined:
    May 3, 2014
    Posts:
    3
    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.
     
    Mikael-H and Deeeds like this.
  2. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,316
    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.
     
    dotsquid and hippocoder like this.
  3. heskey30

    heskey30

    Joined:
    May 3, 2014
    Posts:
    3
    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.
     
    Mikael-H likes this.
  4. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,316
    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.
     
    Last edited: Jun 6, 2017
  5. heskey30

    heskey30

    Joined:
    May 3, 2014
    Posts:
    3
    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.
     
  6. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,316
    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).
     
    Last edited: Jun 6, 2017
  7. basboi

    basboi

    Joined:
    Nov 11, 2016
    Posts:
    14
    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.
     
    Last edited: Jan 30, 2018
  8. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,316
    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.
     
  9. GoalLine

    GoalLine

    Joined:
    Mar 23, 2017
    Posts:
    8
    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.
    Code (CSharp):
    1.  
    2. ContactFilter2D filter;
    3. filter.NoFilter();
    4. RaycastHit2D[] hits;
    5. 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. :/

    Code (CSharp):
    1. RaycastHit2D hits = Physics2D.Raycast(m_GroundCheck.position, -m_GroundCheck.right, 2f);
    2. if(hits.collider != null && hits.collider.tag == "Ground"){
    3.     Debug.Log("Hit ground left");
    4. }
    This code works obviously, but I im still stuck on the ContactFilter method.
     
    Last edited: Aug 30, 2018
    Deeeds likes this.
  10. laszlar

    laszlar

    Joined:
    Jul 9, 2014
    Posts:
    1
    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.
     
  11. Deeeds

    Deeeds

    Joined:
    Mar 15, 2018
    Posts:
    739
    The age of this thread is irrelevant to its significance.
     
    reinfeldx, Mikael-H and Autoquark like this.
  12. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,316
    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.
     
  13. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,316
    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.
     
  14. Momcat

    Momcat

    Joined:
    Oct 12, 2017
    Posts:
    1
    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.



    Code (CSharp):
    1.  
    2. ContactFilter2D filter = new ContactFilter2D();
    3. filter.useTriggers = false;
    4. RaycastHit2D[] hits;
    5. if(Physics2D.Raycast(myposition, mydirection, filter, hits))
    6. {
    7.  
    8. //bunch of code
    9. }
    Trying to understand MelvMay's explanation, I also tried,
    Code (CSharp):
    1. int numOfHits;
    2.  
    3. 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?
     
  15. p3k07

    p3k07

    Joined:
    Mar 30, 2014
    Posts:
    10
    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);
     
  16. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,316
    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: https://gyazo.com/40bfd19023fccaef7f3e91b7a3a9162f
     
    basboi and Treeboater like this.
  17. nbg_yalta

    nbg_yalta

    Joined:
    Oct 3, 2012
    Posts:
    378
    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:

    Code (CSharp):
    1. private RaycastHit2D wallHit;
    2.     bool IsWall
    3.     {
    4.         get
    5.         {
    6.             wallHit = Physics2D.Raycast(box.bounds.center, Vector2.right * moveDirection, 1);
    7.             if (wallHit.collider)
    8.                 return wallHit.collider.CompareTag("wall");
    9.             return false;
    10.         }
    11.     }
    12.