Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

Weird behaviour on TilemapCollider2D

Discussion in '2D' started by paul-masri-stone, Nov 26, 2020.

  1. paul-masri-stone

    paul-masri-stone

    Joined:
    Mar 23, 2020
    Posts:
    49
    I have a Tilemap whose tiles each have a collider defined in the sprite as a square. Because I want to separately detect collisions for each tile, I am using TilemapCollider2D without a composite collider. I have set this to isTrigger false.

    I also have an object with a Rigidbody2D, with continuous collision detection and 0.0001 mass and a CircleCollider2D set to isTrigger false.

    What I believe should happen is that the script on the tilemap should respond with OnCollisionEnter2D each time the circle collider enters a new tile in the grid (or a new face of a tile collider). Am I mistaken?

    What is actually happening is that OnCollisionEnter2D happens at contact with the first tile(s) and then not again until the circle is much further into the grid, even though OnCollisionStay2D() is triggering for lots of tiles. Is this a bug? Or am I fundamentally misunderstanding how OnCollisionEnter2D works for TilemapCollider2D?

    Here's a demo. When OnCollisionEnter2D is called, a white X is drawn marking the collision contact points (for 2 seconds). When OnCollisionStay2D is called, a green + is drawn marking the collision contact points (for 0.5 seconds).

    TilemapCollider2D behaviour.gif
     
  2. ChuanXin

    ChuanXin

    Unity Technologies

    Joined:
    Apr 7, 2015
    Posts:
    1,068
    It should not. The TilemapCollider2D encompasses all Tiles, so OnCollisionEnter2D will be triggered once when the first Tile is collided with, until OnCollisionExit2D is triggered. In between, OnCollisionStay2D will trigger for all contact points.
     
    paul-masri-stone likes this.
  3. paul-masri-stone

    paul-masri-stone

    Joined:
    Mar 23, 2020
    Posts:
    49
    @ChuanXin, thanks for replying on this. I'm supremely grateful for 2d-extras.

    Thanks for clarifying...So even though I'm using TilemapCollider2D without a composite collider, it is still a single collider and provides one call to OnCollisionEnter2D() for the first tile collided with. Got it!

    Can you shed light on the weird contact positions? I have just created a minimal project to demonstrate this: https://github.com/paulmasri/unity-tilemapcollider2d-tester

    It has a game object with a kinematic Rigidbody2D, a CircleCollider2D and a script that uses Rigidbody2D.MovePosition() to move the shape downwards through the tiles at a steady speed (but no renderer). It also has tilemap with a TilemapCollider2D and containing some square sprite tiles. (The sprites do not have Generate Physics Shape checked, but it appears the physics shape is auto-generated by the tile/tilemap anyway.) The tilemap has a script that logs calls to OnCollisionEnter2D(), OnCollisionStay2D() & OnCollisionExit2D() and whether they are ENTER, STAY or EXIT and uses Debug.DrawLine() to draw a white X at contact positions for OnCollisionEnter2D() and a yellow + at contact positions for OnCollisionStay2D().

    Here are 2 gifs showing these contact positions. They appear to be correct for the first point the circle encounters on each tile, but subsequent contact points gradually move away from the collider edges of both the circle and tiles, sometimes even straying outside the tile they refer to (see close-ups on second gif), and performing strange arcs. Is this correct behaviour or a bug? Or once again a misunderstanding on my part?

    Looking at Scene view with focus on the CircleCollider2D container:
    https://github.com/paulmasri/unity-...n/TilemapCollider2D-focus-on-circle-(20s).gif

    Looking at Scene view with focus on the Tilemap:

    https://github.com/paulmasri/unity-.../TilemapCollider2D-focus-on-tilemap-(20s).gif

    Thanks,
    Paul.
     
  4. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,573
    Contact points are where forces are (or would in theory be) applied by the physics system to separate colliders. If you're in overlap then they are not necessarily on the exterior of the collider. This comes directly from Box2D. It has nothing to do with tilemap at all. In-fact, the physics system knows nothing about tiles or any Unity construct at all. It only knows about primitive physics shapes so circle, convex-polygon, capsule and edge which means there's no Unity collider specific things happening in 2D physics.

    In short, contact points are NOT results of intersection tests.
     
    paul-masri-stone likes this.
  5. paul-masri-stone

    paul-masri-stone

    Joined:
    Mar 23, 2020
    Posts:
    49
    That's fascinating... and a total surprise.

    So if I'm understanding right, the contact points (ContactPoint2D.point) are only actually the contact points of the colliders when the colliders are touching rather than overlapping. And that when they start overlapping, the term 'contact point' becomes a misnomer.

    In my case, if I want to track which tiles the circle collider is overlapping with, I need to find a different approach. (I'm thinking: work out which tiles overlap or are within the circle collider's bounding box and raycast from the centre of the collider toward the centre of each tile to give a good approximation of whether there is actual overlap.)
     
  6. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,573
    Well it's always not really appropriate to use the physics system to perform pure intersection tests. The physics system is about dynamic behaviour and not so much a utility used to perform intersection tests. Contacts are only used to know how to separate colliders which is a bad condition from the physics systems POV.

    Looking at your video, it would seem you want to perform simple circle/circle intersection test(s) i.e. large circle against other smaller circles. That's going to be faster than asking the physics system to calculate contacts with normals, impulses etc even if they're not used because it's Kinematic. Maybe you've got more complex tile setups, not sure.

    If all you have are circles in tiles then you only need to know if the distance between the large circle center and the center of any potentially overlapping tiles with those smaller circles is larger/equal to the radius of the large circle + small tile circle.
     
    paul-masri-stone likes this.
  7. paul-masri-stone

    paul-masri-stone

    Joined:
    Mar 23, 2020
    Posts:
    49
    My tiles aren't actually circles, but I'd just arrived at this solution as a good-enough approximation for my use case. Thank you!
     
    MelvMay likes this.
  8. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,573
    Cool. Feel free to ping me if you need any help. I'm on leave right now but always have my eye on the forums sir.

    Good luck.
     
  9. paul-masri-stone

    paul-masri-stone

    Joined:
    Mar 23, 2020
    Posts:
    49
  10. BulbasaurLvl5

    BulbasaurLvl5

    Joined:
    May 10, 2020
    Posts:
    42
    Hi,
    i just came across a very similar problem. I want a script to have "knowledge" of every tilemap tile in its surrounding.
    Code (CSharp):
    1.     private void OnTriggerStay2D(Collider2D other)
    2.     {
    3.         tilemap = other.gameObject.GetComponentInParent<Tilemap>();
    4.  
    5.         cellPosition = tilemap.WorldToCell(transform.position);
    6.         tilepos = tilemap.CellToWorld(cellPosition);
    7.  
    8.         if (!obstacles.Contains(tilepos))
    9.         {
    10.             obstacles.Add(tilepos);
    11.         }
    12.     }
    This is what i got so far. I got two problems/questions: when the object is created it only counts a single collision with the tilemap (also with oncollisionstay2D), but it does collect the other points as it moves.
    Second thing is, that this post made me think about my approach

    The tilemap might be big. I dont know maybe multiple thousand tiles (not sure yet). Is it really better to check the distance to each tile? I thought the collider would be better, because it just operates in its comparible tiny cell?
     
  11. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,573
    I wasn't talking about your problem and scale. The physics system isn't designed to perform such queries. In the end, it's a rigidbody simulation and it knows nothing about tiles and only performs primitive/primitive checks. It's not designed to understand the logical grids of tilemaps etc.

    Personally I think it'd be better for us to provide a non-physics solution here i.e. overlap queries of basic primitive shapes which return you the tiles covered. To be honest, that's pretty simply for you to do too but it's something that the tilemap could provide.
     
  12. paul-masri-stone

    paul-masri-stone

    Joined:
    Mar 23, 2020
    Posts:
    49
    You say "it does collect the other points as it moves". If you are using ContactPoint2D as the points, then this won't work because of the reason explained by MelvMay above. (See also link I posted above.)

    In the end my approach was as follows:
    • Each special tile has a script that handles collisions. During OnTriggerEnter2D(), it sends a UnityEvent with arguments of its own GameObject and the GameObject of the colliding object. During OnTriggerExit2D() it sends a different UnityEvent with the same arguments.
    • The object that is overlapping the tilemap has a script that receives the events and uses them to register/unregister the overlapping tile. This object can therefore keep track of all special tiles it is overlapping with.
    Although the above approach may seem complex, I've found it reliable and it works with any shape colliders.

    If your colliders are more predictable (e.g. rectangle, circle) you could take a simpler approach that's just within the overlapping object: use a mathematical equation to work out which cells you are overlapping with. And you would activate these calculations OnTriggerEnter2D() and continue doing them every frame until OnTriggerExit2D(). With these grid co-ordinates you can interrogate the tilemap for any additional data.
     
  13. BulbasaurLvl5

    BulbasaurLvl5

    Joined:
    May 10, 2020
    Posts:
    42
    Maybe it helps, if i shortly explain what i want to do. I just want some "enemy" entity to know its surroundings to enable it to make some decisions.

    Are you willing to point out the steps i would have to make? As far as i can imagine these are my options (copied from my other thread)
    • create gameobjects on the tilemap to register to the enemy perception -- this would somewhat make the usage of the tilemap redundant, because, as far as i understand, their purpose is to save gameobjects
    • make the enemies access the tilemap array, transform it to world coordinates and manually check for close tiles -- this seems hacky
    • make the enemies raycast in some angles around them and store the collision coordinate -- this might inaccurate, depending on the number of raycasts
    • somehow get used to A* and hope that it can handle this
    I hope my problem becomes clear, with each of these i have to compromise and none of them feel like they are designed to handle this. How is this done properly?

    I'm sorry, but i guess our problems are only similar that we want to detect collision of surrounding tiles. I do not really care if it is a "special tile" or the shape. I just have a randomly generated tilemap sitting in my scene and i want gameobjects to know where those tiles are. There might be "a lot" of gameobjects and the tilemap might be "big". Thus, i thought, collisions might be a good solution, because they only act in their respective cell and do not need to access the whole tilemap. I mean i could do basic math (distance check or whatever), but then i would have to make each gameobject do this check in update for each tile there is, which again, does not seem right. I do not even know how i could attach a script to a single tile in a tilemap. Again sorry if i misunderstood your topic
     
    Last edited: Mar 29, 2021